package vandy.mooc.presenter;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import vandy.mooc.MVP;
import vandy.mooc.R;
import vandy.mooc.common.GenericPresenter;
import vandy.mooc.model.ImageDownloadsModel;
import vandy.mooc.presenter.strategies.DownloadContext;
import vandy.mooc.presenter.strategies.DownloadWithAsyncTask;
import vandy.mooc.presenter.strategies.DownloadWithMessages;
import vandy.mooc.presenter.strategies.DownloadWithRunnables;
import vandy.mooc.presenter.strategies.ImageStrategy;
import vandy.mooc.presenter.strategies.ResetBitmap;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
/**
* This class plays the "Presenter" role in the Model-View-Presenter
* (MVP) pattern by acting upon the Model and the View, i.e., it
* retrieves data from the Model (e.g., ImageDownloadsModel) and
* formats it for display in the View (e.g., ImageDownloadsActivity).
* It expends the GenericPresenter superclass and implements
* MVP.ProvidedPresenterOps and MVP.RequiredModelOps so it can be
* created/managed by the GenericPresenter framework.
*/
public class ImageDownloadsPresenter
extends GenericPresenter<MVP.RequiredPresenterOps,
MVP.ProvidedModelOps,
ImageDownloadsModel>
implements MVP.ProvidedPresenterOps,
MVP.RequiredPresenterOps {
/**
* A WeakReference used to access methods in the View layer. The
* WeakReference enables garbage collection.
*/
private WeakReference<MVP.RequiredViewOps> mView;
/**
* Maps buttons (represented via their resource ids) to
* ImageStrategy implementations.
*/
private ButtonToImageStrategyMapper mButtonToImageStrategyMapper;
/**
* The currently active ImageStrategy, which is used to keep track
* of whether we need to cancel an ongoing download before
* initiating a new one.
*/
private ImageStrategy mActiveImageStrategy = null;
/**
* Stores the current image to redisplay after a runtime
* configuration change.
*/
private Bitmap mCurrentImage = null;
/**
* Stores the default image to display when the "reset image"
* button is pushed.
*/
private Bitmap mDefaultImage = null;
/**
* Default constructor that's needed by the GenericActivity
* framework.
*/
public ImageDownloadsPresenter() {
}
/**
* 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.
mView =
new WeakReference<>(view);
// Initialize the ImageStrategyMapper that efficiently maps
// button ids to strategies for downloading and displaying
// images concurrently.
mButtonToImageStrategyMapper =
new ButtonToImageStrategyMapper
(new int[] {
R.id.runnable_button,
R.id.messages_button,
R.id.async_task_button,
R.id.reset_image_button },
new ImageStrategy[] {
new DownloadWithRunnables(),
new DownloadWithMessages(),
new DownloadWithAsyncTask(),
new ResetBitmap()
});
// Extract the default image.
final InputStream is = (InputStream)
mView.get()
.getActivityContext()
.getResources()
.openRawResource(R.drawable.default_image);
// Decode an InputStream into a Bitmap that
// stores the default image.
mDefaultImage = BitmapFactory.decodeStream(is);
// Show the default image on the user's screen.
resetBitmap();
// Invoke the special onCreate() method in GenericPresenter,
// passing in the ImageDownloadsModel class to
// instantiate/manage and "this" to provide
// ImageDownloadsModel with this MVP.RequiredModelOps
// instance.
super.onCreate(ImageDownloadsModel.class,
this);
}
/**
* Hook method dispatched by the GenericActivity framework to
* initialize the ImageDownloadsPresenter object after it's been
* created.
*
* @param view
* The currently active MVP.RequiredViewOps.
*/
@Override
public void onConfigurationChange(MVP.RequiredViewOps view) {
Log.d(TAG,
"onConfigurationChange() called");
// Reset the WeakReference.
mView =
new WeakReference<>(view);
// Set the default image.
mView.get().displayBitmap(mCurrentImage,
null);
}
/**
* Hook method called to shutdown the Model layer.
*
* @param isChangeConfigurations
* True if a runtime configuration triggered the onDestroy() call.
*/
@Override
public void onDestroy(boolean isChangingConfigurations) {
// Destroy the model.
getModel().onDestroy(isChangingConfigurations);
}
/**
* Called when a user clicks a button to download an image.
*
* @param buttonResId
* Indicates the button pressed by the user.
* @param url
* URL givenby the user.
*/
public void handleButtonClick(int buttonResId,
String url) {
// Create a DownloadContext object associated with this user
// request, which plays the role of the "Context" in the
// Strategy pattern.
final DownloadContext downloadContext =
makeDownloadContext(url);
// Only one download/display is allowed at a time, so if an
// operation is already in progress then cancel it first.
if (mActiveImageStrategy != null)
mActiveImageStrategy.cancel(downloadContext);
// Get the ImageStrategy associated with the buttonResId.
mActiveImageStrategy =
mButtonToImageStrategyMapper.getImageStrategy(buttonResId);
// Invoke the ImageStrategy to download and display the
// image concurrently.
mActiveImageStrategy.downloadAndDisplay(downloadContext);
}
/**
* Factory method that returns the DownloadContext associated with
* this user request, which plays the role of the "Context" in the
* Strategy pattern.
*
* @param url
* URL to download.
*/
private DownloadContext makeDownloadContext(String url) {
// This command is called back after the image is displayed to
// indicate there's no active ImageStrategy.
final Runnable completionCommand =
new Runnable() {
public void run() {
// Indicate there's no active ImageStrategy.
mActiveImageStrategy = null;
}
};
// Create a DownloadContext that stores references to the
// MVP.ProvidedModelOps and completion hook objects in fields,
// which are used by the various concrete ButtonStrategies to
// download and display an image concurrently.
return new DownloadContext(url,
this,
getModel(),
completionCommand);
}
/**
* Set the current image.
*/
public void setCurrentImage(Bitmap image) {
mCurrentImage = image;
}
/**
* Reset bitmap display on the user's screen to the default image.
*/
public void resetBitmap() {
// Store the current image for subsequent use in case of a
// runtime configuration change.
setCurrentImage(mDefaultImage);
// Set the default image.
mView.get().displayBitmap(mDefaultImage, null);
}
/**
* Display a downloaded bitmap image if it's non-null; otherwise,
* it reports an error via a Toast that's displayed on the UI
* Thread. This method can be called from either the UI Thread or
* a background Thread.
*
* @param image
* The bitmap image
* @param completionCommand
* Command whose run() hook method is called after the
* image is displayed.
*/
public void displayBitmap(Bitmap image,
Runnable completionCommand) {
// Check to see if we've been interrupted.
if (Thread.interrupted())
return;
else {
// Store the current image for subsequent use in case of a
// runtime configuration change.
setCurrentImage(image);
// Display this image.
mView.get()
.displayBitmap(image,
completionCommand);
}
}
/**
* Return the Activity context.
*/
@Override
public Context getActivityContext() {
return mView.get().getActivityContext();
}
/**
* Return the Application context.
*/
@Override
public Context getApplicationContext() {
return mView.get().getApplicationContext();
}
}