package vandy.mooc.presenter;
import java.io.File;
import java.lang.ref.WeakReference;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import vandy.mooc.MVP;
import vandy.mooc.common.GenericPresenter;
import vandy.mooc.common.Utils;
import vandy.mooc.model.ImageModel;
import vandy.mooc.model.datamodel.ReplyMessage;
import android.app.Activity;
import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
/**
* This class defines all the image-related operations. It implements
* the various Ops interfaces so it can be created/managed by the
* GenericActivity framework. It plays the role of the "Abstraction"
* in Bridge pattern and the role of the "Presenter" in the
* Model-View-Presenter pattern.
*/
public class ImagePresenter
extends GenericPresenter<MVP.RequiredPresenterOps,
MVP.ProvidedModelOps,
ImageModel>
implements MVP.ProvidedPresenterOps,
MVP.RequiredPresenterOps {
/**
* Used to enable garbage collection.
*/
private WeakReference<MVP.RequiredViewOps> mImageView;
/**
* Stores the running total number of images downloaded that must
* be handled by ServiceResultHandler.
*/
private int mNumImagesToHandle;
/**
* Stores the running total number of images that have been
* handled by the ServiceResultHandler.
*/
private int mNumImagesHandled;
/**
* Stores the directory to be used for all downloaded images.
*/
private Uri mDirectoryPathname = null;
/**
* Array of Strings that represent the valid URLs that have
* been entered.
*/
private ArrayList<Uri> mUrlList;
/**
* Constructor will choose either the Started Service or Bound
* Service implementation of ImagePresenter.
*/
public ImagePresenter() {
}
/**
* Hook method called when a new instance of AcronymPresenter is
* created. One time initialization code goes here, e.g., storing
* a WeakReference to the View layer and initializing the Model
* layer.
*
* @param view
* A reference to the View layer.
*/
@Override
public void onCreate(MVP.RequiredViewOps view) {
// Set the WeakReference.
mImageView = new WeakReference<>(view);
// Create a timestamp that will be unique.
final String timestamp =
new SimpleDateFormat("yyyyMMdd'_'HHmm").format(new Date());
// Use the timestamp to create a pathname for the
// directory that stores downloaded images.
mDirectoryPathname =
Uri.parse(Environment.getExternalStoragePublicDirectory
(Environment.DIRECTORY_DCIM)
+ "/"
+ timestamp
+ "/");
// Initialize the list of URLs.
mUrlList = new ArrayList<Uri>();
// Finish the initialization steps.
resetFields();
// Invoke the special onCreate() method in GenericPresenter,
// passing in the ImageModel class to instantiate/manage and
// "this" to provide ImageModel with this MVP.RequiredModelOps
// instance.
super.onCreate(ImageModel.class,
this);
}
/**
* Hook method dispatched by the GenericActivity framework to
* initialize the ImagePresenter object after a runtime
* configuration change.
*
* @param view The currently active ImagePresenter.View.
*/
@Override
public void onConfigurationChange(MVP.RequiredViewOps view) {
// Reset the mImageView WeakReference.
mImageView = new WeakReference<>(view);
// If the content is non-null then we're done, so set the
// result of the Activity and finish it.
if (allDownloadsComplete()) {
// Hide the progress bar.
mImageView.get().dismissProgressBar();
Log.d(TAG,
"All images have finished downloading");
} else if (downloadsInProgress()) {
// Display the progress bar.
mImageView.get().displayProgressBar();
Log.d(TAG,
"Not all images have finished downloading");
}
// (Re)display the URLs.
mImageView.get().displayUrls();
}
/**
* Hook method called to shutdown the Presenter layer.
*
* @param isChangeConfigurations
* True if a runtime configuration triggered the onDestroy() call.
*/
@Override
public void onDestroy(boolean isChangingConfigurations) {
// Destroy the model.
getModel().onDestroy(isChangingConfigurations);
}
/**
* Get the list of URLs.
*/
@Override
public ArrayList<Uri> getUrlList() {
return mUrlList;
}
/**
* Reset the URL and counter fields and redisplay linear layout.
*/
private void resetFields() {
// Reset the number of images to handle and which have been
// handled.
mNumImagesHandled = 0;
mNumImagesToHandle = 0;
// Clear the URL list.
mUrlList.clear();
// Redisplay the URLs, which should now be empty.
mImageView.get().displayUrls();
}
/**
* Start all the downloads and processing. Plays the role of the
* "Template Method" in the Template Method pattern.
*/
@Override
public void startProcessing() {
if (mUrlList.isEmpty())
Utils.showToast(mImageView.get().getActivityContext(),
"no images provided");
else {
// Make the progress bar visible.
mImageView.get().displayProgressBar();
// Keep track of number of images to download that must be
// displayed.
mNumImagesToHandle = mUrlList.size();
// Iterate over each URL and start the download.
for (final Uri url : mUrlList)
getModel().startDownload(url,
mDirectoryPathname);
}
}
/**
* Interact with the View layer to display the downloaded images
* when they are all returned from the Model.
*/
@Override
public void onDownloadComplete(ReplyMessage replyMessage) {
// Increment the number of images handled regardless of
// whether this result succeeded or failed to download and
// image.
++mNumImagesHandled;
if (replyMessage.getResultCode() == Activity.RESULT_CANCELED)
// Handle a failed download.
mImageView.get().reportDownloadFailure
(replyMessage.getImageURL(),
allDownloadsComplete());
else /* replyMessage.getResultCode() == Activity.RESULT_OK) */
// Handle a successful download.
Log.d(TAG,
"received image at URI "
+ replyMessage.getImagePathname().toString());
// Try to display all images received successfully.
tryToDisplayImages();
}
/**
* Launch an Activity to display all the images that were received
* successfully if all downloads are complete.
*/
private void tryToDisplayImages() {
// If this is last image handled, display images via
// DisplayImagesActivity.
if (allDownloadsComplete()) {
// Dismiss the progress bar.
mImageView.get().dismissProgressBar();
// Initialize state for the next run.
resetFields();
// Only start the DisplayImageActivity if the image folder
// exists and also contains at least 1 image to display.
// Note that if the directory is empty, File.listFiles()
// returns null.
File file = new File(mDirectoryPathname.toString());
if (file.isDirectory()
&& file.listFiles() != null
&& file.listFiles().length > 0) {
// Display the results.
mImageView.get().displayResults(mDirectoryPathname);
}
}
}
/**
* Returns true if all the downloads have completed, else false.
*/
private boolean allDownloadsComplete() {
return mNumImagesHandled == mNumImagesToHandle
&& mNumImagesHandled > 0;
}
/**
* Returns true if there are any downloads in progress, else false.
*/
private boolean downloadsInProgress() {
return mNumImagesToHandle > 0;
}
/**
* Delete all the downloaded images.
*/
@Override
public void deleteDownloadedImages() {
// Delete all the downloaded image.
int fileCount = deleteFiles(mDirectoryPathname,
0);
// Indicate how many files were deleted.
Utils.showToast(mImageView.get().getActivityContext(),
fileCount
+ " downloaded image"
+ (fileCount == 1 ? " was" : "s were")
+ " deleted.");
// Reset the fields for the next run.
resetFields();
}
/**
* A helper method that recursively deletes files in a specified
* directory.
*/
private Integer deleteFiles(Uri directoryPathname,
int fileCount) {
File imageDirectory =
new File(directoryPathname.toString());
File files[] = imageDirectory.listFiles();
if (files == null)
return fileCount;
// Android does not allow you to delete a directory with child
// files, so we need to write code that handles this
// recursively.
for (final File f : files) {
final Uri fileOrDirectoryName = Uri.parse(f.toString());
if (f.isDirectory())
fileCount += deleteFiles(fileOrDirectoryName,
fileCount);
Log.d(TAG,
"deleting file "
+ fileOrDirectoryName.toString()
+ " with count "
+ fileCount);
++fileCount;
f.delete();
}
imageDirectory.delete();
return fileCount;
}
/**
* Return the Activity context.
*/
@Override
public Context getActivityContext() {
return mImageView.get().getActivityContext();
}
/**
* Return the Application context.
*/
@Override
public Context getApplicationContext() {
return mImageView.get().getApplicationContext();
}
}