package com.aviary.android.feather.effects;
import it.sephiroth.android.library.imagezoom.ImageViewTouch;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import org.json.JSONException;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.media.ThumbnailUtils;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewTreeObserver.OnScrollChangedListener;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.ScaleAnimation;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.ViewFlipper;
import android.widget.ViewSwitcher.ViewFactory;
import com.aviary.android.feather.Constants;
import com.aviary.android.feather.FilterManager.FeatherContext;
import com.aviary.android.feather.R;
import com.aviary.android.feather.async_tasks.AsyncImageManager;
import com.aviary.android.feather.async_tasks.AsyncImageManager.OnImageLoadListener;
import com.aviary.android.feather.graphics.PluginDividerDrawable;
import com.aviary.android.feather.graphics.RepeatableHorizontalDrawable;
import com.aviary.android.feather.library.content.FeatherIntent;
import com.aviary.android.feather.library.filters.BorderFilter;
import com.aviary.android.feather.library.filters.FilterLoaderFactory;
import com.aviary.android.feather.library.filters.FilterLoaderFactory.Filters;
import com.aviary.android.feather.library.filters.INativeFilter;
import com.aviary.android.feather.library.filters.NativeFilter;
import com.aviary.android.feather.library.graphics.drawable.FakeBitmapDrawable;
import com.aviary.android.feather.library.moa.Moa;
import com.aviary.android.feather.library.moa.MoaAction;
import com.aviary.android.feather.library.moa.MoaActionFactory;
import com.aviary.android.feather.library.moa.MoaActionList;
import com.aviary.android.feather.library.moa.MoaResult;
import com.aviary.android.feather.library.plugins.FeatherExternalPack;
import com.aviary.android.feather.library.plugins.FeatherInternalPack;
import com.aviary.android.feather.library.plugins.FeatherPack;
import com.aviary.android.feather.library.plugins.PluginManager;
import com.aviary.android.feather.library.plugins.PluginManager.ExternalPlugin;
import com.aviary.android.feather.library.plugins.PluginManager.IPlugin;
import com.aviary.android.feather.library.plugins.PluginManager.InternalPlugin;
import com.aviary.android.feather.library.plugins.UpdateType;
import com.aviary.android.feather.library.services.ConfigService;
import com.aviary.android.feather.library.services.EffectContext;
import com.aviary.android.feather.library.services.ImageCacheService;
import com.aviary.android.feather.library.services.ImageCacheService.SimpleCachedRemoteBitmap;
import com.aviary.android.feather.library.services.PluginService;
import com.aviary.android.feather.library.services.PluginService.OnUpdateListener;
import com.aviary.android.feather.library.services.PluginService.PluginError;
import com.aviary.android.feather.library.services.PreferenceService;
import com.aviary.android.feather.library.tracking.Tracker;
import com.aviary.android.feather.library.utils.ArrayUtils;
import com.aviary.android.feather.library.utils.BitmapUtils;
import com.aviary.android.feather.library.utils.ImageLoader;
import com.aviary.android.feather.library.utils.SystemUtils;
import com.aviary.android.feather.library.utils.UserTask;
import com.aviary.android.feather.utils.UIUtils;
import com.aviary.android.feather.widget.ArrayAdapterExtended;
import com.aviary.android.feather.widget.EffectThumbLayout;
import com.aviary.android.feather.widget.HorizontalVariableListView;
import com.aviary.android.feather.widget.HorizontalVariableListView.OnItemClickedListener;
import com.aviary.android.feather.widget.IapDialog;
import com.aviary.android.feather.widget.IapDialog.OnCloseListener;
import com.aviary.android.feather.widget.IapNotificationLayout;
import com.aviary.android.feather.widget.ImageSwitcher;
public class BordersPanel extends AbstractContentPanel implements ViewFactory, OnUpdateListener, OnImageLoadListener, OnScrollChangedListener, OnItemClickedListener, OnItemSelectedListener {
private final int mPluginType;
/** view flipper for switching between lists */
private ViewFlipper mViewFlipper;
/** thumbnail horizontal listview */
protected HorizontalVariableListView mHList;
/** Panel is rendering. */
protected volatile Boolean mIsRendering = false;
/** Panel is animating */
private volatile boolean mIsAnimating;
/** The current rendering task. */
private RenderTask mCurrentTask;
protected PluginService mPluginService;
protected ImageCacheService mCacheService;
private PreferenceService mPreferenceService;
/** The main image switcher. */
protected ImageSwitcher mImageSwitcher;
/** external plugins enabled */
private boolean mExternalPacksEnabled = true;
/** A reference to the effect applied */
protected MoaActionList mActions = null;
protected String mRenderedEffect;
/** create a reference to the update alert dialog. This to prevent multiple alert messages */
private AlertDialog mUpdateDialog;
/** default width of each effect thumbnail */
private int mFilterCellWidth = 80;
private int mThumbBitmapSize;
private List<String> mInstalledPackages;
/** thumbnail cache manager */
private AsyncImageManager mImageManager;
/** thumbnail for effects */
protected Bitmap mThumbBitmap;
/** current selected position */
protected int mSelectedPosition = -1;
/** first position allowed in selection */
private static int FIRST_POSITION = -1;
/** total number of available plugins */
private int mAvailablePacks = 0;
/** show the first get more button */
private boolean mShowFirstGetMore = true;
private boolean mEnableEffectAnimation = false;
protected Bitmap updateArrowBitmap;
/** hlist scrolled */
private boolean mScrollChanged;
// IAP notification variables
private IapNotificationLayout mIapNotificationPopup;
/** the iap notification view */
private boolean mIapPopupShown;
protected int mIapNotificationIconId;
private boolean mShowIapNotificationAndValue;
// thumbnail properties
private static int mRoundedBordersPixelSize = 16;
private static int mShadowRadiusPixelSize = 4;
private static int mShadowOffsetPixelSize = 2;
private static int mRoundedBordersPaddingPixelSize = 5;
private static int mRoundedBordersStrokePixelSize = 3;
private static int mItemsGapPixelsSize = 2;
private int mExternalThumbPadding = 0;
/** default title for featured divider */
private String mFeaturedDefaultTitle;
/** max number of featured elements to display */
private int mFeaturedCount;
// don't display the error dialog more than once
private static boolean mUpdateErrorHandled = false;
private boolean mFirstTimeRenderer;
/** options used to decode cached images */
private static BitmapFactory.Options mThumbnailOptions;
public BordersPanel( EffectContext context ) {
this( context, FeatherIntent.PluginType.TYPE_BORDER );
}
protected BordersPanel( EffectContext context, int type ) {
super( context );
mPluginType = type;
mIapNotificationIconId = R.drawable.feather_frames_popup_icon;
}
@SuppressWarnings("deprecation")
@Override
public void onCreate( Bitmap bitmap ) {
super.onCreate( bitmap );
mImageManager = new AsyncImageManager( 1 );
mThumbnailOptions = new Options();
mThumbnailOptions.inPreferredConfig = Config.RGB_565;
mPluginService = getContext().getService( PluginService.class );
mCacheService = getContext().getService( ImageCacheService.class );
mPreferenceService = getContext().getService( PreferenceService.class );
if( mPluginType == FeatherIntent.PluginType.TYPE_FILTER )
mExternalPacksEnabled = Constants.getValueFromIntent( Constants.EXTRA_EFFECTS_ENABLE_EXTERNAL_PACKS, true );
else
mExternalPacksEnabled = Constants.getValueFromIntent( Constants.EXTRA_FRAMES_ENABLE_EXTERNAL_PACKS, true );
mViewFlipper = (ViewFlipper) getOptionView().findViewById( R.id.flipper );
mHList = (HorizontalVariableListView) getOptionView().findViewById( R.id.list );
mHList.setOverScrollMode( View.OVER_SCROLL_ALWAYS );
mPreview = BitmapUtils.copy( mBitmap, Bitmap.Config.ARGB_8888 );
// ImageView Switcher setup
mImageSwitcher = (ImageSwitcher) getContentView().findViewById( R.id.switcher );
initContentImage( mImageSwitcher );
// Horizontal list setup
mHList.setOnScrollListener( this );
View content = getOptionView().findViewById( R.id.background );
content.setBackgroundDrawable( RepeatableHorizontalDrawable.createFromView( content ) );
try {
updateArrowBitmap = BitmapFactory.decodeResource( getContext().getBaseContext().getResources(), R.drawable.feather_update_arrow );
} catch ( Throwable t ) {}
mEnableEffectAnimation = Constants.ANDROID_SDK > android.os.Build.VERSION_CODES.GINGERBREAD && SystemUtils.getCpuMhz() > 800;
// mSelectedPosition = 0;
}
@Override
public void onActivate() {
super.onActivate();
ConfigService config = getContext().getService( ConfigService.class );
// new method, using the panel height dinamically
mFilterCellWidth = (int) ( ( getOptionView().findViewById( R.id.background ).getHeight() - getOptionView().findViewById( R.id.bottom_background_overlay ).getHeight() ) * 0.9 );
mThumbBitmapSize = (int) ( mFilterCellWidth * 0.85 );
// mFilterCellWidth = config.getDimensionPixelSize( R.dimen.feather_effects_cell_width );
// mFilterCellWidth = (int) ( ( Constants.SCREEN_WIDTH / UIUtils.getScreenOptimalColumnsPixels( mFilterCellWidth ) ) );
mThumbBitmap = generateThumbnail( mBitmap, mThumbBitmapSize, mThumbBitmapSize );
mInstalledPackages = Collections.synchronizedList( new ArrayList<String>() );
mRoundedBordersPixelSize = config.getDimensionPixelSize( R.dimen.feather_effects_panel_thumb_rounded_border );
mRoundedBordersPaddingPixelSize = config.getDimensionPixelSize( R.dimen.feather_effects_panel_thumb_padding );
mShadowOffsetPixelSize = config.getDimensionPixelSize( R.dimen.feather_effects_panel_thumb_shadow_offset );
mShadowRadiusPixelSize = config.getDimensionPixelSize( R.dimen.feather_effects_panel_thumb_shadow_radius );
mRoundedBordersStrokePixelSize = config.getDimensionPixelSize( R.dimen.feather_effects_panel_thumb_stroke_size );
mItemsGapPixelsSize = config.getDimensionPixelSize( R.dimen.feather_effects_panel_items_gap );
mExternalThumbPadding = config.getDimensionPixelSize( R.dimen.feather_effects_external_thumb_padding );
mFeaturedDefaultTitle = config.getString( R.string.feather_featured );
mFeaturedCount = config.getInteger( R.integer.feather_featured_count );
mHList.setGravity( Gravity.BOTTOM );
mHList.setOverScrollMode( View.OVER_SCROLL_ALWAYS );
mHList.setEdgeGravityY( Gravity.BOTTOM );
mHList.setOnItemSelectedListener( this );
mHList.setOnItemClickedListener( this );
mImageManager.setOnLoadCompleteListener( this );
getContentView().setVisibility( View.VISIBLE );
onPostActivate();
}
protected void initContentImage( ImageSwitcher imageView ) {
if ( null != imageView ) {
imageView.setFactory( this );
imageView.setImageBitmap( mBitmap, true, getContext().getCurrentImageViewMatrix(), Float.MAX_VALUE );
imageView.setAnimateFirstView( false );
}
}
protected final int getPluginType() {
return mPluginType;
}
protected void searchPlugin() {
getContext().searchPlugin( mPluginType );
}
protected void downloadPlugin( final String packagename ) {
getContext().downloadPlugin( packagename, mPluginType );
}
protected void searchOrDownloadPlugin( final String packagename, boolean search ) {
getContext().searchOrDownloadPlugin( packagename, mPluginType, search );
}
private void showUpdateAlert( final CharSequence packageName, final PluginError error, boolean fromUseClick ) {
if ( error != PluginError.NoError ) {
String errorString;
if ( fromUseClick )
errorString = getError( error );
else
errorString = getErrors( error );
if ( error == PluginError.PluginTooOldError ) {
OnClickListener yesListener = new OnClickListener() {
@Override
public void onClick( DialogInterface dialog, int which ) {
downloadPlugin( (String) packageName );
}
};
onGenericError( errorString, R.string.feather_update, yesListener, android.R.string.cancel, null );
} else if ( error == PluginError.PluginTooNewError ) {
OnClickListener yesListener = new OnClickListener() {
@Override
public void onClick( DialogInterface dialog, int which ) {
String pname = getContext().getBaseContext().getPackageName();
downloadPlugin( pname );
}
};
onGenericError( errorString, R.string.feather_update, yesListener, android.R.string.cancel, null );
} else {
onGenericError( errorString );
}
return;
}
}
/**
* Create a popup alert dialog when multiple plugins need to be updated
*
* @param error
*/
private void showUpdateAlertMultiplePlugins( final PluginError error, boolean fromUserClick ) {
if ( error != PluginError.NoError ) {
final String errorString = getErrors( error );
if ( error == PluginError.PluginTooOldError ) {
OnClickListener yesListener = new OnClickListener() {
@Override
public void onClick( DialogInterface dialog, int which ) {
searchPlugin();
}
};
onGenericError( errorString, R.string.feather_update, yesListener, android.R.string.cancel, null );
} else if ( error == PluginError.PluginTooNewError ) {
OnClickListener yesListener = new OnClickListener() {
@Override
public void onClick( DialogInterface dialog, int which ) {
String pname = getContext().getBaseContext().getPackageName();
downloadPlugin( pname );
}
};
onGenericError( errorString, R.string.feather_update, yesListener, android.R.string.cancel, null );
} else {
onGenericError( errorString );
}
}
}
/**
*
* @param set
*/
private void showUpdateAlertMultipleItems( final String pkgname, Set<PluginError> set ) {
if ( null != set ) {
final String errorString = getContext().getBaseContext().getResources().getString( R.string.feather_effects_error_update_multiple );
OnClickListener yesListener = new OnClickListener() {
@Override
public void onClick( DialogInterface dialog, int which ) {
searchOrDownloadPlugin( pkgname, true );
}
};
onGenericError( errorString, R.string.feather_update, yesListener, android.R.string.cancel, null );
}
}
protected String getError( PluginError error ) {
int resId = R.string.feather_effects_error_loading_pack;
switch ( error ) {
case UnknownError:
resId = R.string.feather_effects_unknown_error;
break;
case PluginTooOldError:
resId = R.string.feather_effects_error_update_pack;
break;
case PluginTooNewError:
resId = R.string.feather_effects_error_update_editor;
break;
case PluginNotLoadedError:
break;
case PluginLoadError:
break;
case MethodNotFoundError:
break;
default:
break;
}
return getContext().getBaseContext().getString( resId );
}
protected String getErrors( PluginError error ) {
int resId = R.string.feather_effects_error_loading_packs;
switch ( error ) {
case UnknownError:
resId = R.string.feather_effects_unknown_errors;
break;
case PluginTooOldError:
resId = R.string.feather_effects_error_update_packs;
break;
case PluginTooNewError:
resId = R.string.feather_effects_error_update_editors;
break;
case PluginNotLoadedError:
case PluginLoadError:
case MethodNotFoundError:
default:
break;
}
return getContext().getBaseContext().getString( resId );
}
protected void onPostActivate() {
// register for plugins updates
mPluginService.registerOnUpdateListener( this );
updateInstalledPacks( true );
contentReady();
}
@Override
public void onDestroy() {
mPluginService = null;
mCacheService = null;
mPreferenceService = null;
super.onDestroy();
}
@Override
public void onDeactivate() {
onProgressEnd();
mPluginService.removeOnUpdateListener( this );
mImageManager.setOnLoadCompleteListener( null );
mHList.setOnScrollListener( null );
mHList.setOnItemSelectedListener( null );
mHList.setOnItemClickedListener( null );
super.onDeactivate();
}
@Override
public void onConfigurationChanged( Configuration newConfig, Configuration oldConfig ) {
mImageManager.clearCache();
if ( mIapDialog != null ) {
ViewGroup parent = (ViewGroup) mIapDialog.getParent();
if( null != parent ) {
ExternalPlugin currentPlugin = mIapDialog.getPlugin();
int index = parent.indexOfChild( mIapDialog );
parent.removeView( mIapDialog );
mIapDialog = (IapDialog) UIUtils.getLayoutInflater().inflate( R.layout.feather_iap_dialog, parent, false );
mIapDialog.setLayoutAnimation( null );
parent.addView( mIapDialog, index );
updateIapDialog( currentPlugin );
setApplyEnabled( false );
}
}
updateInstalledPacks( false );
super.onConfigurationChanged( newConfig, oldConfig );
}
@Override
public void onScrollChanged() {
mHList.setOnScrollListener( null );
mScrollChanged = true;
mEnableEffectAnimation = false;
if ( mExternalPacksEnabled ) {
hideIapPopup( 0 );
}
}
@Override
protected void onDispose() {
mHList.setAdapter( null );
if ( null != mImageManager ) {
mImageManager.clearCache();
mImageManager.shutDownNow();
}
if ( mThumbBitmap != null && !mThumbBitmap.isRecycled() ) {
mThumbBitmap.recycle();
}
mThumbBitmap = null;
if ( null != updateArrowBitmap && !updateArrowBitmap.isRecycled() ) {
updateArrowBitmap.recycle();
}
updateArrowBitmap = null;
super.onDispose();
}
@Override
protected void onGenerateResult() {
mLogger.info( "onGenerateResult. isRendering: " + mIsRendering );
if ( mIsRendering ) {
GenerateResultTask task = new GenerateResultTask();
task.execute();
} else {
onComplete( mPreview, mActions );
}
}
@Override
protected void onComplete( Bitmap bitmap, MoaActionList actions ) {
super.onComplete( bitmap, actions );
if ( null != mRenderedEffect ) {
Tracker.recordTag( mRenderedEffect + ": Applied" );
}
}
@Override
public boolean onBackPressed() {
if ( backHandled() ) return true;
return super.onBackPressed();
}
@Override
public void onCancelled() {
killCurrentTask();
mIsRendering = false;
super.onCancelled();
}
@Override
public boolean getIsChanged() {
return super.getIsChanged() || mIsRendering == true;
}
@Override
public void onUpdate( Bundle delta ) {
if ( isActive() && mExternalPacksEnabled ) {
if ( mUpdateDialog != null && mUpdateDialog.isShowing() ) {
// another update alert is showing, skip new alerts
return;
}
if ( validDelta( delta ) ) {
DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
@Override
public void onClick( DialogInterface dialog, int which ) {
updateInstalledPacks( true );
}
};
mUpdateDialog = new AlertDialog.Builder( getContext().getBaseContext() ).setMessage( R.string.filter_pack_updated ).setNeutralButton( android.R.string.ok, listener ).setCancelable( false ).create();
mUpdateDialog.show();
}
}
}
@SuppressWarnings("deprecation")
@Override
public void onLoadComplete( final ImageView view, Bitmap bitmap, int tag ) {
if ( !isActive() ) return;
View parent = (View) view.getParent();
if ( null != bitmap ) {
view.setImageDrawable( new BitmapDrawable( bitmap ) );
} else {
view.setImageResource( R.drawable.feather_iap_dialog_image_na );
}
if ( parent != null && parent.findViewById( R.id.progress ) != null ) {
parent.findViewById( R.id.progress ).setVisibility( View.GONE );
}
if ( !mEnableEffectAnimation ) {
view.setVisibility( View.VISIBLE );
return;
}
if ( null != view && parent != null ) {
if ( mHList.getScrollX() == 0 ) {
if ( parent.getLeft() < mHList.getRight() ) {
ScaleAnimation anim = new ScaleAnimation( 0, 1, 0, 1, Animation.RELATIVE_TO_SELF, (float) 0.5, Animation.RELATIVE_TO_SELF, (float) 0.5 );
anim.setAnimationListener( new AnimationListener() {
@Override
public void onAnimationStart( Animation animation ) {
view.setVisibility( View.VISIBLE );
}
@Override
public void onAnimationRepeat( Animation animation ) {}
@Override
public void onAnimationEnd( Animation animation ) {}
} );
anim.setDuration( 100 );
anim.setStartOffset( mHList.getScreenPositionForView( view ) * 100 );
view.startAnimation( anim );
view.setVisibility( View.INVISIBLE );
return;
}
}
view.setVisibility( View.VISIBLE );
}
}
/**
* bundle contains a list of all updates applications. if one meets the criteria ( is a filter apk ) then return true
*
* @param bundle
* the bundle
* @return true if bundle contains a valid filter package
*/
private boolean validDelta( Bundle bundle ) {
if ( null != bundle ) {
if ( bundle.containsKey( "delta" ) ) {
try {
@SuppressWarnings("unchecked")
ArrayList<UpdateType> updates = (ArrayList<UpdateType>) bundle.getSerializable( "delta" );
if ( null != updates ) {
for ( UpdateType update : updates ) {
if ( FeatherIntent.PluginType.isTypeOf( update.getPluginType(), mPluginType ) ) {
return true;
}
if ( FeatherIntent.ACTION_PLUGIN_REMOVED.equals( update.getAction() ) ) {
// if it's removed check against current listed packs
if ( mInstalledPackages.contains( update.getPackageName() ) ) {
return true;
}
}
}
return false;
}
} catch ( ClassCastException e ) {
return true;
}
}
}
return true;
}
@Override
public View makeView() {
ImageViewTouch view = new ImageViewTouch( getContext().getBaseContext(), null );
view.setBackgroundColor( 0x00000000 );
view.setDoubleTapEnabled( false );
view.setLayoutParams( new ImageSwitcher.LayoutParams( LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT ) );
return view;
}
@Override
protected View generateContentView( LayoutInflater inflater ) {
return inflater.inflate( R.layout.feather_native_effects_content, null );
}
@Override
protected ViewGroup generateOptionView( LayoutInflater inflater, ViewGroup parent ) {
return (ViewGroup) inflater.inflate( R.layout.feather_effects_panel, parent, false );
}
@Override
public Matrix getContentDisplayMatrix() {
return ( (ImageViewTouch) mImageSwitcher.getCurrentView() ).getDisplayMatrix();
}
protected Bitmap generateThumbnail( Bitmap input, final int width, final int height ) {
return ThumbnailUtils.extractThumbnail( input, width, height );
}
/**
* Update the installed plugins
*/
protected void updateInstalledPacks( boolean firstTime ) {
mIsAnimating = true;
if ( mViewFlipper.getDisplayedChild() != 0 ) {
mViewFlipper.setDisplayedChild( 0 );
}
// now try to install every plugin...
if ( firstTime ) {
mHList.setAdapter( null );
}
new PluginInstallTask().execute( mPluginType );
}
/**
* Creates and returns the default adapter for the frames listview
* @param context
* @param result
* @return
*/
protected FramesListAdapter createListAdapter( Context context, List<EffectPack> result ) {
return new FramesListAdapter( context, R.layout.feather_effect_thumb, R.layout.feather_effect_external_thumb, R.layout.feather_stickers_pack_divider_empty,
R.layout.feather_getmore_stickers_thumb, R.layout.feather_getmore_stickers_thumb_inverted, result );
}
/**
* Called after the {@link PluginInstallTask} completed
*
* @param result
* - list of installed packs
* @param mErrors
* - list of errors
*/
private void onEffectListUpdated( List<EffectPack> result, List<EffectPackError> mErrors, int totalCount ) {
// we had errors during installation
if ( null != mErrors && mErrors.size() > 0 ) {
if ( !mUpdateErrorHandled ) {
handleErrors( mErrors );
}
}
FIRST_POSITION = ( mExternalPacksEnabled && mShowFirstGetMore ) ? 1 : 0;
FramesListAdapter adapter = createListAdapter( getContext().getBaseContext(), result );
mHList.setAdapter( adapter );
if ( mViewFlipper.getDisplayedChild() != 1 ) {
mViewFlipper.setDisplayedChild( 1 );
}
if ( mSelectedPosition != FIRST_POSITION ) {
mHList.setSelectedPosition( FIRST_POSITION, false );
if ( mFirstTimeRenderer ) {
onItemSelected( mHList, null, FIRST_POSITION, -1 );
}
mFirstTimeRenderer = true;
}
showIapPopup();
// show the alert only the first time!!
if ( totalCount < 1 && mExternalPacksEnabled && mPluginType == FeatherIntent.PluginType.TYPE_BORDER ) {
if ( !mPreferenceService.containsValue( this.getClass().getSimpleName() + "-install-first-time" ) ) {
OnClickListener listener = new OnClickListener() {
@Override
public void onClick( DialogInterface dialog, int which ) {
getContext().downloadPlugin( PluginService.FREE_BORDERS_PACKAGENAME, FeatherIntent.PluginType.TYPE_BORDER );
dialog.dismiss();
}
};
AlertDialog dialog = new AlertDialog.Builder( getContext().getBaseContext() ).setMessage( R.string.feather_borders_dialog_first_time ).setPositiveButton( android.R.string.ok, listener )
.setNegativeButton( android.R.string.cancel, null ).create();
mPreferenceService.putBoolean( this.getClass().getSimpleName() + "-install-first-time", true );
dialog.show();
}
}
}
/**
* Based on various condition show the IAP notification popup
*/
private void showIapPopup() {
mLogger.info( "showIapPopup" );
// available packs must be > 0
// external packs must be enabled
// popup never shown before
if ( !mShowIapNotificationAndValue ) return;
if ( mIapPopupShown ) return;
mIapPopupShown = true;
if ( mScrollChanged ) return;
if ( !isActive() || getContext() == null || getHandler() == null ) return;
if ( mIapNotificationPopup == null ) {
if ( null != getContext().getBaseContext() ) {
ViewGroup container = ( (FeatherContext) getContext().getBaseContext() ).activatePopupContainer();
UIUtils.getLayoutInflater().inflate( R.layout.feather_iap_notification_popup, container, true );
mIapNotificationPopup = (IapNotificationLayout) container.findViewById( R.id.iap_popup );
}
}
if ( mIapNotificationPopup == null ) return;
Rect r = new Rect( 0, 0, 0, 200 );
Point offset = new Point();
try {
getOptionView().findViewById( R.id.background ).getGlobalVisibleRect( r, offset );
} catch ( Throwable t ) {}
mIapNotificationPopup.setPadding( 0, 0, 0, r.height() );
mIapNotificationPopup.setIcon( mIapNotificationIconId );
mIapNotificationPopup.setText( String.valueOf( mAvailablePacks ) );
// then finally post a delayed execution of the animation
mIapNotificationPopup.show();
}
/**
* Hide the IAP notification popup
*
* @param view
* @param delayMillis
*/
private void hideIapPopup( final long delayMillis ) {
mLogger.info( "hideIapPopup: " + delayMillis );
if ( !isActive() || getHandler() == null ) return;
if ( mIapNotificationPopup == null || mIapNotificationPopup.getParent() == null ) return;
if ( mIapNotificationPopup.getVisibility() == View.GONE ) return;
mIapNotificationPopup.hide( delayMillis );
}
// ///////////////
// IAP - Dialog //
// ///////////////
protected IapDialog mIapDialog;
protected final IapDialog createIapDialog( final ExternalPlugin plugin ) {
ViewGroup container = ( (FeatherContext) getContext().getBaseContext() ).activatePopupContainer();
IapDialog dialog = (IapDialog) container.findViewById( R.id.main_iap_dialog );
if ( dialog == null ) {
UIUtils.getLayoutInflater().inflate( R.layout.feather_iap_dialog, container, true );
dialog = (IapDialog) container.findViewById( R.id.main_iap_dialog );
dialog.setFocusable( true );
dialog.setPlugin( plugin, mPluginType, getContext().getBaseContext() );
dialog.setOnCloseListener( new OnCloseListener() {
@Override
public void onClose() {
removeIapDialog();
}
} );
}
setApplyEnabled( false );
return dialog;
}
private void updateIapDialog( ExternalPlugin plugin ) {
if ( null != mIapDialog && null != plugin ) {
mIapDialog.setPlugin( plugin, mPluginType, getContext().getBaseContext() );
}
}
private boolean removeIapDialog() {
setApplyEnabled( true );
if ( null != mIapDialog ) {
mIapDialog.setOnCloseListener( null );
mIapDialog.hide();
mIapDialog = null;
return true;
}
return false;
}
private void handleErrors( List<EffectPackError> mErrors ) {
if ( mErrors == null || mErrors.size() < 1 ) return;
// first get the total number of errors
HashMap<PluginError, String> hash = new HashMap<PluginError, String>();
for ( EffectPackError item : mErrors ) {
PluginError error = item.mError;
hash.put( error, (String) item.mPackageName );
}
// now manage the different cases
// 1. just one type of error
if ( hash.size() == 1 ) {
// get the first error
EffectPackError item = mErrors.get( 0 );
if ( mErrors.size() == 1 ) {
showUpdateAlert( item.mPackageName, item.mError, false );
} else {
showUpdateAlertMultiplePlugins( item.mError, false );
}
} else {
// 2. here we must handle different errors type
showUpdateAlertMultipleItems( getContext().getBaseContext().getPackageName(), hash.keySet() );
}
mUpdateErrorHandled = true;
}
private void renderEffect( EffectPack item, int position ) {
String label = (String) item.getItemAt( position );
mLogger.log( "renderEffect: " + label );
killCurrentTask();
mCurrentTask = createRenderTask( position );
mCurrentTask.execute( item );
if ( null != item ) {
Tracker.recordTag( item.getItemAt( position ) + ": Selected" );
}
}
protected RenderTask createRenderTask( int position ) {
return new RenderTask( position );
}
boolean killCurrentTask() {
if ( mCurrentTask != null ) {
onProgressEnd();
return mCurrentTask.cancel( true );
}
return false;
}
protected INativeFilter loadNativeFilter( final EffectPack pack, int position, final CharSequence label, boolean hires ) {
BorderFilter filter = (BorderFilter) FilterLoaderFactory.get( Filters.BORDERS );
filter.setBorderName( label );
filter.setHiRes( hires );
IPlugin plugin = pack.mPluginRef;
if ( null != plugin ) {
if ( plugin instanceof InternalPlugin ) {
filter.setSourceApp( ( (InternalPlugin) plugin ).getSourceDir( mPluginType ) );
// border size
int[] sizes = ( (InternalPlugin) plugin ).listBordersWidths();
position -= pack.getIndex();
if( null != sizes && sizes.length > ( position - 1 ) && position > 0 ) {
int borderSize = sizes[position - 1];
filter.setSize( (double) borderSize / 100.0 );
}
}
}
return filter;
}
protected INativeFilter loadNativeFilterForThumbnail( final EffectPack pack, int position, final CharSequence label ) {
return new NativeFilter( "undefined" );
}
boolean backHandled() {
if ( mIsAnimating ) return true;
if ( null != mIapDialog ) {
removeIapDialog();
return true;
}
killCurrentTask();
return false;
}
static class ViewHolder {
TextView text;
ImageView image;
}
class FramesListAdapter extends ArrayAdapterExtended<EffectPack> {
private int mLayoutResId;
private int mExternalLayoutResId;
private int mAltLayoutResId;
private int mAltLayout2ResId;
private int mDividerLayoutResId;
private int mCount = -1;
private List<EffectPack> mData;
private LayoutInflater mLayoutInflater;
private int mDefaultHeight;
protected BitmapDrawable mExternalFolderIcon;
static final int TYPE_GET_MORE_FIRST = 0;
static final int TYPE_GET_MORE_LAST = 1;
static final int TYPE_NORMAL = 2;
static final int TYPE_EXTERNAL = 3;
static final int TYPE_DIVIDER = 4;
public FramesListAdapter( Context context, int mainResId, int externalResId, int dividerResId, int altResId, int altResId2, List<EffectPack> objects ) {
super( context, mainResId, objects );
mLayoutResId = mainResId;
mExternalLayoutResId = externalResId;
mAltLayoutResId = altResId;
mAltLayout2ResId = altResId2;
mDividerLayoutResId = dividerResId;
mData = objects;
mLayoutInflater = UIUtils.getLayoutInflater();
mExternalFolderIcon = getExternalBackgroundDrawable( context );
mDefaultHeight = getOptionView().findViewById( R.id.background ).getHeight() - getOptionView().findViewById( R.id.bottom_background_overlay ).getHeight();
}
protected BitmapDrawable getExternalBackgroundDrawable( Context context ) {
return (BitmapDrawable) context.getResources().getDrawable( R.drawable.feather_frames_pack_background );
}
protected void finalize() throws Throwable {
Log.i( "effects-adapter", "finalize" );
};
@Override
public int getCount() {
if ( mCount == -1 ) {
int total = 0; // first get more
for ( EffectPack pack : mData ) {
if ( null == pack ) {
total++;
continue;
}
pack.setIndex( total );
total += pack.size;
}
// return total;
mCount = total;
}
return mCount;
}
@Override
public void notifyDataSetChanged() {
mCount = -1;
super.notifyDataSetChanged();
}
@Override
public void notifyDataSetAdded() {
mCount = -1;
super.notifyDataSetAdded();
}
@Override
public void notifyDataSetRemoved() {
mCount = -1;
super.notifyDataSetRemoved();
}
@Override
public void notifyDataSetInvalidated() {
mCount = -1;
super.notifyDataSetInvalidated();
}
@Override
public int getViewTypeCount() {
return 5;
}
@Override
public int getItemViewType( int position ) {
if ( !mExternalPacksEnabled ) return TYPE_NORMAL;
EffectPack item = getItem( position );
if ( null == item ) {
if ( position == 0 )
return TYPE_GET_MORE_FIRST;
else
return TYPE_GET_MORE_LAST;
}
if ( item.isDivider ) return TYPE_DIVIDER;
if ( item.isExternal ) return TYPE_EXTERNAL;
return TYPE_NORMAL;
}
@Override
public EffectPack getItem( int position ) {
for ( int i = 0; i < mData.size(); i++ ) {
EffectPack pack = mData.get( i );
if ( null == pack ) continue;
if ( position >= pack.index && position < pack.index + pack.size ) {
return pack;
}
}
return null;
}
@SuppressWarnings("deprecation")
@Override
public View getView( final int position, final View convertView, final ViewGroup parent ) {
View view;
ViewHolder holder = null;
int type = getItemViewType( position );
final EffectPack item = getItem( position );
int layoutWidth;
int layoutHeight = LayoutParams.MATCH_PARENT;
if ( convertView == null ) {
holder = new ViewHolder();
if ( type == TYPE_GET_MORE_FIRST ) {
view = mLayoutInflater.inflate( mAltLayoutResId, parent, false );
layoutHeight = mDefaultHeight;
layoutWidth = mFilterCellWidth;
} else if ( type == TYPE_GET_MORE_LAST ) {
view = mLayoutInflater.inflate( mAltLayout2ResId, parent, false );
layoutHeight = mDefaultHeight;
layoutWidth = mFilterCellWidth;
// hide the last "get more" button if there's no need
View lastChild = parent.getChildAt( parent.getChildCount() - 1 );
if ( null != lastChild ) {
if ( lastChild.getRight() < parent.getRight() ) {
layoutWidth = 0;
}
}
} else if ( type == TYPE_NORMAL ) {
view = mLayoutInflater.inflate( mLayoutResId, parent, false );
holder.text = (TextView) view.findViewById( R.id.text );
holder.image = (ImageView) view.findViewById( R.id.image );
holder.image.setImageDrawable( new BitmapDrawable( mThumbBitmap ) );
view.setTag( holder );
LayoutParams params = holder.image.getLayoutParams();
params.width = params.height = mThumbBitmapSize;
holder.image.setLayoutParams( params );
holder.image.requestLayout();
layoutHeight = LayoutParams.WRAP_CONTENT;
layoutWidth = mThumbBitmapSize + mItemsGapPixelsSize;
} else if ( type == TYPE_EXTERNAL ) {
view = mLayoutInflater.inflate( mExternalLayoutResId, parent, false );
holder.text = (TextView) view.findViewById( R.id.text );
holder.image = (ImageView) view.findViewById( R.id.image );
view.setTag( holder );
LayoutParams params = holder.image.getLayoutParams();
params.width = mThumbBitmapSize + (mExternalThumbPadding * 2);
params.height = mThumbBitmapSize + mExternalThumbPadding;
holder.image.setImageDrawable( mExternalFolderIcon );
holder.image.setLayoutParams( params );
holder.image.requestLayout();
layoutHeight = LayoutParams.WRAP_CONTENT;
layoutWidth = mThumbBitmapSize + mItemsGapPixelsSize + ( mExternalThumbPadding * 2 );
} else {
// TYPE_DIVIDER
view = mLayoutInflater.inflate( mDividerLayoutResId, parent, false );
holder.image = (ImageView) view.findViewById( R.id.image );
view.setTag( holder );
layoutWidth = EffectThumbLayout.LayoutParams.WRAP_CONTENT;
layoutHeight = mDefaultHeight;
}
view.setLayoutParams( new EffectThumbLayout.LayoutParams( layoutWidth, layoutHeight ) );
} else {
view = convertView;
holder = (ViewHolder) view.getTag();
}
Callable<Bitmap> executor;
if ( type == TYPE_NORMAL ) {
holder.text.setText( item.getLabelAt( position ) );
final String effectName = (String) item.getItemAt( position );
executor = createContentCallable( item, position, effectName );
mImageManager.execute( executor, item.index + "/" + effectName, holder.image );
} else if ( type == TYPE_EXTERNAL ) {
holder.text.setText( item.mTitle );
ExternalPlugin plugin = (ExternalPlugin) item.mPluginRef;
if ( null != plugin ) {
executor = createExternalContentCallable( plugin.getIconUrl() );
mImageManager.execute( executor, plugin.getIconUrl(), holder.image, 4000 );
}
} else if ( type == TYPE_DIVIDER ) {
Drawable drawable = holder.image.getDrawable();
if ( drawable instanceof PluginDividerDrawable ) {
( (PluginDividerDrawable) drawable ).setTitle( item.mTitle.toString() );
} else {
PluginDividerDrawable d = new PluginDividerDrawable( drawable, item.mTitle.toString() );
holder.image.setImageDrawable( d );
}
} else {
// get more
if ( mShowIapNotificationAndValue ) {
TextView totalText = (TextView) view.findViewById( R.id.text01 );
totalText.setText( String.valueOf( mAvailablePacks ) );
}
}
return view;
}
protected Callable<Bitmap> createContentCallable( final EffectPack item, int position, final String effectName ) {
return new BorderThumbnailCallable( mCacheService, (InternalPlugin) item.mPluginRef, effectName, mFilterCellWidth );
}
protected Callable<Bitmap> createExternalContentCallable( final String iconUrl ) {
return new ExternalFramesThumbnailCallable( iconUrl, mCacheService, mExternalFolderIcon, this.getContext().getResources(), R.drawable.feather_iap_dialog_image_na );
}
}
static void actionsForRoundedThumbnail( final boolean isValid, INativeFilter filter ) {
MoaAction action = MoaActionFactory.action( "ext-roundedborders" );
action.setValue( "padding", mRoundedBordersPaddingPixelSize );
action.setValue( "roundPx", mRoundedBordersPixelSize );
action.setValue( "strokeColor", 0xffa6a6a6 );
action.setValue( "strokeWeight", mRoundedBordersStrokePixelSize );
if ( !isValid ) {
action.setValue( "overlaycolor", 0x99000000 );
}
filter.getActions().add( action );
// shadow
action = MoaActionFactory.action( "ext-roundedshadow" );
action.setValue( "color", 0x99000000 );
action.setValue( "radius", mShadowRadiusPixelSize );
action.setValue( "roundPx", mRoundedBordersPixelSize );
action.setValue( "offsetx", mShadowOffsetPixelSize );
action.setValue( "offsety", mShadowOffsetPixelSize );
action.setValue( "padding", mRoundedBordersPaddingPixelSize );
filter.getActions().add( action );
}
// ////////////////////////
// OnItemClickedListener //
// ////////////////////////
@Override
public boolean onItemClick( AdapterView<?> parent, View view, int position, long id ) {
mLogger.info( "onItemClick: " + position );
if ( isActive() ) {
if ( mHList.getAdapter() == null ) return false;
int viewType = mHList.getAdapter().getItemViewType( position );
if ( viewType == FramesListAdapter.TYPE_NORMAL ) {
EffectPack item = (EffectPack) mHList.getAdapter().getItem( position );
if ( item != null && item.mStatus == PluginError.NoError ) {
return true;
} else {
showUpdateAlert( item.mPackageName, item.mStatus, true );
return false;
}
} else if ( viewType == FramesListAdapter.TYPE_GET_MORE_FIRST || viewType == FramesListAdapter.TYPE_GET_MORE_LAST ) {
if ( position == 0 ) {
Tracker.recordTag( "LeftGetMoreEffects : Selected" );
} else {
Tracker.recordTag( "RightGetMoreEffects : Selected" );
}
searchPlugin();
return false;
} else if ( viewType == FramesListAdapter.TYPE_EXTERNAL ) {
EffectPack item = (EffectPack) mHList.getAdapter().getItem( position );
if ( null != item ) {
if ( android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.FROYO && Constants.getApplicationMaxMemory() >= 32 ) {
ExternalPlugin externalPlugin = (ExternalPlugin) item.mPluginRef;
if ( externalPlugin == null ) return false;
if ( null == mIapDialog ) {
mIapDialog = createIapDialog( externalPlugin );
} else {
updateIapDialog( externalPlugin );
}
} else {
downloadPlugin( item.mPackageName.toString() );
}
}
}
}
return false;
}
// /////////////////////////
// OnItemSelectedListener //
// /////////////////////////
@Override
public void onItemSelected( AdapterView<?> parent, View view, int position, long id ) {
mLogger.info( "onItemSelected: " + position );
mSelectedPosition = position;
if ( isActive() ) {
if ( mHList.getAdapter() == null ) return;
int viewType = mHList.getAdapter().getItemViewType( position );
if ( viewType == FramesListAdapter.TYPE_NORMAL ) {
EffectPack item = (EffectPack) mHList.getAdapter().getItem( position );
if ( item == null ) return;
if ( item.mStatus == PluginError.NoError ) {
// so we assume the view is already selected and so let's selected the "original" effect by default
if ( !item.isExternal ) {
renderEffect( item, position );
}
}
}
}
}
@Override
public void onNothingSelected( AdapterView<?> parent ) {
mLogger.info( "onNothingSelected" );
if ( parent.getAdapter() != null ) {
EffectPack item = ( (FramesListAdapter) parent.getAdapter() ).getItem( FIRST_POSITION );
if ( null != item ) {
renderEffect( item, FIRST_POSITION );
}
if ( null != getHandler() ) {
getHandler().postDelayed( new Runnable() {
@Override
public void run() {
mHList.setSelectedPosition( FIRST_POSITION, false );
}
}, 200 );
}
}
}
static class ExternalFramesThumbnailCallable implements Callable<Bitmap> {
String mUri;
int mFallbackResId;
BitmapDrawable mFolder;
SoftReference<ImageCacheService> cacheServiceRef;
SoftReference<Resources> resourcesRef;
public ExternalFramesThumbnailCallable( final String uri, ImageCacheService cacheService, final BitmapDrawable folderBackground, Resources resources, final int fallbackResId ) {
mUri = uri;
mFallbackResId = fallbackResId;
cacheServiceRef = new SoftReference<ImageCacheService>( cacheService );
resourcesRef = new SoftReference<Resources>( resources );
mFolder = folderBackground;
}
@Override
public Bitmap call() throws Exception {
if( mUri == null || mUri.length() < 1 ){
return mFolder.getBitmap();
}
Bitmap bitmap = null;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Config.ARGB_8888;
ImageCacheService cache = cacheServiceRef.get();
if( null == cache ){
return mFolder.getBitmap();
}
SimpleCachedRemoteBitmap request;
try {
request = cache.requestRemoteBitmap( PluginService.CONTENT_DEFAULT_URL + "/" + mUri );
bitmap = request.getBitmap( options );
} catch( Exception e ){
e.printStackTrace();
}
// fallback icon
if ( null == bitmap ) {
if ( null != resourcesRef.get() ) {
try {
bitmap = BitmapFactory.decodeResource( resourcesRef.get(), mFallbackResId );
} catch ( Throwable t ) {}
}
}
if ( null != bitmap ) {
Bitmap result = generateBitmap( bitmap );
if( result != bitmap ) {
bitmap.recycle();
bitmap = null;
bitmap = result;
}
return bitmap;
} else {
return mFolder.getBitmap();
}
}
Bitmap generateBitmap( Bitmap icon ) {
return icon;
}
}
/**
* Downloads and renders the sticker thumbnail
*
* @author alessandro
*
*/
static class BorderThumbnailCallable implements Callable<Bitmap> {
InternalPlugin mPlugin;
int mFinalSize;
String mUrl;
SoftReference<ImageCacheService> cacheRef;
public BorderThumbnailCallable( ImageCacheService cacheService, final InternalPlugin plugin, final String srcUrl, final int size ) {
mPlugin = plugin;
mFinalSize = size;
mUrl = srcUrl;
cacheRef = new SoftReference<ImageCacheService>( cacheService );
}
@Override
public Bitmap call() throws Exception {
ImageCacheService cache = cacheRef.get();
Bitmap bitmap;
if ( null != cache ) {
bitmap = cache.getBitmap( mPlugin.getPluginType() + "-" + mUrl, mThumbnailOptions );
if ( null != bitmap ) return bitmap;
}
try {
bitmap = ImageLoader.getPluginItemBitmap( mPlugin, mUrl, FeatherIntent.PluginType.TYPE_BORDER, null, mFinalSize, mFinalSize );
} catch ( Exception e ) {
return null;
}
if ( null != bitmap ) {
NativeFilter filter = new NativeFilter( "undefined" );
actionsForRoundedThumbnail( true, filter );
Bitmap result = filter.execute( bitmap, null, 1, 1 );
bitmap.recycle();
bitmap = result;
if ( null != bitmap && null != cache ) {
cache.putBitmap( mPlugin.getPluginType() + "-" + mUrl, bitmap );
}
return bitmap;
}
return null;
}
}
protected CharSequence[] getOptionalEffectsNames() {
return new CharSequence[] { "original" };
}
protected CharSequence[] getOptionalEffectsLabels() {
return new CharSequence[] { "Original" };
}
/**
* Install all the
*
* @author alessandro
*
*/
class PluginInstallTask extends AsyncTask<Integer, Void, List<EffectPack>> {
List<EffectPackError> mErrors;
private PluginService mEffectsService;
private int mInstalledCount = 0;
@Override
protected void onPreExecute() {
super.onPreExecute();
mEffectsService = getContext().getService( PluginService.class );
mErrors = Collections.synchronizedList( new ArrayList<EffectPackError>() );
mImageManager.clearCache();
}
@Override
protected List<EffectPack> doInBackground( Integer... params ) {
// List of installed plugins available on the device
final int pluginType = params[0];
long sharedUpdateTime = 0, lastUpdateTime = 0;
FeatherInternalPack installedPacks[];
FeatherPack availablePacks[];
if ( mExternalPacksEnabled ) {
while ( !mPluginService.isUpdated() ) {
try {
Thread.sleep( 50 );
} catch ( InterruptedException e ) {
e.printStackTrace();
}
mLogger.log( "waiting for plugin service..." );
}
installedPacks = mPluginService.getInstalled( getContext().getBaseContext(), pluginType );
availablePacks = mPluginService.getAvailable( pluginType );
} else {
if( pluginType == FeatherIntent.PluginType.TYPE_FILTER )
installedPacks = new FeatherInternalPack[] { FeatherInternalPack.getDefault( getContext().getBaseContext() ) };
else
installedPacks = new FeatherInternalPack[] {};
availablePacks = new FeatherExternalPack[] {};
}
mInstalledCount = installedPacks.length;
if ( null != mPreferenceService && mExternalPacksEnabled ) sharedUpdateTime = mPreferenceService.getLong( this.getClass().getName() + "-plugins-update-date", 0 );
if ( null != mPluginService ) lastUpdateTime = mPluginService.getLastUpdateTime();
// List of the available plugins online
mAvailablePacks = availablePacks.length;
List<EffectPack> result = Collections.synchronizedList( new ArrayList<EffectPack>() );
mInstalledPackages.clear();
if ( mExternalPacksEnabled ) {
if( mPluginType == FeatherIntent.PluginType.TYPE_BORDER ) {
mShowFirstGetMore = !( (installedPacks.length == 0 && availablePacks.length == 1) || (installedPacks.length == 1 && availablePacks.length == 0) );
}
if( mInstalledCount == 0 ) {
mEnableEffectAnimation = false;
}
if ( mShowFirstGetMore ) result.add( null );
}
int index = 0;
for ( FeatherPack pack : installedPacks ) {
if ( pack instanceof FeatherInternalPack ) {
InternalPlugin plugin = (InternalPlugin) PluginManager.create( getContext().getBaseContext(), pack );
final CharSequence packagename = plugin.getPackageName();
final CharSequence label = plugin.getLabel( pluginType );
PluginError status;
if ( plugin.isExternal() ) {
status = installPlugin( packagename.toString(), plugin.getPluginType() );
if ( status != PluginError.NoError ) {
EffectPackError error = new EffectPackError( packagename, label, status );
mErrors.add( error );
}
} else {
status = PluginError.NoError;
}
CharSequence[] filters = listPackItems( plugin );
CharSequence[] labels = listPackLabels( plugin, filters );
CharSequence[] filters2 = null, labels2 = null;
if ( index == 0 ) {
try {
CharSequence[] f = getOptionalEffectsNames();
CharSequence[] n = getOptionalEffectsLabels();
if ( null != f && null != n && f.length == n.length ) {
filters2 = ArrayUtils.concat( f, filters );
labels2 = ArrayUtils.concat( n, labels );
}
} catch ( IllegalAccessException e ) {
e.printStackTrace();
}
if ( null != filters2 && null != labels2 ) {
filters = filters2;
labels = labels2;
}
}
final EffectPack effectPack = new EffectPack( packagename, label, filters, labels, status, plugin, false );
mInstalledPackages.add( packagename.toString() );
if ( isActive() ) {
result.add( effectPack );
if ( ( index + 1 ) < installedPacks.length ) {
FeatherInternalPack nextPack = installedPacks[index + 1];
InternalPlugin nextPlugin = (InternalPlugin) PluginManager.create( getContext().getBaseContext(), nextPack );
if ( null != nextPlugin ) {
result.add( new EffectPack( nextPlugin.getLabel( mPluginType ).toString() ) );
}
}
}
index++;
}
}
if ( mExternalPacksEnabled ) {
if ( availablePacks.length > 0 && installedPacks.length > 0 ) {
result.add( new EffectPack( mFeaturedDefaultTitle ) );
}
index = 0;
for ( FeatherPack pack : availablePacks ) {
if ( index >= mFeaturedCount ) break;
ExternalPlugin plugin = (ExternalPlugin) PluginManager.create( getContext().getBaseContext(), pack );
final CharSequence packagename = plugin.getPackageName();
final CharSequence label = plugin.getLabel( pluginType );
final EffectPack effectPack = new EffectPack( packagename, label, null, null, PluginError.NoError, plugin, true );
if ( isActive() ) {
result.add( effectPack );
}
index++;
}
}
if ( mInstalledPackages != null && mInstalledPackages.size() > 0 ) {
if ( mExternalPacksEnabled && mShowFirstGetMore ) {
result.add( null );
}
}
if ( mExternalPacksEnabled ) {
if ( sharedUpdateTime != lastUpdateTime ) {
mLogger.log( "lastUpdateTime: " + lastUpdateTime + " != sharedUpdateTime: " + sharedUpdateTime );
if ( mPreferenceService != null ) mPreferenceService.putLong( this.getClass().getName() + "-plugins-update-date", lastUpdateTime );
mShowIapNotificationAndValue = mAvailablePacks > 0;
} else {
mShowIapNotificationAndValue = false;
}
}
return result;
}
@Override
protected void onPostExecute( List<EffectPack> result ) {
super.onPostExecute( result );
onEffectListUpdated( result, mErrors, mInstalledCount );
mIsAnimating = false;
}
/**
* Returns the list of available items in the passed plugin pack
*
* @param plugin
*/
protected CharSequence[] listPackItems( InternalPlugin plugin ) {
return plugin.listPackItems( mPluginType );
}
/**
* For every item in the passed plugin return its label
*
* @param plugin
* @param items
* @return
*/
protected CharSequence[] listPackLabels( InternalPlugin plugin, CharSequence[] items ) {
CharSequence[] labels = new String[items.length];
for ( int i = 0; i < items.length; i++ ) {
labels[i] = plugin.getResourceLabel( mPluginType, items[i] );
}
return labels;
}
private PluginError installPlugin( final String packagename, final int pluginType ) {
if ( mEffectsService.installed( packagename ) ) {
return PluginError.NoError;
}
return mEffectsService.install( getContext().getBaseContext(), packagename, pluginType );
}
}
class EffectPackError {
CharSequence mPackageName;
CharSequence mLabel;
PluginError mError;
public EffectPackError( CharSequence packagename, CharSequence label, PluginError error ) {
mPackageName = packagename;
mLabel = label;
mError = error;
}
}
static class EffectPack {
CharSequence mPackageName;
CharSequence[] mValues;
CharSequence[] mLabels;
CharSequence mTitle;
PluginError mStatus;
IPlugin mPluginRef;
int size = 0;
int index = 0;
boolean isExternal;
boolean isDivider;
public EffectPack( final String label ) {
isDivider = true;
size = 1;
mStatus = PluginError.NoError;
mTitle = label;
}
public EffectPack( CharSequence packageName, CharSequence pakageTitle, CharSequence[] filters, CharSequence[] labels, PluginError status, IPlugin plugin, boolean external ) {
mPackageName = packageName;
mValues = filters;
mLabels = labels;
mStatus = status;
mTitle = pakageTitle;
mPluginRef = plugin;
isExternal = external;
isDivider = false;
if ( null != filters ) {
size = filters.length;
} else {
size = 1;
}
}
public int getCount() {
return size;
}
public int getIndex() {
return index;
}
public void setIndex( int value ) {
index = value;
}
public CharSequence getItemAt( int position ) {
return mValues[position - index];
}
public CharSequence getLabelAt( int position ) {
return mLabels[position - index];
}
@Override
protected void finalize() throws Throwable {
mPluginRef = null;
super.finalize();
}
}
/**
* Render the selected effect
*/
protected class RenderTask extends UserTask<EffectPack, Bitmap, Bitmap> implements OnCancelListener {
int mPosition;
String mError;
MoaResult mMoaMainExecutor;
MoaResult mMoaPreviewExecutor;
/**
* Instantiates a new render task.
*
* @param tag
*/
public RenderTask( final int position ) {
mPosition = position;
}
@Override
public void onPreExecute() {
super.onPreExecute();
onProgressStart();
}
private INativeFilter initFilter( EffectPack pack, int position, String label ) {
final INativeFilter filter = loadNativeFilter( pack, position, label, true );
mActions = (MoaActionList) filter.getActions().clone();
if ( filter instanceof BorderFilter ) ( (BorderFilter) filter ).setHiRes( false );
try {
mMoaMainExecutor = filter.prepare( mBitmap, mPreview, 1, 1 );
} catch ( JSONException e ) {
e.printStackTrace();
mMoaMainExecutor = null;
return null;
}
return filter;
}
protected MoaResult initPreview( INativeFilter filter ) {
return null;
}
/**
* Process the preview bitmap while executing in background the full image
*/
public void doSmallPreviewInBackground() {
// rendering the small preview
if ( mMoaPreviewExecutor != null ) {
mMoaPreviewExecutor.execute();
if ( mMoaPreviewExecutor.active > 0 ) {
publishProgress( mMoaPreviewExecutor.outputBitmap );
}
}
}
public void doFullPreviewInBackground( final String effectName ) {
// rendering the full preview
mMoaMainExecutor.execute();
}
@Override
public Bitmap doInBackground( final EffectPack... params ) {
if ( isCancelled() ) return null;
final EffectPack pack = params[0];
final String mEffect = (String) pack.getItemAt( mPosition );
mRenderedEffect = mEffect;
INativeFilter filter = initFilter( pack, mPosition, mEffect );
if ( null != filter ) {
mMoaPreviewExecutor = initPreview( filter );
} else {
return null;
}
mIsRendering = true;
// render small preview if required
doSmallPreviewInBackground();
if ( isCancelled() ) return null;
// rendering the full preview
try {
doFullPreviewInBackground( mEffect );
} catch ( Exception exception ) {
mError = exception.getMessage();
exception.printStackTrace();
return null;
}
mLogger.log( " complete. isCancelled? " + isCancelled(), mEffect );
if ( !isCancelled() ) {
return mMoaMainExecutor.outputBitmap;
} else {
return null;
}
}
@Override
public void onProgressUpdate( Bitmap... values ) {
super.onProgressUpdate( values );
// we're using a FakeBitmapDrawable just to upscale the small bitmap
// to be rendered the same way as the full image
final Bitmap preview = values[0];
if ( null != preview ) {
final FakeBitmapDrawable drawable = new FakeBitmapDrawable( preview, mBitmap.getWidth(), mBitmap.getHeight() );
mImageSwitcher.setImageDrawable( drawable, true, null, Float.MAX_VALUE );
}
}
@Override
public void onPostExecute( final Bitmap result ) {
super.onPostExecute( result );
if ( !isActive() ) return;
mPreview = result;
if ( result == null || mMoaMainExecutor == null || mMoaMainExecutor.active == 0 ) {
onRestoreOriginalBitmap();
if ( mError != null ) {
onGenericError( mError );
}
setIsChanged( false );
mActions = null;
} else {
onApplyNewBitmap( result );
setIsChanged( true );
}
onProgressEnd();
mIsRendering = false;
mCurrentTask = null;
}
protected void onApplyNewBitmap( final Bitmap result ) {
if ( SystemUtils.isHoneyComb() ) {
Moa.notifyPixelsChanged( result );
}
mImageSwitcher.setImageBitmap( result, true, null, Float.MAX_VALUE );
}
protected void onRestoreOriginalBitmap() {
// restore the original bitmap...
mImageSwitcher.setImageBitmap( mBitmap, false, null, Float.MAX_VALUE );
}
@Override
public void onCancelled() {
super.onCancelled();
if ( mMoaMainExecutor != null ) {
mMoaMainExecutor.cancel();
}
if ( mMoaPreviewExecutor != null ) {
mMoaPreviewExecutor.cancel();
}
mIsRendering = false;
}
@Override
public void onCancel( DialogInterface dialog ) {
cancel( true );
}
}
/**
* Used to generate the Bitmap result. If user clicks on the "Apply" button when an effect is still rendering, then starts this
* task.
*/
class GenerateResultTask extends AsyncTask<Void, Void, Void> {
ProgressDialog mProgress = new ProgressDialog( getContext().getBaseContext() );
@Override
protected void onPreExecute() {
super.onPreExecute();
mProgress.setTitle( getContext().getBaseContext().getString( R.string.feather_loading_title ) );
mProgress.setMessage( getContext().getBaseContext().getString( R.string.effect_loading_message ) );
mProgress.setIndeterminate( true );
mProgress.setCancelable( false );
mProgress.show();
}
@Override
protected Void doInBackground( Void... params ) {
mLogger.info( "GenerateResultTask::doInBackground", mIsRendering );
while ( mIsRendering ) {
mLogger.log( "waiting...." );
}
return null;
}
@Override
protected void onPostExecute( Void result ) {
super.onPostExecute( result );
mLogger.info( "GenerateResultTask::onPostExecute" );
if ( getContext().getBaseActivity().isFinishing() ) return;
if ( mProgress.isShowing() ) mProgress.dismiss();
onComplete( mPreview, mActions );
}
}
}