/* * AVIARY API TERMS OF USE * Full Legal Agreement * The following terms and conditions and the terms and conditions at http://www.aviary.com/terms (collectively, the “Terms”) govern your use of any and all data, text, software, tools, documents and other materials associated with the application programming interface offered by Aviary, Inc. (the "API"). By clicking on the “Accept” button, OR BY USING OR ACCESSING ANY PORTION OF THE API, you or the entity or company that you represent are unconditionally agreeing to be bound by the terms, including those available by hyperlink from within this document, and are becoming a party to the Terms. Your continued use of the API shall also constitute assent to the Terms. If you do not unconditionally agree to all of the Terms, DO NOT USE OR ACCESS ANY PORTION OF THE API. If the terms set out herein are considered an offer, acceptance is expressly limited to these terms. IF YOU DO NOT AGREE TO THE TERMS, YOU MAY NOT USE THE API, IN WHOLE OR IN PART. * * Human-Friendly Summary * If you use the API, you automatically agree to these Terms of Service. Don't use our API if you don't agree with this document! * 1. GRANT OF LICENSE. * Subject to your ("Licensee") full compliance with all of Terms of this agreement ("Agreement"), Aviary, Inc. ("Aviary") grants Licensee a non-exclusive, revocable, nonsublicensable, nontransferable license to download and use the API solely to embed a launchable Aviary application within Licensee’s mobile or website application (“App”) and to access data from Aviary in connection with such application. Licensee may not install or use the API for any other purpose without Aviary's prior written consent. * * Licensee shall not use the API in connection with or to promote any products, services, or materials that constitute, promote or are used primarily for the purpose of dealing in: spyware, adware, or other malicious programs or code, counterfeit goods, items subject to U.S. embargo, unsolicited mass distribution of email ("spam"), multi-level marketing proposals, hate materials, hacking/surveillance/interception/descrambling equipment, libelous, defamatory, obscene, pornographic, abusive or otherwise offensive content, prostitution, body parts and bodily fluids, stolen products and items used for theft, fireworks, explosives, and hazardous materials, government IDs, police items, gambling, professional services regulated by state licensing regimes, non-transferable items such as airline tickets or event tickets, weapons and accessories. * * Use Aviary the way it's intended (as a photo-enhancement service), or get our permission first. * If your service does anything illegal or potentially offensive, we don't want Aviary to be in your app. Nothing personal - it just reflects badly on our brand. * 2. BRANDING. * Licensee agrees to the following: (a) on every App page that makes use of the Aviary API, Licensee shall display an Aviary logo crediting Aviary only in accordance with the branding instructions available at [www.aviary.com/branding]; (b) it shall maintain a clickable link to the following location [www.aviary.com/api-info], prominently in the licensee App whenever the API is displayed to the end user. (c) it may not otherwise use the Aviary logo without specific written permission from Aviary; and (d) any use of the Aviary logo on an App page shall be less prominent than the logo or mark that primarily describes the Licensee website, and Licensee’s use of the Aviary logo shall not imply any endorsement of the Licensee website by Aviary. * * (a) Don't remove or obscure the Aviary logo in the editor. * (b) Don't remove or obscure the link to Aviary's mobile info. We link the Aviary logo to this info so you don't need to add anything extra to your app. * (c) We're probably cool with you using our logo as part of a press release announcing our editor or otherwise promoting your use of Aviary. Just please ask us first. :) * (d) Please make sure that your users aren't confused as to who made your app by always keeping your logo more prominent than ours. You did most of the hard work for your app and should get all of the credit. :) * 3. PROPRIETARY RIGHTS. * As between Aviary and Licensee, the API and all intellectual property rights in and to the API are and shall at all times remain the sole and exclusive property of Aviary and are protected by applicable intellectual property laws and treaties. Except for the limited license expressly granted herein, no other license is granted, no other use is permitted and Aviary (and its licensors) shall retain all right, title and interest in and to the API and the Aviary logos. * * Aviary owns all of the rights in the API it is allowing you to use. Our allowance of you to use it, does not mean we are transferring ownership to you. * 4. OTHER RESTRICTIONS. * Except as expressly and unambiguously authorized under this Agreement, Licensee may not (i) copy, rent, lease, sell, transfer, assign, sublicense, disassemble, reverse engineer or decompile (except to the limited extent expressly authorized by applicable statutory law), modify or alter any part of the API; (ii) otherwise use the API on behalf of any third party. * * (i) Please don't break our API down and redistribute it without our consent. * (ii) Please don't agree to use our API if you are not the party using it. If you are a developer building the API into a third party app, please have your client review these terms, as they have to agree to them before they can use the API. * 5. MODIFICATIONS TO THIS AGREEMENT. * Aviary reserves the right, in its sole discretion to modify this Agreement at any time by posting a notice to Aviary.com. You shall be responsible for reviewing and becoming familiar with any such modification. Such modifications are effective upon first posting or notification and use of the Aviary API by Licensee following any such notification constitutes Licensee’s acceptance of the terms and conditions of this Agreement as modified. * * We may update this agreement from time to time, as needed. We don't anticipate any major changes, just tweaks to the legalese to reflect any new feature updates or material changes to how the API is offered. While we will make a good faith effort to notify everyone when these terms update with posts on our blog, etc... it's your responsibility to keep up-to-date with these terms on a regular basis. We'll post the last-update date at the bottom of the agreement to make it easier to know if the terms have changed. * 6. WARRANTY DISCLAIMER. * THE API IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, AVIARY AND ITS VENDORS EACH DISCLAIM ALL WARRANTIES, WHETHER EXPRESS, IMPLIED OR STATUTORY, REGARDING THE API, INCLUDING WITHOUT LIMITATION ANY AND ALL IMPLIED WARRANTIES OF MERCHANTABILITY, ACCURACY, RESULTS OF USE, RELIABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE, INTERFERENCE WITH QUIET ENJOYMENT, AND NON-INFRINGEMENT OF THIRD-PARTY RIGHTS. FURTHER, AVIARY DISCLAIMS ANY WARRANTY THAT LICENSEE'S USE OF THE API WILL BE UNINTERRUPTED OR ERROR FREE. * * Things might break. Hopefully not and if so, we'll do our best to fix it immediately. But if it happens, please note that we aren't responsible. You are using the API "as is" and understand the risk inherent in that. * 7. SUPPORT AND UPGRADES. * This Agreement does not entitle Licensee to any support and/or upgrades for the APIs, unless Licensee makes separate arrangements with Aviary and pays all fees associated with such support. Any such support and/or upgrades provided by Aviary shall be subject to the terms of this Agreement as modified by the associated support Agreement. * * We can't promise to offer any kind of support or future upgrades. We plan to help all of our partners to the best of our ability, but use of our API doesn't entitle you to this. * 8. LIABILITY LIMITATION. * REGARDLESS OF WHETHER ANY REMEDY SET FORTH HEREIN FAILS OF ITS ESSENTIAL PURPOSE OR OTHERWISE, AND EXCEPT FOR BODILY INJURY, IN NO EVENT WILL AVIARY OR ITS VENDORS, BE LIABLE TO LICENSEE OR TO ANY THIRD PARTY UNDER ANY TORT, CONTRACT, NEGLIGENCE, STRICT LIABILITY OR OTHER LEGAL OR EQUITABLE THEORY FOR ANY LOST PROFITS, LOST OR CORRUPTED DATA, COMPUTER FAILURE OR MALFUNCTION, INTERRUPTION OF BUSINESS, OR OTHER SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES OF ANY KIND ARISING OUT OF THE USE OR INABILITY TO USE THE API, EVEN IF AVIARY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH LOSS OR DAMAGES AND WHETHER OR NOT SUCH LOSS OR DAMAGES ARE FORESEEABLE. ANY CLAIM ARISING OUT OF OR RELATING TO THIS AGREEMENT MUST BE BROUGHT WITHIN ONE (1) YEAR AFTER THE OCCURRENCE OF THE EVENT GIVING RISE TO SUCH CLAIM. IN ADDITION, AVIARY DISCLAIMS ALL LIABILITY OF ANY KIND OF AVIARY'S VENDORS. * * Want to sue us anyway? That's cool (really not), but you only have a year to do it. Better act quick, Perry Mason. * 9. INDEMNITY. * Licensee agrees that Aviary shall have no liability whatsoever for any use Licensee makes of the API. Licensee shall indemnify and hold harmless Aviary from any and all claims, damages, liabilities, costs and fees (including reasonable attorneys' fees) arising from Licensee's use of the API. * * You acknowledge that we aren't responsible at all, for anything that happens resulting from your use of our API. * 10. TERM AND TERMINATION. * This Agreement shall continue until terminated as set forth in this Section. Either party may terminate this Agreement at any time, for any reason, or for no reason including, but not limited to, if Licensee violates any provision of this Agreement. Any termination of this Agreement shall also terminate the license granted hereunder. Upon termination of this Agreement for any reason, Licensee shall destroy and remove from all computers, hard drives, networks, and other storage media all copies of the API, and shall so certify to Aviary that such actions have occurred. Aviary shall have the right to inspect and audit Licensee's facilities to confirm the foregoing. Sections 3 through 11 and all accrued rights to payment shall survive termination of this Agreement. * * We can revoke your license (and you can choose to end your license) at any time, for any reason. We won't take this lightly, but we do hold onto this right should it be needed. * If either party terminates this license, you will need to remove the Aviary API from your code entirely. We'll have the right to double-check to make sure you have. * Terminating the agreement ends your ability to use our App. It doesn't change some of the other sections (namely 3-11). * 11. GOVERNMENT USE. * If Licensee is part of an agency, department, or other entity of the United States Government ("Government"), the use, duplication, reproduction, release, modification, disclosure or transfer of the API are restricted in accordance with the Federal Acquisition Regulations as applied to civilian agencies and the Defense Federal Acquisition Regulation Supplement as applied to military agencies. The API are a "commercial item," "commercial computer software" and "commercial computer software documentation." In accordance with such provisions, any use of the API by the Government shall be governed solely by the terms of this Agreement. * * Work for the government? Here is some special legalese just for you. * 12. EXPORT CONTROLS. * Licensee shall comply with all export laws and restrictions and regulations of the Department of Commerce, the United States Department of Treasury Office of Foreign Assets Control ("OFAC"), or other United States or foreign agency or authority, and Licensee shall not export, or allow the export or re-export of the API in violation of any such restrictions, laws or regulations. By downloading or using the API, Licensee agrees to the foregoing and represents and warrants that Licensee is not located in, under the control of, or a national or resident of any restricted country. * * To any potential partner located in a country with whom it is illegal for the USA to do business: We're genuinely sorry our governments are being jerks to each other and look forward to the day when it isn't illegal for us to do business together. * 13. MISCELLANEOUS. * Unless the parties have entered into a written amendment to this agreement that is signed by both parties regarding the Aviary API, this Agreement constitutes the entire agreement between Licensee and Aviary pertaining to the subject matter hereof, and supersedes any and all written or oral agreements with respect to such subject matter. This Agreement, and any disputes arising from or relating to the interpretation thereof, shall be governed by and construed under New York law as such law applies to agreements between New York residents entered into and to be performed within New York by two residents thereof and without reference to its conflict of laws principles or the United Nations Conventions for the International Sale of Goods. Except to the extent otherwise determined by Aviary, any action or proceeding arising from or relating to this Agreement must be brought in a federal court in the Southern District of New York or in state court in New York County, New York, and each party irrevocably submits to the jurisdiction and venue of any such court in any such action or proceeding. The prevailing party in any action arising out of this Agreement shall be entitled to an award of its costs and attorneys' fees. This Agreement may be amended only by a writing executed by Aviary. If any provision of this Agreement is held to be unenforceable for any reason, such provision shall be reformed only to the extent necessary to make it enforceable. The failure of Aviary to act with respect to a breach of this Agreement by Licensee or others does not constitute a waiver and shall not limit Aviary's rights with respect to such breach or any subsequent breaches. This Agreement is personal to Licensee and may not be assigned or transferred for any reason whatsoever (including, without limitation, by operation of law, merger, reorganization, or as a result of an acquisition or change of control involving Licensee) without Aviary's prior written consent and any action or conduct in violation of the foregoing shall be void and without effect. Aviary expressly reserves the right to assign this Agreement and to delegate any of its obligations hereunder. * * This is the entire and only material agreement on this matter between our companies (unless we have another one, signed by both of us). * Any disputes on this agreement will be governed by NY law and NY courts. While you are in town suing us, please do make sure to stop in a real NY deli and get some pastrami and rye. It's delicious! * More discussion of where the court will be located. Like boyscouts, our motto is "Always Be Prepared" and courts and wedding halls book up early this time of year. * You sue us and we win, you're buying our attorneys a new Mercedes. * Even if you use white-out on your screen to erase some of this agreement, it doesn't matter. Only Aviary can put the white-out on the screen. * If some of this agreement isn't legally valid, whatever remains if it will hold strong. * If we don't respond quickly to your breaching this agreement, it doesn't mean we can't do so in the future. * This agreement will always be between Aviary, Inc and you. You can't transfer this agreement. If someone buys your product or company and plans to continue using it, they will need to agree to these terms separately. * In the event that Aviary, Inc is sold or the API changes ownership, Aviary will be able to transfer the API to a new owner without impacting our agreement. * If some of this agreement isn't legally valid, whatever remains if it will hold strong. * Last Updated September 15, 2011 * * It's a sunny, brisk day in NYC. We hope you're having a good day whenever you read and agree to this! Please do drop us an email with any further questions about this agreement to api@aviary.com and either way please do let us know how you plan to use our API so we can promote you! Cheers, Avi */ package com.aviary.android.feather; import it.sephiroth.android.library.imagezoom.ImageViewTouch; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Future; import android.app.AlertDialog; import android.app.Dialog; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.ColorFilter; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.provider.MediaStore.Images.Media; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.view.animation.Animation.AnimationListener; import android.view.animation.AnimationUtils; import android.widget.ArrayAdapter; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import android.widget.ToggleButton; import android.widget.ViewAnimator; import android.widget.ViewFlipper; import com.aviary.android.feather.FilterManager.FeatherContext; import com.aviary.android.feather.FilterManager.OnBitmapChangeListener; import com.aviary.android.feather.FilterManager.OnToolListener; import com.aviary.android.feather.async_tasks.DownloadImageAsyncTask; import com.aviary.android.feather.async_tasks.DownloadImageAsyncTask.OnImageDownloadListener; import com.aviary.android.feather.async_tasks.ExifTask; import com.aviary.android.feather.effects.AbstractEffectPanel.ContentPanel; import com.aviary.android.feather.effects.EffectLoaderService; import com.aviary.android.feather.graphics.AnimatedRotateDrawable; import com.aviary.android.feather.graphics.RepeatableHorizontalDrawable; import com.aviary.android.feather.graphics.ToolIconsDrawable; import com.aviary.android.feather.library.content.EffectEntry; import com.aviary.android.feather.library.content.FeatherIntent; import com.aviary.android.feather.library.filters.FilterLoaderFactory; import com.aviary.android.feather.library.filters.NativeFilterProxy; import com.aviary.android.feather.library.filters.NativeFilterProxy.JNIInitError; import com.aviary.android.feather.library.graphics.animation.Flip3dAnimation; import com.aviary.android.feather.library.graphics.drawable.IBitmapDrawable; 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.media.ExifInterfaceWrapper; import com.aviary.android.feather.library.services.FutureListener; import com.aviary.android.feather.library.services.LocalDataService; import com.aviary.android.feather.library.services.ThreadPoolService; import com.aviary.android.feather.library.services.drag.DragLayer; import com.aviary.android.feather.library.tracking.Tracker; import com.aviary.android.feather.library.utils.BitmapUtils; import com.aviary.android.feather.library.utils.IOUtils; import com.aviary.android.feather.library.utils.ImageLoader.ImageSizes; import com.aviary.android.feather.library.utils.ReflectionUtils; import com.aviary.android.feather.library.utils.SystemUtils; import com.aviary.android.feather.library.utils.UIConfiguration; import com.aviary.android.feather.receivers.FeatherSystemReceiver; import com.aviary.android.feather.utils.ThreadUtils; import com.aviary.android.feather.utils.UIUtils; import com.aviary.android.feather.widget.BottombarViewFlipper; import com.aviary.android.feather.widget.IToast; import com.aviary.android.feather.widget.ToolbarView; import com.aviary.android.feather.widget.ToolbarView.OnToolbarClickListener; import com.aviary.android.feather.widget.wp.CellLayout; import com.aviary.android.feather.widget.wp.CellLayout.CellInfo; import com.aviary.android.feather.widget.wp.Workspace; import com.aviary.android.feather.widget.wp.Workspace.OnPageChangeListener; import com.aviary.android.feather.widget.wp.WorkspaceIndicator; /** * FeatherActivity is the main activity controller. * * @author alessandro */ public class FeatherActivity extends MonitoredActivity implements OnToolbarClickListener, OnImageDownloadListener, OnToolListener, FeatherContext, OnPageChangeListener, OnBitmapChangeListener { /** The Constant ALERT_CONFIRM_EXIT. */ private static final int ALERT_CONFIRM_EXIT = 0; /** The Constant ALERT_DOWNLOAD_ERROR. */ private static final int ALERT_DOWNLOAD_ERROR = 1; /** The Constant ALERT_REVERT_IMAGE. */ private static final int ALERT_REVERT_IMAGE = 2; static { logger = LoggerFactory.getLogger( FeatherActivity.class.getSimpleName(), LoggerType.ConsoleLoggerType ); } /** Version string number. */ public static final String SDK_VERSION = "2.2.1"; /** Internal version number. */ public static final int SDK_INT = 74; /** SHA-1 version id. */ public static final String ID = "$Id: ea259b84ab4c54948ab4d5eeb2e340ccfe6ee8e5 $"; /** delay between click and panel opening */ private static final int TOOLS_OPEN_DELAY_TIME = 50; /** The default result code. */ private int pResultCode = RESULT_CANCELED; /** The main top toolbar view. */ private ToolbarView mToolbar; /** The maim tools workspace. */ private Workspace mWorkspace; /** The main view flipper. */ private ViewAnimator mViewFlipper; /** The maim image view. */ private ImageViewTouch mImageView; /** The main drawing view container for tools implementing {@link ContentPanel}. */ private ViewGroup mDrawingViewContainer; /** inline progress loader. */ private View mInlineProgressLoader; /** The main filter controller. */ protected FilterManager mFilterManager; /** tool list to show. */ protected List<String> mToolList; /** The items per page for the main workspace. */ private int mItemsPerPage; /** The total screen cols. */ private int mScreenCols; /** The total screen rows. */ private int mScreenRows; /** The workspace indicator. */ private WorkspaceIndicator mWorkspaceIndicator; /** saving variable. */ protected boolean mSaving; /** The current screen orientation. */ private int mOrientation; /** The bottom bar flipper. */ private BottombarViewFlipper mBottomBarFlipper; /** default logger. */ protected static Logger logger; /** default handler. */ protected final Handler mHandler = new Handler(); /** hide exit alert confirmation. */ protected boolean mHideExitAlertConfirmation = false; /** The list of tools entries. */ private List<EffectEntry> mListEntries; /** The toolbar content animator. */ private ViewFlipper mToolbarContentAnimator; /** The toolbar main animator. */ private ViewFlipper mToolbarMainAnimator; /** the popup container */ private ViewGroup mPopupContainer; private DragLayer mDragLayer; /** Main image downloader task **/ private DownloadImageAsyncTask mDownloadTask; private static class MyUIHandler extends Handler { private WeakReference<FeatherActivity> mParent; MyUIHandler( FeatherActivity parent ) { mParent = new WeakReference<FeatherActivity>( parent ); } @Override public void handleMessage( Message msg ) { super.handleMessage( msg ); logger.info( "handleMessage: " + msg.what ); FeatherActivity parent = mParent.get(); if ( null != parent ) { switch ( msg.what ) { case FilterManager.STATE_OPENING: parent.mToolbar.setClickable( false ); parent.resetToolIndicator(); break; case FilterManager.STATE_OPENED: parent.mToolbar.setClickable( true ); break; case FilterManager.STATE_CLOSING: parent.mToolbar.setClickable( false ); parent.mImageView.setVisibility( View.VISIBLE ); break; case FilterManager.STATE_CLOSED: parent.mWorkspace.setEnabled( true ); parent.mToolbar.setClickable( true ); parent.mToolbar.setState( ToolbarView.STATE.STATE_SAVE, true ); parent.mToolbar.setSaveEnabled( true ); parent.mWorkspace.requestFocus(); break; case FilterManager.STATE_DISABLED: parent.mWorkspace.setEnabled( false ); parent.mToolbar.setClickable( false ); parent.mToolbar.setSaveEnabled( false ); break; case FilterManager.STATE_CONTENT_READY: parent.mImageView.setVisibility( View.GONE ); break; case FilterManager.STATE_READY: parent.mToolbar.setTitle( parent.mFilterManager.getCurrentEffect().labelResourceId, false ); parent.mToolbar.setState( ToolbarView.STATE.STATE_APPLY, false ); break; } } } } private MyUIHandler mUIHandler; /** The default broadcast receiver. It receives messages from the {@link FeatherSystemReceiver} */ private BroadcastReceiver mDefaultReceiver = new BroadcastReceiver() { @Override public void onReceive( Context context, Intent intent ) { if ( mFilterManager != null ) { Bundle extras = intent.getExtras(); if ( null != extras ) { if ( extras.containsKey( FeatherIntent.APPLICATION_CONTEXT ) ) { String app_context = extras.getString( FeatherIntent.APPLICATION_CONTEXT ); if ( app_context.equals( context.getApplicationContext().getPackageName() ) ) { mFilterManager.onPluginChanged( intent ); } } } } } }; /** * Override the internal setResult in order to register the internal close status. * * @param resultCode * the result code * @param data * the data */ protected final void onSetResult( int resultCode, Intent data ) { pResultCode = resultCode; setResult( resultCode, data ); } @Override public void onCreate( Bundle savedInstanceState ) { onPreCreate(); super.onCreate( savedInstanceState ); setContentView( R.layout.feather_main ); onInitializeUtils(); initializeUI(); onRegisterReceiver(); // initiate the filter manager mUIHandler = new MyUIHandler( this ); mFilterManager = new FilterManager( this, mUIHandler, getApiKey() ); mFilterManager.setOnToolListener( this ); mFilterManager.setOnBitmapChangeListener( this ); mFilterManager.setDragLayer( mDragLayer ); // first check the validity of the incoming intent Uri srcUri = handleIntent( getIntent() ); if ( srcUri == null ) { onSetResult( RESULT_CANCELED, null ); finish(); return; } LocalDataService data = mFilterManager.getService( LocalDataService.class ); data.setSourceImageUri( srcUri ); // download the requested image loadImage( srcUri ); // initialize filters delayedInitializeTools(); logger.error( "MAX MEMORY", mFilterManager.getApplicationMaxMemory() ); Tracker.recordTag( "feather: opened" ); } protected void onPreCreate() {} /** * Initialize the IntentFilter receiver in order to get updates about new installed plugins */ protected void onRegisterReceiver() { IntentFilter filter = new IntentFilter(); filter.addAction( FeatherIntent.ACTION_PLUGIN_ADDED ); filter.addAction( FeatherIntent.ACTION_PLUGIN_REMOVED ); filter.addAction( FeatherIntent.ACTION_PLUGIN_REPLACED ); filter.addDataScheme( "package" ); registerReceiver( mDefaultReceiver, filter ); } /** * Initialize utility classes */ protected void onInitializeUtils() { UIUtils.init( this ); Constants.init( this ); final String api_key = getApiKey(); JNIInitError result = NativeFilterProxy.init( this, null, api_key ); if( result != JNIInitError.NoError ) { Toast.makeText( getApplicationContext(), "Sorry an error occurred: " + result.name(), Toast.LENGTH_LONG ).show(); logger.error("Error initializing Aviary: " + result.name() ); finish(); } if( api_key == null || !(api_key.length() > 0) ) { logger.error( "Attention. API-KEY cannot be found or is invalid" ); finish(); } } /* * (non-Javadoc) * * @see android.app.Activity#onSaveInstanceState(android.os.Bundle) */ @Override protected void onSaveInstanceState( Bundle outState ) { logger.info( "onSaveInstanceState" ); super.onSaveInstanceState( outState ); } @Override protected void onRestoreInstanceState( Bundle savedInstanceState ) { logger.info( "onRestoreInstanceState: " + savedInstanceState ); super.onRestoreInstanceState( savedInstanceState ); } /* * (non-Javadoc) * * @see com.aviary.android.feather.MonitoredActivity#onDestroy() */ @Override protected void onDestroy() { logger.info( "onDestroy" ); if ( pResultCode != RESULT_OK ) Tracker.recordTag( "feather: cancelled" ); super.onDestroy(); unregisterReceiver( mDefaultReceiver ); mToolbar.setOnToolbarClickListener( null ); mFilterManager.setOnBitmapChangeListener( null ); mFilterManager.setOnToolListener( null ); mWorkspace.setOnPageChangeListener( null ); if ( null != mDownloadTask ) { mDownloadTask.setOnLoadListener( null ); mDownloadTask = null; } if ( mFilterManager != null ) { mFilterManager.dispose(); } mUIHandler = null; mFilterManager = null; } @Override protected void onPause() { super.onPause(); // here we tweak the default android animation between activities // just comment the next line if you don't want to have custom animations overridePendingTransition( R.anim.feather_app_zoom_enter_large, R.anim.feather_app_zoom_exit_large ); } /** * Initialize ui. */ @SuppressWarnings("deprecation") private void initializeUI() { // forcing a horizontal repeatable background findViewById( R.id.workspace_container ).setBackgroundDrawable( new RepeatableHorizontalDrawable( getResources(), R.drawable.feather_toolbar_background ) ); findViewById( R.id.toolbar ).setBackgroundDrawable( new RepeatableHorizontalDrawable( getResources(), R.drawable.feather_toolbar_background ) ); // register the toolbar listeners mToolbar.setOnToolbarClickListener( this ); mImageView.setDoubleTapEnabled( false ); // initialize the workspace initWorkspace(); } /* * (non-Javadoc) * * @see android.app.Activity#onCreateDialog(int) */ @Override protected Dialog onCreateDialog( int id ) { Dialog dialog = null; switch ( id ) { case ALERT_CONFIRM_EXIT: dialog = new AlertDialog.Builder( this ).setTitle( R.string.confirm ).setMessage( R.string.confirm_quit_message ) .setPositiveButton( R.string.yes_leave, new DialogInterface.OnClickListener() { @Override public void onClick( DialogInterface dialog, int which ) { dialog.dismiss(); onBackPressed( true ); } } ).setNegativeButton( R.string.keep_editing, new DialogInterface.OnClickListener() { @Override public void onClick( DialogInterface dialog, int which ) { dialog.dismiss(); } } ).create(); break; case ALERT_DOWNLOAD_ERROR: dialog = new AlertDialog.Builder( this ).setTitle( R.string.attention ) .setMessage( R.string.error_download_image_message ).create(); break; case ALERT_REVERT_IMAGE: dialog = new AlertDialog.Builder( this ).setTitle( R.string.revert_dialog_title ) .setMessage( R.string.revert_dialog_message ) .setPositiveButton( android.R.string.yes, new DialogInterface.OnClickListener() { @Override public void onClick( DialogInterface dialog, int which ) { dialog.dismiss(); onRevert(); } } ).setNegativeButton( android.R.string.no, new DialogInterface.OnClickListener() { @Override public void onClick( DialogInterface dialog, int which ) { dialog.dismiss(); } } ).create(); break; } return dialog; } /** * Revert the original image. */ private void onRevert() { Tracker.recordTag( "feather: reset image" ); LocalDataService service = mFilterManager.getService( LocalDataService.class ); loadImage( service.getSourceImageUri() ); } /** * Manage the screen configuration change if the screen orientation has changed, notify the filter manager and reload the main * workspace view. * * @param newConfig * the new config */ @Override public void onConfigurationChanged( final Configuration newConfig ) { super.onConfigurationChanged( newConfig ); if ( mOrientation != newConfig.orientation ) { mOrientation = newConfig.orientation; Constants.update( this ); mHandler.post( new Runnable() { @Override public void run() { boolean handled = false; if ( mFilterManager != null ) handled = mFilterManager.onConfigurationChanged( newConfig ); if ( !handled ) { initWorkspace(); delayedInitializeTools(); } else { mHandler.post( new Runnable() { @Override public void run() { initWorkspace(); delayedInitializeTools(); } } ); } } } ); } mOrientation = newConfig.orientation; } /* * (non-Javadoc) * * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu) */ @Override public boolean onCreateOptionsMenu( Menu menu ) { MenuInflater inflater = getMenuInflater(); inflater.inflate( R.menu.feather_menu, menu ); return true; } /* * (non-Javadoc) * * @see android.app.Activity#onPrepareOptionsMenu(android.view.Menu) */ @Override public boolean onPrepareOptionsMenu( Menu menu ) { if ( mSaving ) return false; if ( mFilterManager.getEnabled() && mFilterManager.isClosed() ) { return true; } else { return false; } } /* * (non-Javadoc) * * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem) */ @SuppressWarnings("deprecation") @Override public boolean onOptionsItemSelected( MenuItem item ) { int id = item.getItemId(); if ( id == R.id.edit_reset ) { showDialog( ALERT_REVERT_IMAGE ); return true; } else if ( id == R.id.edit_cancel ) { onMenuCancel(); return true; } else if ( id == R.id.edit_save ) { onSaveClick(); return true; } else if ( id == R.id.edit_premium ) { onMenuFindMorePlugins(); return true; } else { return super.onOptionsItemSelected( item ); } } /** * User clicked on cancel from the main menu */ @SuppressWarnings("deprecation") private void onMenuCancel() { if ( mFilterManager.getBitmapIsChanged() ) { if ( mHideExitAlertConfirmation ) { onSetResult( RESULT_CANCELED, null ); finish(); } else { showDialog( ALERT_CONFIRM_EXIT ); } } else { onSetResult( RESULT_CANCELED, null ); finish(); } } /** * User clicked on the store main menu item */ private void onMenuFindMorePlugins() { mFilterManager.searchPlugin( -1 ); Tracker.recordTag( "menu: get_more" ); } /** * Load an image using an async task. * * @param data * the data */ protected void loadImage( Uri data ) { if ( null != mDownloadTask ) { mDownloadTask.setOnLoadListener( null ); mDownloadTask = null; } mDownloadTask = new DownloadImageAsyncTask( data ); mDownloadTask.setOnLoadListener( this ); mDownloadTask.execute( getBaseContext() ); } /** * Inits the workspace. */ private void initWorkspace() { mScreenRows = getResources().getInteger( R.integer.feather_config_portraitRows ); mScreenCols = getResources().getInteger( R.integer.toolCount ); mItemsPerPage = mScreenRows * mScreenCols; mWorkspace.setHapticFeedbackEnabled( false ); mWorkspace.setIndicator( mWorkspaceIndicator ); mWorkspace.setOnPageChangeListener( this ); mWorkspace.setAdapter( null ); } /* * (non-Javadoc) * * @see android.app.Activity#onContentChanged() */ @Override public void onContentChanged() { super.onContentChanged(); mDragLayer = (DragLayer) findViewById( R.id.dragLayer ); mToolbar = (ToolbarView) findViewById( R.id.toolbar ); mBottomBarFlipper = (BottombarViewFlipper) findViewById( R.id.bottombar_view_flipper ); mWorkspace = (Workspace) mBottomBarFlipper.findViewById( R.id.workspace ); mImageView = (ImageViewTouch) findViewById( R.id.image ); mDrawingViewContainer = (ViewGroup) findViewById( R.id.drawing_view_container ); mInlineProgressLoader = findViewById( R.id.image_loading_view ); mWorkspaceIndicator = (WorkspaceIndicator) findViewById( R.id.workspace_indicator ); mViewFlipper = ( (ViewAnimator) findViewById( R.id.main_flipper ) ); mToolbarMainAnimator = ( (ViewFlipper) mToolbar.findViewById( R.id.top_indicator_main ) ); mToolbarContentAnimator = ( (ViewFlipper) mToolbar.findViewById( R.id.top_indicator_panel ) ); mPopupContainer = (ViewGroup) findViewById( R.id.feather_dialogs_container ); // update the progressbar animation drawable AnimatedRotateDrawable d = new AnimatedRotateDrawable( getResources(), R.drawable.feather_spinner_white_16 ); ProgressBar view = (ProgressBar) mToolbarContentAnimator.getChildAt( 1 ); view.setIndeterminateDrawable( d ); // adding the bottombar content view at runtime, otherwise fail to get the corrent child // LayoutInflater inflater = (LayoutInflater) getSystemService( Context.LAYOUT_INFLATER_SERVICE ); // View contentView = inflater.inflate( R.layout.feather_option_panel_content, mBottomBarFlipper, false ); // FrameLayout.LayoutParams p = new FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, // FrameLayout.LayoutParams.WRAP_CONTENT ); // p.gravity = Gravity.BOTTOM; // mBottomBarFlipper.addView( contentView, 0, p ); mBottomBarFlipper.setDisplayedChild( 1 ); } /* * (non-Javadoc) * * @see android.app.Activity#onBackPressed() */ @SuppressWarnings("deprecation") @Override public void onBackPressed() { if ( !mFilterManager.onBackPressed() ) { if ( mToastLoader != null ) mToastLoader.hide(); // hide info screen if opened if ( isInfoScreenVisible() ) { hideInfoScreen(); return; } if ( mFilterManager.getBitmapIsChanged() ) { if ( mHideExitAlertConfirmation ) { super.onBackPressed(); } else { showDialog( ALERT_CONFIRM_EXIT ); } } else { super.onBackPressed(); } } } /** * On back pressed. * * @param force * the super backpressed behavior */ protected void onBackPressed( boolean force ) { if ( force ) super.onBackPressed(); else onBackPressed(); } /** * Handle the original received intent. * * @param intent * the intent * @return the uri */ protected Uri handleIntent( Intent intent ) { LocalDataService service = mFilterManager.getService( LocalDataService.class ); if ( intent != null && intent.getData() != null ) { Uri data = intent.getData(); if ( SystemUtils.isIceCreamSandwich() ) { if ( data.toString().startsWith( "content://com.android.gallery3d.provider" ) ) { // use the com.google provider, not the com.android provider ( for ICS only ) data = Uri.parse( data.toString().replace( "com.android.gallery3d", "com.google.android.gallery3d" ) ); } } Bundle extras = intent.getExtras(); if ( extras != null ) { Uri destUri = (Uri) extras.getParcelable( Constants.EXTRA_OUTPUT ); if ( destUri != null ) { service.setDestImageUri( destUri ); String outputFormatString = extras.getString( Constants.EXTRA_OUTPUT_FORMAT ); if ( outputFormatString != null ) { CompressFormat format = Bitmap.CompressFormat.valueOf( outputFormatString ); service.setOutputFormat( format ); } } if ( extras.containsKey( Constants.EXTRA_TOOLS_LIST ) ) { mToolList = Arrays.asList( extras.getStringArray( Constants.EXTRA_TOOLS_LIST ) ); } if ( extras.containsKey( Constants.EXTRA_HIDE_EXIT_UNSAVE_CONFIRMATION ) ) { mHideExitAlertConfirmation = extras.getBoolean( Constants.EXTRA_HIDE_EXIT_UNSAVE_CONFIRMATION ); } } return data; } return null; } /** * Load the current tools list in a separate thread */ private void delayedInitializeTools() { Thread t = new Thread( new Runnable() { @Override public void run() { final List<EffectEntry> listEntries = loadTools(); mHandler.post( new Runnable() { @Override public void run() { onToolsLoaded( listEntries ); } } ); } } ); t.start(); } private List<String> loadStandaloneTools() { // let's use a global try..catch try { // This is the preference class used in the standalone app // if the tool list is empty, let's try to use // the user defined toolset Object instance = ReflectionUtils.invokeStaticMethod( "com.aviary.android.feather.utils.SettingsUtils", "getInstance", new Class[] { Context.class }, this ); if ( null != instance ) { Object toolList = ReflectionUtils.invokeMethod( instance, "getToolList" ); if ( null != toolList && toolList instanceof String[] ) { return Arrays.asList( (String[]) toolList ); } } } catch ( Exception t ) { } return null; } private List<EffectEntry> loadTools() { if ( null == mListEntries ) { EffectLoaderService service = mFilterManager.getService( EffectLoaderService.class ); if ( service == null ) return null; if ( mToolList == null ) { mToolList = loadStandaloneTools(); if ( null == mToolList ) { mToolList = Arrays.asList( FilterLoaderFactory.getDefaultFilters() ); } } List<EffectEntry> listEntries = new ArrayList<EffectEntry>(); Map<String, EffectEntry> entryMap = new HashMap<String, EffectEntry>(); EffectEntry[] all_entries = service.getEffects(); for ( int i = 0; i < all_entries.length; i++ ) { FilterLoaderFactory.Filters entry_name = all_entries[i].name; if ( !mToolList.contains( entry_name.name() ) ) continue; entryMap.put( entry_name.name(), all_entries[i] ); } if( !Constants.getValueFromIntent( Constants.EXTRA_FRAMES_ENABLE_EXTERNAL_PACKS, true ) ) { entryMap.remove( FilterLoaderFactory.Filters.BORDERS.name() ); } for ( String toolName : mToolList ) { if ( !entryMap.containsKey( toolName ) ) continue; listEntries.add( entryMap.get( toolName ) ); } return listEntries; } return mListEntries; } protected void onToolsLoaded( List<EffectEntry> listEntries ) { mListEntries = listEntries; WorkspaceAdapter adapter = new WorkspaceAdapter( getBaseContext(), R.layout.feather_workspace_screen, -1, mListEntries ); mWorkspace.setAdapter( adapter ); if ( mListEntries.size() <= mItemsPerPage ) { mWorkspaceIndicator.setVisibility( View.INVISIBLE ); } else { mWorkspaceIndicator.setVisibility( View.VISIBLE ); } } /** * Returns the application main toolbar which contains the save/undo/redo buttons. * * @return the toolbar * @see ToolbarView */ @Override public ToolbarView getToolbar() { return mToolbar; } /** * Return the current panel used to populate the active tool options. * * @return the options panel container */ @Override public ViewGroup getOptionsPanelContainer() { return mBottomBarFlipper.getContent(); } /* * (non-Javadoc) * * @see com.aviary.android.feather.FilterManager.FeatherContext#getBottomBar() */ @Override public BottombarViewFlipper getBottomBar() { return mBottomBarFlipper; } /** * Returns the application workspace view which contains the scrolling tools icons. * * @return the workspace * @see WorkspaceView */ public Workspace getWorkspace() { return mWorkspace; } /** * Return the main image view. * * @return the main image */ @Override public ImageViewTouch getMainImage() { return mImageView; } /** * Return the actual view used to populate a {@link ContentPanel}. * * @see {@link ContentPanel#getContentView(LayoutInflater)} * @return the drawing image container */ @Override public ViewGroup getDrawingImageContainer() { return mDrawingViewContainer; } @Override public ViewGroup activatePopupContainer() { mPopupContainer.setVisibility( View.VISIBLE ); return mPopupContainer; } @Override public void deactivatePopupContainer() { mPopupContainer.removeAllViews(); mPopupContainer.setVisibility( View.GONE ); } // --------------------- // Toolbar events // --------------------- /** * User clicked on the save button.<br /> * Start the save process */ @Override public void onSaveClick() { if ( mFilterManager.getEnabled() ) { mFilterManager.onSave(); if ( mFilterManager != null ) { Bitmap bitmap = mFilterManager.getBitmap(); if ( bitmap != null ) { performSave( bitmap ); } } } } /** * User clicked on the apply button.<br /> * Apply the current tool modifications and update the main image */ @Override public void onApplyClick() { mFilterManager.onApply(); } /** * User cancelled the active tool. Discard all the tool changes. */ @Override public void onCancelClick() { mFilterManager.onCancel(); } /** * load the original file EXIF data and store the result into the local data instance */ protected void loadExif() { logger.log( "loadExif" ); final LocalDataService data = mFilterManager.getService( LocalDataService.class ); ThreadPoolService thread = mFilterManager.getService( ThreadPoolService.class ); if ( null != data && thread != null ) { final String path = data.getSourceImagePath(); FutureListener<Bundle> listener = new FutureListener<Bundle>() { @Override public void onFutureDone( Future<Bundle> future ) { try { Bundle result = future.get(); if ( null != result ) { data.setOriginalExifBundle( result ); } } catch ( Throwable e ) { e.printStackTrace(); } } }; if ( null != path ) { thread.submit( new ExifTask(), listener, path ); } else { logger.warning( "orinal file path not available" ); } } } /** * Try to compute the original file absolute path */ protected void computeOriginalFilePath() { final LocalDataService data = mFilterManager.getService( LocalDataService.class ); if ( null != data ) { data.setSourceImagePath( null ); Uri uri = data.getSourceImageUri(); if ( null != uri ) { String path = IOUtils.getRealFilePath( this, uri ); if ( null != path ) { data.setSourceImagePath( path ); } } } } // -------------------------------- // DownloadImageAsyncTask listener // -------------------------------- /** * Local or remote image has been completely loaded. Now it's time to enable all the tools and fade in the image * * @param result * the result * @param originalSize * int array containing the width and height of the loaded bitmap */ @Override public void onDownloadComplete( Bitmap result, ImageSizes sizes ) { logger.log( "onDownloadComplete" ); mDownloadTask = null; mImageView.setImageBitmap( result, true, null, UIConfiguration.IMAGE_VIEW_MAX_ZOOM ); Animation animation = AnimationUtils.loadAnimation( this, android.R.anim.fade_in ); animation.setFillEnabled( true ); mImageView.setVisibility( View.VISIBLE ); mImageView.startAnimation( animation ); hideProgressLoader(); int[] originalSize = { -1, -1 }; if ( null != sizes ) { originalSize = sizes.getRealSize(); onImageSize( sizes.getOriginalSize(), sizes.getNewSize(), sizes.getBucketSize() ); } if ( mFilterManager != null ) { if ( mFilterManager.getEnabled() ) { mFilterManager.onReplaceImage( result, originalSize ); } else { mFilterManager.onActivate( result, originalSize ); } } if ( null != result && null != originalSize && originalSize.length > 1 ) { logger.error( "original.size: " + originalSize[0] + "x" + originalSize[1] ); logger.error( "final.size: " + result.getWidth() + "x" + result.getHeight() ); } computeOriginalFilePath(); loadExif(); } /* * (non-Javadoc) * * @see com.aviary.android.feather.async_tasks.DownloadImageAsyncTask.OnImageDownloadListener#onDownloadError(java.lang.String) */ @SuppressWarnings("deprecation") @Override public void onDownloadError( String error ) { logger.error( "onDownloadError", error ); mDownloadTask = null; hideProgressLoader(); showDialog( ALERT_DOWNLOAD_ERROR ); } /** * Hide progress loader. */ private void hideProgressLoader() { Animation fadeout = new AlphaAnimation( 1, 0 ); fadeout.setDuration( getResources().getInteger( R.integer.feather_config_mediumAnimTime ) ); fadeout.setAnimationListener( new AnimationListener() { @Override public void onAnimationStart( Animation animation ) {} @Override public void onAnimationRepeat( Animation animation ) {} @Override public void onAnimationEnd( Animation animation ) { mInlineProgressLoader.setVisibility( View.GONE ); } } ); mInlineProgressLoader.startAnimation( fadeout ); } /* * (non-Javadoc) * * @see com.aviary.android.feather.async_tasks.DownloadImageAsyncTask.OnImageDownloadListener#onDownloadStart() */ @Override public void onDownloadStart() { mImageView.setVisibility( View.INVISIBLE ); mInlineProgressLoader.setVisibility( View.VISIBLE ); } // ------------------------------- // Bitmap change listener // ------------------------------- @Override public void onPreviewChange( Bitmap bitmap ) { final boolean changed = BitmapUtils.compareBySize( ( (IBitmapDrawable) mImageView.getDrawable() ).getBitmap(), bitmap ); mImageView.setImageBitmap( bitmap, changed, null, UIConfiguration.IMAGE_VIEW_MAX_ZOOM ); } @Override public void onPreviewChange( ColorFilter filter ) { mImageView.setColorFilter( filter ); } @Override public void onBitmapChange( Bitmap bitmap, boolean update, android.graphics.Matrix matrix ) { mImageView.setImageBitmap( bitmap, update, matrix, UIConfiguration.IMAGE_VIEW_MAX_ZOOM ); }; @Override public void onClearColorFilter() { mImageView.clearColorFilter(); } /** * Perform save. * * @param bitmap * the bitmap */ protected void performSave( final Bitmap bitmap ) { if ( mSaving ) return; mSaving = true; Tracker.recordTag( "feather: saved" ); // disable the filter manager... mFilterManager.setEnabled( false ); LocalDataService service = mFilterManager.getService( LocalDataService.class ); // Release bitmap memory Bundle myExtras = getIntent().getExtras(); // if request intent has "return-data" then the result bitmap // will be encoded into the result itent if ( myExtras != null && myExtras.getBoolean( Constants.EXTRA_RETURN_DATA ) ) { Bundle extras = new Bundle(); extras.putParcelable( "data", bitmap ); onSetResult( RESULT_OK, new Intent().setData( service.getDestImageUri() ).setAction( "inline-data" ).putExtras( extras ) ); finish(); } else { ThreadUtils.startBackgroundJob( this, null, "Saving...", new Runnable() { @Override public void run() { doSave( bitmap ); } }, mHandler ); } } /** * Do save. * * @param bitmap * the bitmap */ protected void doSave( Bitmap bitmap ) { // result extras Bundle extras = new Bundle(); LocalDataService service = mFilterManager.getService( LocalDataService.class ); Uri saveUri = service.getDestImageUri(); // if the request intent has EXTRA_OUTPUT declared // then save the image into the output uri and return it if ( saveUri != null ) { OutputStream outputStream = null; String scheme = saveUri.getScheme(); try { if ( scheme == null ) { outputStream = new FileOutputStream( saveUri.getPath() ); } else { outputStream = getContentResolver().openOutputStream( saveUri ); } if ( outputStream != null ) { int quality = Constants.getValueFromIntent( Constants.EXTRA_OUTPUT_QUALITY, 80 ); bitmap.compress( service.getOutputFormat(), quality, outputStream ); } } catch ( IOException ex ) { logger.error( "Cannot open file", saveUri, ex ); } finally { IOUtils.closeSilently( outputStream ); } onSetResult( RESULT_OK, new Intent().setData( saveUri ).putExtras( extras ) ); } else { // no output uri declared, save the image in a new path // and return it String url = Media.insertImage( getContentResolver(), bitmap, "title", "modified with Aviary Feather" ); if ( url != null ) { saveUri = Uri.parse( url ); getContentResolver().notifyChange( saveUri, null ); } onSetResult( RESULT_OK, new Intent().setData( saveUri ).putExtras( extras ) ); } final Bitmap b = bitmap; mHandler.post( new Runnable() { @Override public void run() { mImageView.clear(); b.recycle(); } } ); if ( null != saveUri ) { saveExif( saveUri ); } mSaving = false; finish(); } protected void saveExif( Uri uri ) { logger.log( "saveExif: " + uri ); if ( null != uri ) { saveExif( uri.getPath() ); } } protected void saveExif( String path ) { logger.log( "saveExif: " + path ); if ( null == path ) { return; } LocalDataService data = mFilterManager.getService( LocalDataService.class ); ExifInterfaceWrapper newexif = null; if ( null != data ) { try { newexif = new ExifInterfaceWrapper( path ); } catch ( IOException e ) { logger.error( e.getMessage() ); e.printStackTrace(); return; }; Bundle bundle = data.getOriginalExifBundle(); if ( null != bundle ) { try { newexif.copyFrom( bundle ); newexif.setAttribute( ExifInterfaceWrapper.TAG_ORIENTATION, "0" ); newexif.setAttribute( ExifInterfaceWrapper.TAG_SOFTWARE, "Aviary for Android " + SDK_VERSION ); // implements this to include your own tags onSaveCustomTags( newexif ); newexif.saveAttributes(); } catch ( Throwable t ) { t.printStackTrace(); logger.error( t.getMessage() ); } } } } protected void onSaveCustomTags( ExifInterfaceWrapper exif ) { } /* * (non-Javadoc) * * @see com.aviary.android.feather.FilterManager.OnToolListener#onToolCompleted() */ @Override public void onToolCompleted() { final long anim_time = mToolbar.getInAnimationTime(); mToolbarMainAnimator.postDelayed( new Runnable() { @Override public void run() { mToolbarMainAnimator.setDisplayedChild( 2 ); } }, anim_time + 100 ); mToolbarMainAnimator.postDelayed( new Runnable() { @Override public void run() { mToolbarMainAnimator.setDisplayedChild( 0 ); } }, anim_time + 900 ); } private IToast mToastLoader; /** * show the progress indicator in the toolbar content. */ @Override public void showToolProgress() { mToolbarContentAnimator.setDisplayedChild( 1 ); } /** * hide the progress indicator in the toolbar content reset to the first null state. */ @Override public void hideToolProgress() { mToolbarContentAnimator.setDisplayedChild( 0 ); } @Override public void showModalProgress() { if ( mToastLoader == null ) { mToastLoader = UIUtils.createModalLoaderToast(); } mToastLoader.show(); } @Override public void hideModalProgress() { if ( mToastLoader != null ) { mToastLoader.hide(); } } /** * Creates the info screen animations. * * @param isOpening * the is opening */ private void createInfoScreenAnimations( final boolean isOpening ) { final float centerX = mViewFlipper.getWidth() / 2.0f; final float centerY = mViewFlipper.getHeight() / 2.0f; Animation mMainViewAnimationIn, mMainViewAnimationOut; final int duration = getResources().getInteger( R.integer.feather_config_infoscreen_animTime ); // we allow the flip3d animation only if the OS is > android 2.2 if ( android.os.Build.VERSION.SDK_INT > 8 ) { mMainViewAnimationIn = new Flip3dAnimation( isOpening ? -180 : 180, 0, centerX, centerY ); mMainViewAnimationOut = new Flip3dAnimation( 0, isOpening ? 180 : -180, centerX, centerY ); mMainViewAnimationIn.setDuration( duration ); mMainViewAnimationOut.setDuration( duration ); } else { // otherwise let's just use a regular alpha animation mMainViewAnimationIn = new AlphaAnimation( 0.0f, 1.0f ); mMainViewAnimationOut = new AlphaAnimation( 1.0f, 0.0f ); mMainViewAnimationIn.setDuration( duration / 2 ); mMainViewAnimationOut.setDuration( duration / 2 ); } mViewFlipper.setInAnimation( mMainViewAnimationIn ); mViewFlipper.setOutAnimation( mMainViewAnimationOut ); } /** * Display the big info screen Flip the main image with the infoscreen view. */ private void showInfoScreen() { createInfoScreenAnimations( true ); // Add the infoscreen view to the UI. ViewGroup view = (ViewGroup) mViewFlipper.findViewById( R.id.infocreeen_container ); if( null != view && view.getChildCount() == 0 ) { UIUtils.getLayoutInflater().inflate( R.layout.feather_infoscreen, view, true ); } mViewFlipper.setDisplayedChild( 1 ); TextView text = (TextView) mViewFlipper.findViewById( R.id.version_text ); text.setText( "v " + FeatherActivity.SDK_VERSION ); mViewFlipper.findViewById( R.id.aviary_infoscreen_submit ).setOnClickListener( new OnClickListener() { @Override public void onClick( View v ) { String url = "http://www.aviary.com"; Intent i = new Intent( Intent.ACTION_VIEW ); i.setData( Uri.parse( url ) ); try { startActivity( i ); } catch ( ActivityNotFoundException e ) { e.printStackTrace(); } } } ); } /** * Hide info screen. */ private void hideInfoScreen() { createInfoScreenAnimations( false ); mViewFlipper.setDisplayedChild( 0 ); View convertView = mWorkspace.getChildAt( mWorkspace.getChildCount() - 1 ); if ( null != convertView ) { View button = convertView.findViewById( R.id.tool_image ); if ( button != null && button instanceof ToggleButton ) { ( (ToggleButton) button ).setChecked( false ); } } } /** * Checks if is info screen visible. * * @return true, if is info screen visible */ private boolean isInfoScreenVisible() { return mViewFlipper.getDisplayedChild() == 1; } /** * reset the toolbar indicator to the first null state. */ public void resetToolIndicator() { mToolbarContentAnimator.setDisplayedChild( 0 ); } /** * Gets the uI handler. * * @return the uI handler */ Handler getUIHandler() { return mUIHandler; } /* * (non-Javadoc) * * @see com.aviary.android.feather.MonitoredActivity#onStart() */ @Override public void onStart() { super.onStart(); mOrientation = getResources().getConfiguration().orientation; // getWindowManager().getDefaultDisplay().getRotation(); } /* * (non-Javadoc) * * @see com.aviary.android.feather.MonitoredActivity#onStop() */ @Override public void onStop() { super.onStop(); } /* * (non-Javadoc) * * @see android.app.Activity#onRestart() */ @Override protected void onRestart() { super.onRestart(); } /* * (non-Javadoc) * * @see com.aviary.android.feather.MonitoredActivity#onResume() */ @Override protected void onResume() { super.onResume(); } protected void onImageSize( String originalSize, String scaledSize, String bucket ) { HashMap<String, String> attributes = new HashMap<String, String>(); attributes.put( "originalSize", originalSize ); attributes.put( "newSize", scaledSize ); attributes.put( "bucketSize", bucket ); Tracker.recordTag( "image: scaled", attributes ); } /** * The Class WorkspaceAdapter. */ class WorkspaceAdapter extends ArrayAdapter<EffectEntry> { /** The m layout inflater. */ private LayoutInflater mLayoutInflater; /** The m resource id. */ private int mResourceId; /** * Instantiates a new workspace adapter. * * @param context * the context * @param resource * the resource * @param textViewResourceId * the text view resource id * @param objects * the objects */ public WorkspaceAdapter( Context context, int resource, int textViewResourceId, List<EffectEntry> objects ) { super( context, resource, textViewResourceId, objects ); mResourceId = resource; mLayoutInflater = (LayoutInflater) getContext().getSystemService( Context.LAYOUT_INFLATER_SERVICE ); } /** * We need to return 1 page more than the real count because of the info screen. Last page will be the info screen * * @return the count */ @Override public int getCount() { int realCount = (int) Math.ceil( (double) super.getCount() / mItemsPerPage ); return realCount + 1; } /** * Gets the real count. * * @return the real count */ public int getRealCount() { return super.getCount(); } /* * (non-Javadoc) * * @see android.widget.BaseAdapter#getItemViewType(int) */ @Override public int getItemViewType( int position ) { if ( position == getCount() - 1 ) { return 1; } else { return 0; } } /* * (non-Javadoc) * * @see android.widget.BaseAdapter#getViewTypeCount() */ @Override public int getViewTypeCount() { return 2; } /** * The Class WorkspaceToolViewHolder. */ class WorkspaceToolViewHolder { /** The image. */ public ImageView image; /** The text. */ public TextView text; }; /* * (non-Javadoc) * * @see android.widget.ArrayAdapter#getView(int, android.view.View, android.view.ViewGroup) */ @Override public View getView( int position, View convertView, ViewGroup parent ) { if ( convertView == null ) { convertView = mLayoutInflater.inflate( mResourceId, mWorkspace, false ); ( (CellLayout) convertView ).setNumRows( mScreenRows ); ( (CellLayout) convertView ).setNumCols( mScreenCols ); } CellLayout cell = (CellLayout) convertView; int index = position * mItemsPerPage; int realCount = getRealCount(); WorkspaceToolViewHolder holder; if ( getItemViewType( position ) == 1 ) { // last page, info screen cell.removeAllViews(); CellInfo cellInfo = cell.findVacantCell( mScreenCols, 1 ); ViewGroup toolView = (ViewGroup) mLayoutInflater.inflate( R.layout.feather_egg_info_view, parent, false ); CellLayout.LayoutParams lp = new CellLayout.LayoutParams( cellInfo.cellX, cellInfo.cellY, cellInfo.spanH, cellInfo.spanV ); cell.addView( toolView, -1, lp ); ToggleButton button = (ToggleButton) toolView.findViewById( R.id.tool_image ); button.setChecked( false ); button.setOnCheckedChangeListener( new OnCheckedChangeListener() { @Override public void onCheckedChanged( CompoundButton buttonView, boolean isChecked ) { if ( isChecked ) { showInfoScreen(); mWorkspace.setFocusable( false ); } else { hideInfoScreen(); mWorkspace.setFocusable( true ); } } } ); return cell; } if ( cell.findViewById( R.id.egg_info_view ) != null ) { cell.removeAllViews(); } for ( int i = 0; i < mItemsPerPage; i++ ) { ViewGroup toolView; CellInfo cellInfo = cell.findVacantCell( 1, 1 ); if ( cellInfo == null ) { toolView = (ViewGroup) cell.getChildAt( i ); holder = (WorkspaceToolViewHolder) toolView.getTag(); } else { toolView = (ViewGroup) mLayoutInflater.inflate( R.layout.feather_egg_view, parent, false ); CellLayout.LayoutParams lp = new CellLayout.LayoutParams( cellInfo.cellX, cellInfo.cellY, cellInfo.spanH, cellInfo.spanV ); cell.addView( toolView, -1, lp ); holder = new WorkspaceToolViewHolder(); holder.image = (ImageView) toolView.findViewById( R.id.tool_image ); holder.text = (TextView) toolView.findViewById( R.id.tool_text ); // if( null != UIUtils.getAppTypeFace() ){ // holder.text.setTypeface( UIUtils.getAppTypeFace() ); // } toolView.setTag( holder ); } if ( ( index + i ) < realCount ) { loadEgg( index + i, toolView ); toolView.setVisibility( View.VISIBLE ); } else { toolView.setVisibility( View.INVISIBLE ); } } convertView.requestLayout(); return convertView; } /** * Load egg. * * @param position * the position * @param view * the view */ private void loadEgg( int position, final ViewGroup view ) { EffectEntry entry = getItem( position ); final WorkspaceToolViewHolder holder = (WorkspaceToolViewHolder) view.getTag(); holder.image.setImageDrawable( new ToolIconsDrawable( getResources(), entry.iconResourceId ) ); holder.text.setText( entry.labelResourceId ); holder.image.setTag( entry ); holder.image.setOnClickListener( new OnClickListener() { @Override public void onClick( View v ) { mUIHandler.postDelayed( new Runnable() { @Override public void run() { if ( mWorkspace.isEnabled() ) mFilterManager.activateEffect( (EffectEntry) holder.image.getTag() ); } }, TOOLS_OPEN_DELAY_TIME ); } } ); } } /* * (non-Javadoc) * * @see com.aviary.android.feather.widget.wp.Workspace.OnPageChangeListener#onPageChanged(int) */ @Override public void onPageChanged( int which, int old ) { if ( mWorkspace != null && mWorkspace.getAdapter() != null ) { if ( which == mWorkspace.getAdapter().getCount() - 2 && old == mWorkspace.getAdapter().getCount() - 1 ) { if ( isInfoScreenVisible() ) { hideInfoScreen(); } } } } public FilterManager getController() { return mFilterManager; } }