package com.aviary.android.feather.effects;
import java.util.HashMap;
import android.annotation.SuppressLint;
import android.content.DialogInterface.OnClickListener;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.os.Handler;
import android.os.Message;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.aviary.android.feather.library.filters.IFilter;
import com.aviary.android.feather.library.log.LoggerFactory;
import com.aviary.android.feather.library.log.LoggerFactory.Logger;
import com.aviary.android.feather.library.log.LoggerFactory.LoggerType;
import com.aviary.android.feather.library.moa.MoaActionList;
import com.aviary.android.feather.library.services.EffectContext;
// TODO: Auto-generated Javadoc
/**
* Base class for all the feather tools.
*
* @author alessandro
*/
@SuppressLint("HandlerLeak")
public abstract class AbstractEffectPanel {
static final int PREVIEW_BITMAP_CHANGED = 1;
static final int PREVIEW_FILTER_CHANGED = 2;
static final int FILTER_SAVE_COMPLETED = 3;
static final int PROGRESS_START = 4;
static final int PROGRESS_END = 5;
static final int PROGRESS_MODAL_START = 6;
static final int PROGRESS_MODAL_END = 7;
static final int SET_TOOLBAR_TITLE = 8;
static final int RESTORE_TOOLBAR_TITLE = 9;
static final int HIDE_TOOLBAR_APPLY_BUTTON = 10;
static final int SHOW_TOOLBAR_APPLY_BUTTON = 11;
/**
* If the current panel implements {@link #AbstractEffectPanel.ContentPanel} this listener is used by the FilterManager to hide the main
* application image when the content panel send the onReady event.
*
* @author alessandro
*
*/
public static interface OnContentReadyListener {
/**
* On ready. Panel is ready to display its contents
*
* @param panel
* the panel
*/
void onReady( AbstractEffectPanel panel );
};
/**
* The listener interface for receiving onProgress events. The class that is interested in processing a onProgress event
* implements this interface, and the object created with that class is registered with a component using the component's
* <code>addOnProgressListener<code> method. When
* the onProgress event occurs, that object's appropriate
* method is invoked.
*
* @see OnProgressEvent
*/
public static interface OnProgressListener {
/**
* On progress start.
*/
void onProgressStart();
/**
* On progress end.
*/
void onProgressEnd();
/** a progress modal has been requested */
void onProgressModalStart();
/** hide the progress modal */
void onProgressModalEnd();
}
/**
* The listener interface for receiving onPreview events. The class that is interested in processing a onPreview event implements
* this interface, and the object created with that class is registered with a component using the component's
* <code>addOnPreviewListener<code> method. When
* the onPreview event occurs, that object's appropriate
* method is invoked.
*
* @see OnPreviewEvent
*/
public static interface OnPreviewListener {
/**
* Some parameters have changed and the effect has generated a new bitmap with the new parameters applied on it.
*
* @param result
* the result
*/
void onPreviewChange( Bitmap result );
/**
* On preview change.
*
* @param colorFilter
* the color filter
*/
void onPreviewChange( ColorFilter colorFilter );
};
/**
* The listener interface for receiving onApplyResult events. The class that is interested in processing a onApplyResult event
* implements this interface, and the object created with that class is registered with a component using the component's
* <code>addOnApplyResultListener<code> method. When
* the onApplyResult event occurs, that object's appropriate
* method is invoked.
*
* @see OnApplyResultEvent
*/
public static interface OnApplyResultListener {
/**
* On complete.
*
* @param result
* the result
* @param actions
* the actions executed
* @param trackingAttributes
* the tracking attributes
*/
void onComplete( Bitmap result, MoaActionList actions, HashMap<String, String> trackingAttributes );
}
/**
* The listener interface for receiving onError events. The class that is interested in processing a onError event implements
* this interface, and the object created with that class is registered with a component using the component's
* <code>addOnErrorListener<code> method. When
* the onError event occurs, that object's appropriate
* method is invoked.
*
* @see OnErrorEvent
*/
public static interface OnErrorListener {
/**
* On error.
*
* @param error
* the error
*/
void onError( String error );
void onError( String error, int yesLabel, OnClickListener yesListener, int noLabel, OnClickListener noListener );
}
/**
* Base interface for all the tools.
*
* @author alessandro
*/
public static interface OptionPanel {
/**
* Returns a view used to populate the option panel.
*
* @param inflater
* the inflater
* @param viewGroup
* the view group
* @return the option view
*/
View getOptionView( LayoutInflater inflater, ViewGroup viewGroup );
}
/**
* Base interface for the tools which will provide a content panel.
*
* @author alessandro
*
*/
public static interface ContentPanel {
/**
* Sets the on ready listener.
*
* @param listener
* the new on ready listener
*/
void setOnReadyListener( OnContentReadyListener listener );
/**
* Creates and return a new view which will be placed over the original image and its used by the contentpanel to draw its own
* preview.
*
* @param inflater
* the inflater
* @return the content view
*/
View getContentView( LayoutInflater inflater );
/**
* Return the current content view.
*
* @return the content view
*/
View getContentView();
/**
* Returns the current Image display matrix used in the content panel. This is useful when the application leaves the current
* tool and the original image needs to be updated using the content panel image. We need to know the content's panel image
* matrix in order to present the same image size/position to the user.
*
* @return the content display matrix
*/
Matrix getContentDisplayMatrix();
}
/** If a tool need to store a copy of the input bitmap, use this member which will be automatically recycled. */
protected Bitmap mPreview;
/**
* This is the input Bitmap passed from the FilterManager class.
*/
protected Bitmap mBitmap;
private boolean mActive;
private boolean mCreated;
protected boolean mChanged;
protected boolean mSaving;
protected long mRenderTime;
protected boolean mEnabled;
protected IFilter mFilter;
protected HashMap<String, String> mTrackingAttributes;
protected OnProgressListener mProgressListener;
protected OnPreviewListener mListener;
protected OnApplyResultListener mApplyListener;
protected OnErrorListener mErrorListener;
private EffectContext mFilterContext;
protected Logger mLogger;
/** The main listener handler. */
final Handler mListenerHandler = new Handler() {
@Override
public void handleMessage( Message msg ) {
super.handleMessage( msg );
switch ( msg.what ) {
case PREVIEW_FILTER_CHANGED:
if ( mListener != null && isActive() ) {
mListener.onPreviewChange( (ColorFilter) msg.obj );
}
break;
case PREVIEW_BITMAP_CHANGED:
if ( mListener != null && isActive() ) {
mListener.onPreviewChange( (Bitmap) msg.obj );
}
break;
case PROGRESS_START:
if ( mProgressListener != null && isCreated() ) {
mProgressListener.onProgressStart();
}
break;
case PROGRESS_END:
if ( mProgressListener != null && isCreated() ) {
mProgressListener.onProgressEnd();
}
break;
case PROGRESS_MODAL_START:
if ( mProgressListener != null && isCreated() ) {
mProgressListener.onProgressModalStart();
}
break;
case PROGRESS_MODAL_END:
if ( mProgressListener != null && isCreated() ) {
mProgressListener.onProgressModalEnd();
}
break;
case SET_TOOLBAR_TITLE:
if( isActive() )
getContext().setToolbarTitle( (CharSequence) msg.obj );
break;
case RESTORE_TOOLBAR_TITLE:
if( isActive() )
getContext().restoreToolbarTitle();
break;
case HIDE_TOOLBAR_APPLY_BUTTON:
if( isActive() ) {
getContext().setPanelApplyStatusEnabled( false );
}
break;
case SHOW_TOOLBAR_APPLY_BUTTON:
if( isActive() ) {
getContext().setPanelApplyStatusEnabled( true );
}
break;
default:
break;
}
}
};
/**
* Instantiates a new abstract effect panel.
*
* @param context
* the context
*/
public AbstractEffectPanel( EffectContext context ) {
mFilterContext = context;
mActive = false;
mEnabled = true;
mTrackingAttributes = new HashMap<String, String>();
setIsChanged( false );
mLogger = LoggerFactory.getLogger( this.getClass().getSimpleName(), LoggerType.ConsoleLoggerType );
}
public Handler getHandler() {
return mListenerHandler;
}
/**
* Change the toolbar title
* @param text
*/
protected void setToolbarTitle( final CharSequence text ) {
mListenerHandler.obtainMessage( SET_TOOLBAR_TITLE, text ).sendToTarget();
}
/**
* Restore the toolbar title to its default value
*/
protected void restoreToolbarTitle() {
mListenerHandler.sendEmptyMessage( RESTORE_TOOLBAR_TITLE );
}
/**
* Enabled/Disable the "apply" button in the current panel
* @param value
*/
protected void setApplyEnabled( boolean value ) {
mListenerHandler.sendEmptyMessage( value ? SHOW_TOOLBAR_APPLY_BUTTON : HIDE_TOOLBAR_APPLY_BUTTON );
}
/**
* On progress start.
*/
protected void onProgressStart() {
if ( isActive() ) {
mListenerHandler.sendEmptyMessage( PROGRESS_START );
}
}
/**
* On progress end.
*/
protected void onProgressEnd() {
if ( isActive() ) {
mListenerHandler.sendEmptyMessage( PROGRESS_END );
}
}
protected void onProgressModalStart() {
if ( isActive() ) {
mListenerHandler.sendEmptyMessage( PROGRESS_MODAL_START );
}
}
protected void onProgressModalEnd() {
if ( isActive() ) {
mListenerHandler.sendEmptyMessage( PROGRESS_MODAL_END );
}
}
/**
* Sets the panel enabled state.
*
* @param value
* the new enabled
*/
public void setEnabled( boolean value ) {
mEnabled = value;
}
/**
* Checks if is enabled.
*
* @return true, if is enabled
*/
public boolean isEnabled() {
return mEnabled;
}
/**
* Return true if current panel state is between the onActivate/onDeactivate states.
*
* @return true, if is active
*/
public boolean isActive() {
return mActive && isCreated();
}
/**
* Return true if current panel state is between onCreate/onDestroy states.
*
* @return true, if is created
*/
public boolean isCreated() {
return mCreated;
}
/**
* Sets the on preview listener.
*
* @param listener
* the new on preview listener
*/
public void setOnPreviewListener( OnPreviewListener listener ) {
mListener = listener;
}
/**
* Sets the on apply result listener.
*
* @param listener
* the new on apply result listener
*/
public void setOnApplyResultListener( OnApplyResultListener listener ) {
mApplyListener = listener;
}
/**
* Sets the on error listener.
*
* @param listener
* the new on error listener
*/
public void setOnErrorListener( OnErrorListener listener ) {
mErrorListener = listener;
}
/**
* Sets the on progress listener.
*
* @param listener
* the new on progress listener
*/
public void setOnProgressListener( OnProgressListener listener ) {
mProgressListener = listener;
}
/**
* Called first when the panel has been created and it is ready to be shown.
*
* @param bitmap
* the bitmap
*/
public void onCreate( Bitmap bitmap ) {
mLogger.info( "onCreate" );
mBitmap = bitmap;
mCreated = true;
}
/**
* panel is being shown.
*/
public void onOpening() {
mLogger.info( "onOpening" );
}
/**
* panel is being closed.
*/
public void onClosing() {
mLogger.info( "onClosing" );
}
/**
* Return true if you want the back event handled by the current panel otherwise return false and the back button will be handled
* by the system.
*
* @return true, if successful
*/
public boolean onBackPressed() {
return false;
}
/**
* Device configuration changed.
*
* @param newConfig
* the new config
*/
public void onConfigurationChanged( Configuration newConfig, Configuration oldConfig ) {
}
/**
* The main context requests to apply the current status of the filter.
*/
public void onSave() {
mLogger.info( "onSave" );
if ( mSaving == false ) {
mSaving = true;
mRenderTime = System.currentTimeMillis();
onGenerateResult();
}
}
/**
* Manager is asking to cancel the current tool. Return false if no further user interaction is necessary and you agree to close
* this panel. Return true otherwise. If you want to manage this event you
* can then cancel the panel by calling {@link EffectContext#cancel()} on the current context
*
* onCancel -> onCancelled -> onDeactivate -> onDestroy
*
* @return true, if successful
*/
public boolean onCancel() {
mLogger.info( "onCancel" );
return false;
}
/*
* Panel is being closed without applying the result.
* Either because the user clicked on the cancel button or because a back
* event has been fired.
*/
public void onCancelled() {
mLogger.info( "onCancelled" );
setEnabled( false );
}
/**
* Check if the current panel has pending changes.
*
* @return the checks if is changed
*/
public boolean getIsChanged() {
return mChanged;
}
/**
* Sets the 'changed' status of the current panel.
*
* @param value
* the new checks if is changed
*/
protected void setIsChanged( boolean value ) {
mChanged = value;
}
/**
* Panel is now hidden and it need to be disposed.
*/
public void onDestroy() {
mLogger.info( "onDestroy" );
mCreated = false;
onDispose();
}
/**
* Called after onCreate as soon as the panel it's ready to receive user interactions
*
* panel lifecycle: 1. onCreate 2. onActivate 3. ( user interactions.. ) 3.1 onCancel/onBackPressed 4. onSave|onCancelled 5.
* onDeactivate 6. onDestroy
*/
public void onActivate() {
mLogger.info( "onActivate" );
mActive = true;
}
/**
* Called just before start hiding the panel No user interactions should be accepted anymore after this point.
*/
public void onDeactivate() {
mLogger.info( "onDeactivate" );
setEnabled( false );
mActive = false;
}
/**
* Return the current Effect Context.
*
* @return the context
*/
public EffectContext getContext() {
return mFilterContext;
}
/**
* On dispose.
*/
protected void onDispose() {
mLogger.info( "onDispose" );
internalDispose();
}
/**
* Internal dispose.
*/
private void internalDispose() {
recyclePreview();
mPreview = null;
mBitmap = null;
mListener = null;
mErrorListener = null;
mApplyListener = null;
mFilterContext = null;
mFilter = null;
}
/**
* Recycle and free the preview bitmap.
*/
protected void recyclePreview() {
if ( mPreview != null && !mPreview.isRecycled() && !mPreview.equals( mBitmap ) ) {
mLogger.warning( "[recycle] preview Bitmap: " + mPreview );
mPreview.recycle();
}
}
/**
* On preview changed.
*
* @param bitmap
* the bitmap
*/
protected void onPreviewChanged( Bitmap bitmap ) {
onPreviewChanged( bitmap, true );
}
/**
* On preview changed.
*
* @param colorFilter
* the color filter
* @param notify
* the notify
*/
protected void onPreviewChanged( ColorFilter colorFilter, boolean notify ) {
setIsChanged( colorFilter != null );
if ( notify && isActive() ) {
mListenerHandler.removeMessages( PREVIEW_FILTER_CHANGED );
Message msg = mListenerHandler.obtainMessage( PREVIEW_FILTER_CHANGED );
msg.obj = colorFilter;
mListenerHandler.sendMessage( msg );
}
// if ( mListener != null && notify && isActive() ) mListener.onPreviewChange( colorFilter );
}
/**
* On preview changed.
*
* @param bitmap
* the bitmap
* @param notify
* the notify
*/
protected void onPreviewChanged( Bitmap bitmap, boolean notify ) {
setIsChanged( bitmap != null );
if ( bitmap == null || !bitmap.equals( mPreview ) ) {
recyclePreview();
}
mPreview = bitmap;
if ( notify && isActive() ) {
Message msg = mListenerHandler.obtainMessage( PREVIEW_BITMAP_CHANGED );
msg.obj = bitmap;
mListenerHandler.sendMessage( msg );
}
// if ( mListener != null && notify && isActive() ) mListener.onPreviewChange( bitmap );
}
/**
* Called when the current effect panel has completed the generation of the final bitmap.
*
* @param bitmap
* the bitmap
* @param actions
* list of the current applied actions
*/
protected void onComplete( Bitmap bitmap, MoaActionList actions ) {
mLogger.info( "onComplete" );
long t = System.currentTimeMillis();
if ( mApplyListener != null && isActive() ) {
if ( !mTrackingAttributes.containsKey( "renderTime" ) )
mTrackingAttributes.put( "renderTime", Long.toString( t - mRenderTime ) );
mApplyListener.onComplete( bitmap, actions, mTrackingAttributes );
}
mPreview = null;
mSaving = false;
}
/**
* On generic error.
*
* @param error
* the error
*/
protected void onGenericError( String error ) {
if ( mErrorListener != null && isActive() ) mErrorListener.onError( error );
}
protected void onGenericError( int resId ) {
if ( mErrorListener != null && isActive() ) {
String label = getContext().getBaseContext().getString( resId );
mErrorListener.onError( label );
}
}
protected void onGenericError( int resId, int yesLabel, OnClickListener yesListener, int noLabel, OnClickListener noListener ) {
if ( mErrorListener != null && isActive() ) {
String message = getContext().getBaseContext().getString( resId );
onGenericError( message, yesLabel, yesListener, noLabel, noListener );
}
}
protected void onGenericError( String message, int yesLabel, OnClickListener yesListener, int noLabel, OnClickListener noListener ) {
if ( mErrorListener != null && isActive() ) {
mErrorListener.onError( message, yesLabel, yesListener, noLabel, noListener );
}
}
/**
* On generic error.
*
* @param e
* the e
*/
protected void onGenericError( Exception e ) {
onGenericError( e.getMessage() );
}
/**
* This methods is called by the {@link #onSave()} method. Here the implementation of the current option panel should generate
* the result bitmap, even asyncronously, and when completed it must call the {@link #onComplete(Bitmap)} event.
*/
protected void onGenerateResult() {
onGenerateResult( null );
}
protected void onGenerateResult( MoaActionList actions ) {
onComplete( mPreview, actions );
}
}