Android Open Source - Aviary-Android-SDK Feather Activity






From Project

Back to project page Aviary-Android-SDK.

License

The source code is released under:

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 ...

If you think the Android project Aviary-Android-SDK listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * AVIARY API TERMS OF USE//from   w ww  .j av  a2 s  .  com
 * 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 Licensees 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 Licensees 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
 * Licensees 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 it.sephiroth.android.library.imagezoom.ImageViewTouchBase.DisplayType;
import it.sephiroth.android.library.imagezoom.graphics.IBitmapDrawable;
import it.sephiroth.android.library.media.ExifInterfaceExtended;
import it.sephiroth.android.library.widget.AdapterView;
import it.sephiroth.android.library.widget.AdapterView.OnItemClickListener;
import it.sephiroth.android.library.widget.HListView;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Matrix;
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.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
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.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.aviary.android.feather.AviaryMainController.FeatherContext;
import com.aviary.android.feather.AviaryMainController.OnBitmapChangeListener;
import com.aviary.android.feather.AviaryMainController.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.cds.AviaryCds;
import com.aviary.android.feather.cds.CdsUtils;
import com.aviary.android.feather.common.log.LoggerFactory;
import com.aviary.android.feather.common.log.LoggerFactory.Logger;
import com.aviary.android.feather.common.log.LoggerFactory.LoggerType;
import com.aviary.android.feather.common.threading.Future;
import com.aviary.android.feather.common.threading.FutureListener;
import com.aviary.android.feather.common.utils.DateTimeUtils;
import com.aviary.android.feather.common.utils.IOUtils;
import com.aviary.android.feather.common.utils.PackageManagerUtils;
import com.aviary.android.feather.common.utils.ReflectionUtils;
import com.aviary.android.feather.common.utils.SDKUtils;
import com.aviary.android.feather.common.utils.ScreenUtils;
import com.aviary.android.feather.common.utils.SystemUtils;
import com.aviary.android.feather.effects.AbstractPanel.ContentPanel;
import com.aviary.android.feather.effects.AbstractPanelLoaderService;
import com.aviary.android.feather.headless.AviaryInitializationException;
import com.aviary.android.feather.headless.filters.NativeFilterProxy;
import com.aviary.android.feather.library.Constants;
import com.aviary.android.feather.library.MonitoredActivity;
import com.aviary.android.feather.library.content.ToolEntry;
import com.aviary.android.feather.library.filters.FilterLoaderFactory;
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.ImageSizes;
import com.aviary.android.feather.library.utils.UIConfiguration;
import com.aviary.android.feather.utils.ThreadUtils;
import com.aviary.android.feather.widget.AviaryBottomBarViewFlipper;
import com.aviary.android.feather.widget.AviaryBottomBarViewFlipper.OnBottomBarItemClickListener;
import com.aviary.android.feather.widget.AviaryImageRestoreSwitcher;
import com.aviary.android.feather.widget.AviaryImageRestoreSwitcher.OnRestoreStateListener;
import com.aviary.android.feather.widget.AviaryImageRestoreSwitcher.RestoreState;
import com.aviary.android.feather.widget.AviaryNavBarViewFlipper;
import com.aviary.android.feather.widget.AviaryNavBarViewFlipper.OnToolbarClickListener;
import com.aviary.android.feather.widget.AviaryToast;

/**
 * FeatherActivity is the main activity controller.
 * 
 * @author alessandro
 */
public class FeatherActivity extends MonitoredActivity implements OnToolbarClickListener, OnImageDownloadListener, OnToolListener, FeatherContext,
    OnBitmapChangeListener, OnBottomBarItemClickListener, OnRestoreStateListener, OnItemClickListener {

  private static final int ALERT_CONFIRM_EXIT = 0;
  private static final int ALERT_DOWNLOAD_ERROR = 1;
  private static final int ALERT_REVERT_IMAGE = 2;
  private static final int ALERT_FEEDBACK = 3;
  private static final int ALERT_ABOUT = 4;
  private static final int ALERT_CONFIRM_EXIT_WITH_NO_CHANGES = 5;

  static {
    logger = LoggerFactory.getLogger( FeatherActivity.class.getSimpleName(), LoggerType.ConsoleLoggerType );
  }

  /** SHA-1 version id. */
  public static final String ID = "$Id: d6e8b15a32d57de787894ab437cee84d35ac40ff $";

  /** delay between click and panel opening */
  private static final int TOOLS_OPEN_DELAY_TIME = 50;

  private int pResultCode = RESULT_CANCELED;

  /** The main toolbar view. */
  private AviaryNavBarViewFlipper mToolbar;

  /** the main tools list view */
  private HListView mToolsList;

  /** The custom Toast used for modal loaders */
  private AviaryToast mToastLoader;

  /**
   * The main drawing view container for tools implementing {@link ContentPanel}.
   */
  private ViewGroup mDrawingViewContainer;

  /** inline progress loader. */
  private View mInlineProgressLoader;

  /** The main controller. */
  protected AviaryMainController mMainController;

  /** tool list to show. */
  protected List<String> mToolList;

  /** saving variable. */
  protected boolean mSaving;

  /** The current screen orientation. */
  private int mOrientation;

  /** The bottom bar view. */
  private AviaryBottomBarViewFlipper 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<ToolEntry> mListEntries;

  /** the popup container */
  private ViewGroup mPopupContainer;

  private DragLayer mDragLayer;

  /** Main image downloader task **/
  private DownloadImageAsyncTask mDownloadTask;

  /** The current Activity is visible and active */
  private boolean mIsRunning;

  private Handler mUIHandler = new Handler( new Handler.Callback() {

    @Override
    public boolean handleMessage( Message msg ) {
      onStateChanged( msg.what, msg.arg1, msg.obj );
      return true;
    }
  } );

  protected void onStateChanged( int new_state, int arg1, Object obj ) {
    logger.info( "onStateChanged: " + new_state );

    switch ( new_state ) {
      case AviaryMainController.STATE_OPENING:
        mToolbar.setClickable( false );
        break;

      case AviaryMainController.STATE_OPENED:
        mToolbar.setClickable( true );
        break;

      case AviaryMainController.STATE_CLOSING:
        mToolbar.setClickable( false );
        mImageRestore.setVisibility( View.VISIBLE );
        break;

      case AviaryMainController.STATE_CLOSED:
        mToolsList.setEnabled( true );
        mToolbar.setClickable( true );
        mToolbar.close();
        mToolbar.setSaveEnabled( true );
        mToolsList.requestFocus();
        break;

      case AviaryMainController.STATE_DISABLED:

        mToolsList.setEnabled( false );
        mToolbar.setClickable( false );
        mToolbar.setSaveEnabled( false );
        break;

      case AviaryMainController.STATE_CONTENT_READY:
        mImageRestore.setVisibility( View.GONE );
        break;

      case AviaryMainController.STATE_READY:
        mToolbar.setTitle( mMainController.getActiveTool().labelResourceId, false );
        mToolbar.open();
        // once a tool panel has been opened, reset the main image view display
        // TODO: this is not correct
        getMainImage().resetMatrix();
        mImageRestore.clearStatus();
        break;

      case AviaryMainController.TOOLBAR_TITLE:
        mToolbar.setTitle( (CharSequence) obj );
        break;

      case AviaryMainController.TOOLBAR_TITLE_INT:
        mToolbar.setTitle( arg1 );
        break;

      case AviaryMainController.TOOLBAR_APPLY_VISIBILITY:
        mToolbar.setApplyVisible( arg1 == 0 ? false : true );
        break;
    }
  }

  private AviaryImageRestoreSwitcher mImageRestore;

  /**
   * 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;

    if ( null != data && null != mMainController ) {

      LocalDataService service = mMainController.getService( LocalDataService.class );

      // copy the relevant extras from the original intent
      if ( service.getIntentContainsKey( Constants.EXTRA_OUTPUT_HIRES_SESSION_ID ) ) {
        data.putExtra( Constants.EXTRA_OUTPUT_HIRES_SESSION_ID, service.getIntentValue( Constants.EXTRA_OUTPUT_HIRES_SESSION_ID, "" ) );
      }
    }

    setResult( resultCode, data );
  }

  @Override
  public void onCreate( Bundle savedInstanceState ) {

    long t1 = DateTimeUtils.tick();

    onPreCreate();

    super.onCreate( savedInstanceState );

    // if the device is not considered a tablet
    // let's disable the landascape orientation
    if ( !ScreenUtils.isTablet( this ) ) {
      setRequestedOrientation( ActivityInfo.SCREEN_ORIENTATION_PORTRAIT );
    }

    setContentView( R.layout.aviary_main_view );

    // Create the main Controller
    initializeController();

    onInitializeUtils();

    onInitializeUI();

    // initiate the filter manager
    mMainController.setOnToolListener( this );
    mMainController.setOnBitmapChangeListener( this );
    mMainController.setDragLayer( mDragLayer );

    // first check the validity of the incoming intent
    Uri srcUri = handleIntent( getIntent() );

    if ( srcUri == null ) {
      onSetResult( RESULT_CANCELED, null );
      finish();
      return;
    }

    // download the requested image
    loadImage( srcUri );

    // initialize filters
    delayedInitializeTools();

    // calling post-create
    onPostCreate();
    
    logger.error( "MAX MEMORY", SystemUtils.getApplicationTotalMemory() );
    Tracker.recordTag( "feather: opened" );
    DateTimeUtils.tick( t1, "onCreate finished" );
  }
  
  private void initializeController() {
    mMainController = new AviaryMainController( this, mUIHandler );
    onControllerLoaded( mMainController );
  }
  
  protected void onControllerLoaded( AviaryMainController controller ) {}

  protected void onPostCreate() {}

  protected void onPreCreate() {}

  protected void onInitializeUtils() {
    try {
      NativeFilterProxy.init( this );
    } catch ( AviaryInitializationException e ) {
      e.printStackTrace();

      Toast.makeText( getApplicationContext(), "Sorry an error occurred: " + e.getMessage(), Toast.LENGTH_LONG ).show();
      finish();
    }
  }

  @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 );
  }

  @Override
  protected void onDestroy() {
    logger.info( "onDestroy" );

    if ( pResultCode != RESULT_OK ) Tracker.recordTag( "feather: cancelled" );

    super.onDestroy();

    mToolbar.setOnToolbarClickListener( null );
    mBottomBarFlipper.setOnBottomBarItemClickListener( null );
    mMainController.setOnBitmapChangeListener( null );
    mMainController.setOnToolListener( null );

    if ( null != mDownloadTask ) {
      mDownloadTask.setOnLoadListener( null );
      mDownloadTask = null;
    }

    if ( mMainController != null ) {
      mMainController.dispose();
    }

    mUIHandler = null;
    mMainController = null;
  }

  /**
   * Initialize varius UI elements.
   */
  private void onInitializeUI() {
    // register the toolbar listeners
    mToolbar.setOnToolbarClickListener( this );

    // image view
    mImageRestore.getDefaultImageView().setDoubleTapEnabled( false );
    mImageRestore.getDefaultImageView().setDisplayType( DisplayType.FIT_IF_BIGGER );
    mImageRestore.getRestoredImageView().setDisplayType( DisplayType.FIT_IF_BIGGER );

    // initialize tools UI
    // mToolsList.setOverScrollMode( HListView.OVER_SCROLL_ALWAYS );
    mToolsList.setAdapter( null );

    mBottomBarFlipper.setOnBottomBarItemClickListener( this );

    LocalDataService service = mMainController.getService( LocalDataService.class );
    
    if ( service.getIntentContainsKey( Constants.EXTRA_WHITELABEL ) ) {
      mBottomBarFlipper.toggleLogoVisibility( false );
    }
  }

  @Override
  protected Dialog onCreateDialog( int id ) {

    Dialog dialog = null;

    switch ( id ) {
      case ALERT_CONFIRM_EXIT:
        dialog = new AlertDialog.Builder( this ).setTitle( R.string.feather_confirm ).setMessage( R.string.confirm_quit_message )
            .setPositiveButton( R.string.feather_yes_leave, new DialogInterface.OnClickListener() {

              @Override
              public void onClick( DialogInterface dialog, int which ) {
                dialog.dismiss();
                onBackPressed( true );
              }
            } ).setNegativeButton( R.string.feather_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.feather_attention ).setMessage( R.string.feather_error_download_image_message )
            .create();
        break;

      case ALERT_REVERT_IMAGE:
        dialog = new AlertDialog.Builder( this ).setTitle( R.string.feather_revert_dialog_title ).setMessage( R.string.feather_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;

      case ALERT_FEEDBACK:
        dialog = createFeedbackDialog();
        break;

      case ALERT_ABOUT:
        dialog = createAboutDialog();
        break;

      case ALERT_CONFIRM_EXIT_WITH_NO_CHANGES:
        dialog = new AlertDialog.Builder( this ).setTitle( R.string.feather_confirm ).setMessage( R.string.feather_unsaved_from_camera )
            .setPositiveButton( R.string.feather_yes_leave, new DialogInterface.OnClickListener() {

              @Override
              public void onClick( DialogInterface dialog, int which ) {
                dialog.dismiss();
                onBackPressed( true );
              }
            } ).setNegativeButton( R.string.feather_keep_editing, new DialogInterface.OnClickListener() {

              @Override
              public void onClick( DialogInterface dialog, int which ) {
                dialog.dismiss();
              }
            } ).create();
        break;
    }

    return dialog;
  }

  protected Dialog createBaseDialog( int message, String buttonText, final OnClickListener button1Listener ) {
    final Dialog dialog = new Dialog( FeatherActivity.this, R.style.AviaryTheme_Dialog_Custom );
    dialog.requestWindowFeature( Window.FEATURE_NO_TITLE );
    dialog.setContentView( R.layout.aviary_feedback_dialog_view );
    dialog.setCanceledOnTouchOutside( true );

    Window dialogWindow = dialog.getWindow();
    TextView textVersion = (TextView) dialogWindow.findViewById( R.id.aviary_version );
    TextView textMessage = (TextView) dialogWindow.findViewById( R.id.aviary_text );

    Button button1 = (Button) dialogWindow.findViewById( R.id.aviary_button1 );
    Button button2 = (Button) dialogWindow.findViewById( R.id.aviary_button2 );

    textVersion.setText( getString( R.string.feather_version ) + " " + SDKUtils.SDK_VERSION );
    textMessage.setText( message );

    button1.setText( buttonText );
    button1.setOnClickListener( new OnClickListener() {

      @Override
      public void onClick( View v ) {
        if ( null != button1Listener ) {
          button1Listener.onClick( v );
        }
        dialog.dismiss();
      }
    } );

    button2.setOnClickListener( new OnClickListener() {

      @Override
      public void onClick( View v ) {
        dialog.dismiss();
      }
    } );

    return dialog;
  }

  /**
   * Creates the About dialog
   * 
   * @return
   */
  protected Dialog createAboutDialog() {

    final OnClickListener listener = new OnClickListener() {

      @Override
      public void onClick( View v ) {
        final Intent intent = new Intent( android.content.Intent.ACTION_VIEW );
        intent.setData( Uri.parse( "http://www.aviary.com/android" ) );
        FeatherActivity.this.startActivity( intent );
      }
    };

    final Dialog dialog = createBaseDialog( R.string.feather_about_dialog_message, "aviary.com/android", listener );
    return dialog;
  }

  /**
   * Creates the Feedback dialog
   * 
   * @return
   */
  protected Dialog createFeedbackDialog() {
    final OnClickListener listener = new OnClickListener() {

      @Override
      public void onClick( View v ) {
        
        Intent intent = new Intent( Intent.ACTION_VIEW );
        intent.setData( Uri.parse( "http://support.aviary.com/" ) );
        FeatherActivity.this.startActivity( intent );        
      }
    };

    final Dialog dialog = createBaseDialog( R.string.feather_feedback_dialog_message, getString( R.string.feather_send_feedback ), listener );
    return dialog;
  }

  /**
   * Revert the original image.
   */
  private void onRevert() {
    Tracker.recordTag( "feather: reset image" );

    LocalDataService service = mMainController.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;

      if ( null != mMainController ) {
        mMainController.onConfigurationChanged( newConfig );
      }

    }
    mOrientation = newConfig.orientation;
  }

  @Override
  public boolean onPrepareOptionsMenu( Menu menu ) {
    if ( mSaving ) return false;
    if ( mMainController.getEnabled() && mMainController.isClosed() ) {
      return true;
    } else {
      return false;
    }
  }

  @SuppressWarnings ( "deprecation" )
  @Override
  public void onBottomBarItemClick( int id ) {
    if ( id == R.id.aviary_white_logo ) {
      showDialog( ALERT_ABOUT );
    }
  }

  /**
   * Load an image using an async task.
   * @param data
   */
  protected void loadImage( Uri data ) {
    if ( null != mDownloadTask ) {
      mDownloadTask.setOnLoadListener( null );
      mDownloadTask = null;
    }

    LocalDataService dataService = mMainController.getService( LocalDataService.class );
    dataService.setSourceImageUri( data );
    int maxSize = dataService.getIntentValue( Constants.EXTRA_MAX_IMAGE_SIZE, 0 );

    mDownloadTask = new DownloadImageAsyncTask( data, maxSize );
    mDownloadTask.setOnLoadListener( this );
    mDownloadTask.execute( getBaseContext() );
  }

  @Override
  public void onContentChanged() {
    super.onContentChanged();

    mDragLayer = (DragLayer) findViewById( R.id.dragLayer );

    mToolbar = (AviaryNavBarViewFlipper) findViewById( R.id.aviary_navbar );
    mBottomBarFlipper = (AviaryBottomBarViewFlipper) findViewById( R.id.aviary_bottombar );

    mToolsList = mBottomBarFlipper.getToolsListView();

    mDrawingViewContainer = (ViewGroup) findViewById( R.id.drawing_view_container );
    mInlineProgressLoader = findViewById( R.id.image_loading_view );

    mPopupContainer = (ViewGroup) findViewById( R.id.feather_dialogs_container );

    mBottomBarFlipper.setDisplayedChild( 1 );

    mImageRestore = (AviaryImageRestoreSwitcher) findViewById( R.id.aviary_restore );
  }

  @SuppressWarnings ( "deprecation" )
  @Override
  public void onBackPressed() {

    if ( mToolbar.restored() || mImageRestore.getStatus() != RestoreState.None ) {
      logger.log( "Restore enabled, let's close that first!" );
      mImageRestore.clearStatus();
      return;
    }

    if ( !mMainController.onBackPressed() ) {

      if ( mToastLoader != null ) mToastLoader.hide();

      if ( mMainController.getBitmapIsChanged() ) {

        if ( mHideExitAlertConfirmation ) {
          super.onBackPressed();
        } else {
          showDialog( ALERT_CONFIRM_EXIT );
        }
      } else {
        if ( !handleBackPressedOnUnchangedImage() ) {
          super.onBackPressed();
        }
      }
    }
  }

  @SuppressWarnings ( "deprecation" )
  protected boolean handleBackPressedOnUnchangedImage() {
    final AviaryMainController controller = getMainController();
    if ( null != controller ) {
      LocalDataService service = getMainController().getService( LocalDataService.class );
      if ( null != service ) {
        if ( service.getIntentContainsKey( Constants.EXTRA_IN_SOURCE_TYPE ) ) {
          String value = service.getIntentValue( Constants.EXTRA_IN_SOURCE_TYPE, "Gallery" );
          if ( "Camera".equals( value ) ) {
            // ok show the alert message...
            showDialog( ALERT_CONFIRM_EXIT_WITH_NO_CHANGES );
            return true;
          }
        }
      }
    }
    return false;
  }

  /**
   * Override the default behavior of back pressed
   * 
   * @param force
   *            the super backpressed behavior
   */
  protected void onBackPressed( boolean force ) {
    if ( force ) super.onBackPressed();
    else onBackPressed();
  }

  /**
   * Handles the original input Intent.
   * 
   * @param intent
   * @return the uri of the image to be loaded
   */
  protected Uri handleIntent( Intent intent ) {

    logger.info( "handleIntent" );

    LocalDataService service = mMainController.getService( LocalDataService.class );

    if ( intent != null && intent.getData() != null ) {
      
      final String action = intent.getAction();
      HashMap<String, String> map = new HashMap<String, String>();
      map.put( "action", null != action ? action : "null" );
      Tracker.recordTag( "feather: intent", map );
      
      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" ) );
        }
      }

      logger.log( "src: " + data );

      Bundle extras = intent.getExtras();
      if ( extras != null ) {
        Uri destUri = (Uri) extras.getParcelable( Constants.EXTRA_OUTPUT );

        if ( destUri != null ) {

          logger.log( "dest: " + destUri );

          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<ToolEntry> listEntries = loadTools();
        final List<String> permissions = CdsUtils.getPermissions( FeatherActivity.this );
        mHandler.post( new Runnable() {

          @Override
          public void run() {
            onToolsLoaded( listEntries, permissions );
          }
        } );
      }
    } );
    t.start();
  }

  private List<String> loadStandaloneTools() {
    if ( PackageManagerUtils.isStandalone( this ) ) {
      logger.info( "isStandalone, 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<ToolEntry> loadTools() {
    AbstractPanelLoaderService service = mMainController.getService( AbstractPanelLoaderService.class );
    if ( service == null ) return null;
    if ( mToolList == null ) {

      mToolList = loadStandaloneTools();

      if ( null == mToolList ) {
        mToolList = Arrays.asList( FilterLoaderFactory.getDefaultFilters() );
      }
    }

    List<ToolEntry> listEntries = new ArrayList<ToolEntry>();
    Map<String, ToolEntry> entryMap = new HashMap<String, ToolEntry>();
    ToolEntry[] all_entries = service.getToolsEntries();

    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] );
    }

    for ( String toolName : mToolList ) {
      if ( !entryMap.containsKey( toolName ) ) continue;
      listEntries.add( entryMap.get( toolName ) );
    }
    return listEntries;
  }

  /**
   * List of tools loaded, we're ready to display them
   * 
   * @param listEntries
   */
  protected void onToolsLoaded( final List<ToolEntry> listEntries, final List<String> permissions ) {
    logger.info( "onToolsLoaded: %s", permissions );
    
    LocalDataService data = mMainController.getService( LocalDataService.class );
    boolean white_label = permissions.contains( AviaryCds.Permission.whitelabel.name() ) && data.getIntentContainsKey( Constants.EXTRA_WHITELABEL );
    
    mListEntries = listEntries;
    mToolsList.setAdapter( new ListAdapter( this, mListEntries, white_label ) );
    mToolsList.setOnItemClickListener( this );
    
    mBottomBarFlipper.toggleLogoVisibility( !white_label );
  }

  /**
   * Return the current panel used to populate the active tool options.
   * 
   * @return the options panel container
   */
  @Override
  public ViewGroup getOptionsPanelContainer() {
    return mBottomBarFlipper.getContentPanel();
  }

  @Override
  public AviaryBottomBarViewFlipper getBottomBar() {
    return mBottomBarFlipper;
  }

  /**
   * Return the main image view.
   * 
   * @return the main image
   */
  @Override
  public ImageViewTouch getMainImage() {
    return mImageRestore.getDefaultImageView();
  }

  /**
   * 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
  // ---------------------

  @Override
  public void onRestoreClick() {
    logger.info( "onRestoreClick" );
    if ( null != mMainController ) {
      if ( mMainController.getEnabled() ) {

        Tracker.recordTag( "prepost: RestoreOriginalClicked" );

        mMainController.onRestoreOriginal();
        mImageRestore.clearStatus();
      }
    }
  }

  /**
   * User clicked on the Done button
   * Start the save process
   */
  @Override
  public void onDoneClick() {

    if ( null != mMainController ) {
      if ( mMainController.getEnabled() ) {

        LocalDataService dataService = mMainController.getService( LocalDataService.class );

        final boolean changed = mMainController.getBitmapIsChanged();
        final boolean saveWithNoChanges = dataService.getIntentValue( Constants.EXTRA_IN_SAVE_ON_NO_CHANGES, true );

        if ( LoggerFactory.LOG_ENABLED ) {
          logger.log( "bitmap changed: " + changed );
          logger.log( "save with no changes: " + saveWithNoChanges );
        }

        // If the Image is not modified and the calling Intent
        // does not allow to save unmodified images, then return
        // a RESULT_CANCELED value
        if ( !changed && !saveWithNoChanges ) {
          exitWithNoModifications();
          return;
        }

        mMainController.onSave();

        Bitmap bitmap = mMainController.getBitmap();
        if ( bitmap != null ) {
          performSave( bitmap, changed );
        }
      }
    }
  }

  /**
   * Exit the Activity with a RESULT_CANCELED value and an extra
   * value to indicate that no modifications were made to the image.
   */
  protected void exitWithNoModifications() {
    logger.info( "exitWithNoModifications" );
    Intent result = new Intent();
    result.putExtra( Constants.EXTRA_OUT_BITMAP_CHANGED, false );
    onSetResult( RESULT_CANCELED, result );
    finish();
  }

  /**
   * User clicked on the Apply button of a tool
   * Apply the current tool modifications and update the main image
   */
  @Override
  public void onApplyClick() {
    mMainController.onApply();
  }

  /**
   * load the original file EXIF data and store the result into the local data
   * instance
   */
  protected void loadExif() {
    logger.log( "loadExif" );
    final LocalDataService data = mMainController.getService( LocalDataService.class );
    ThreadPoolService thread = mMainController.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.warn( "orinal file path not available" );
      }
    }
  }

  /**
   * Try to compute the original file absolute path
   */
  protected void computeOriginalFilePath() {
    final LocalDataService data = mMainController.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( final Bitmap result, final ImageSizes sizes ) {

    logger.info( "onDownloadComplete" );

    mDownloadTask = null;

    getMainImage().setImageBitmap( result, null, -1, UIConfiguration.IMAGE_VIEW_MAX_ZOOM );

    Animation animation = AnimationUtils.loadAnimation( FeatherActivity.this, android.R.anim.fade_in );
    animation.setFillEnabled( true );

    mImageRestore.setVisibility( View.VISIBLE );
    mImageRestore.startAnimation( animation );

    hideProgressLoader();

    int[] originalSize = { -1, -1 };
    if ( null != sizes ) {
      originalSize = sizes.getRealSize();
      onImageSize( sizes.getOriginalSize(), sizes.getNewSize(), sizes.getBucketSize() );
    }

    if ( mMainController != null ) {
      if ( !mMainController.getEnabled() ) {
        mMainController.onActivate( result, originalSize );
      }

      if ( mMainController.getOriginalBitmap() != null ) {
        mImageRestore.getRestoredImageView().setImageBitmap( mMainController.getOriginalBitmap(), null, -1, UIConfiguration.IMAGE_VIEW_MAX_ZOOM );
        mImageRestore.setOnRestoreStateListener( FeatherActivity.this );
        mImageRestore.setRestoreEnabled( true );
      } else {
        mImageRestore.setRestoreEnabled( false );
        mImageRestore.setOnRestoreStateListener( null );
      }
    }

    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();
  }

  @SuppressWarnings ( "deprecation" )
  @Override
  public void onDownloadError( final String error ) {
    logger.error( "onDownloadError", error );
    mDownloadTask = null;
    hideProgressLoader();
    showDialog( ALERT_DOWNLOAD_ERROR );
  }

  @Override
  protected void onActivityResult( int requestCode, int resultCode, Intent data ) {
    super.onActivityResult( requestCode, resultCode, data );
    mMainController.onActivityResult( requestCode, resultCode, data );
  }

  /**
   * Hide progress loader.
   */
  private void hideProgressLoader() {
    Animation fadeout = new AlphaAnimation( 1, 0 );
    fadeout.setDuration( getResources().getInteger( android.R.integer.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 );
  }

  @Override
  public void onDownloadStart() {
    mImageRestore.setVisibility( View.INVISIBLE );
    mInlineProgressLoader.setVisibility( View.VISIBLE );
  }

  // -------------------------------
  // Bitmap change listener
  // -------------------------------

  /**
   * We have a new preview bitmap to display
   * in the main ImageView
   */
  @Override
  public void onPreviewChange( Bitmap bitmap, boolean reset ) {

    boolean changed = true;

    if ( !reset ) {
      changed = BitmapUtils.compareBySize( ( (IBitmapDrawable) getMainImage().getDrawable() ).getBitmap(), bitmap );
    }

    logger.info( "onPreviewChange: " + bitmap + ", changed: " + changed );

    Matrix matrix = null;
    if ( !changed ) {
      matrix = getMainImage().getDisplayMatrix();
    }

    getMainImage().setImageBitmap( bitmap, matrix, -1, UIConfiguration.IMAGE_VIEW_MAX_ZOOM );
    mImageRestore.clearStatus();
  }

  /**
   * Invalidate the main ImageView
   */
  @Override
  public void onInvalidateBitmap() {
    getMainImage().invalidate();
    mImageRestore.clearStatus();
  }

  /**
   * Replace the current Bitmap with a new one
   */
  @Override
  public void onBitmapChange( Bitmap bitmap, boolean update, Matrix matrix ) {
    if ( !update && matrix == null ) {
      matrix = getMainImage().getDisplayMatrix();
    }
    getMainImage().setImageBitmap( bitmap, matrix, -1, UIConfiguration.IMAGE_VIEW_MAX_ZOOM );
    mImageRestore.clearStatus();
  };

  /**
   * Perform save.
   * 
   * @param bitmap
   *            the bitmap
   */
  protected void performSave( final Bitmap bitmap, final boolean changed ) {
    if ( mSaving ) return;

    mSaving = true;
    Tracker.recordTag( "feather: saved" );

    // disable the filter manager...
    mMainController.setEnabled( false );

    LocalDataService service = mMainController.getService( LocalDataService.class );

    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, getString( R.string.feather_save_progress ), new Runnable() {

        @Override
        public void run() {
          doSave( bitmap, changed );
        }
      }, mHandler );
    }
  }

  /**
   * Final save operation
   * 
   * @param bitmap
   *            the current Bitmap handled by the {@link AviaryMainController}
   * @param changed
   *            bitmap has been changed by the user
   */
  protected void doSave( Bitmap bitmap, boolean changed ) {

    // result extras
    Bundle extras = new Bundle();

    // save the "changed" information
    extras.putBoolean( Constants.EXTRA_OUT_BITMAP_CHANGED, changed );

    LocalDataService service = mMainController.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 = service.getIntentValue( 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() {
        getMainImage().clear();
        mImageRestore.getRestoredImageView().clear();
        b.recycle();
      }
    } );

    if ( null != saveUri ) {
      saveExif( saveUri );
    }

    mSaving = false;
    finish();
  }

  /**
   * Save the exif tags
   * 
   * @param uri
   */
  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 = mMainController.getService( LocalDataService.class );
    ExifInterfaceExtended newexif = null;

    if ( null != data ) {
      try {
        newexif = new ExifInterfaceExtended( path );
      } catch ( IOException e ) {
        logger.error( e.getMessage() );
        e.printStackTrace();
        return;
      }
      ;

      Bundle bundle = data.getOriginalExifBundle();
      if ( null != bundle ) {
        try {

          int imageWidth = newexif.getAttributeInt( ExifInterfaceExtended.TAG_JPEG_IMAGE_WIDTH, 0 );
          int imageLength = newexif.getAttributeInt( ExifInterfaceExtended.TAG_JPEG_IMAGE_HEIGHT, 0 );

          // copy all the tags from the original exif
          // to the new exif
          newexif.copyFrom( bundle, true );

          newexif.setAttribute( ExifInterfaceExtended.TAG_JPEG_IMAGE_WIDTH, String.valueOf( imageWidth ) );
          newexif.setAttribute( ExifInterfaceExtended.TAG_JPEG_IMAGE_HEIGHT, String.valueOf( imageLength ) );

          newexif.setAttribute( ExifInterfaceExtended.TAG_EXIF_ORIENTATION, "0" );
          newexif.setAttribute( ExifInterfaceExtended.TAG_EXIF_SOFTWARE, "Aviary for Android " + SDKUtils.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( ExifInterfaceExtended exif ) {}

  @Override
  public void onToolCompleted() {}

  /**
   * show the progress indicator in the toolbar content.
   */
  @Override
  public void showToolProgress() {
    mToolbar.setApplyProgressVisible( true );
  }

  /**
   * hide the progress indicator in the toolbar content reset to the first
   * null state.
   */
  @Override
  public void hideToolProgress() {
    mToolbar.setApplyProgressVisible( false );
  }

  @Override
  public void showModalProgress() {
    if ( mToastLoader == null ) {
      mToastLoader = com.aviary.android.feather.utils.UIUtils.createModalLoaderToast( this );
    }
    mToastLoader.show();
  }

  @Override
  public void hideModalProgress() {
    if ( mToastLoader != null ) {
      mToastLoader.hide();
    }
  }

  /**
   * Gets the uI handler.
   * 
   * @return the uI handler
   */
  Handler getUIHandler() {
    return mUIHandler;
  }

  @Override
  public void onStart() {
    logger.info( "onStart" );
    super.onStart();
    mOrientation = getResources().getConfiguration().orientation; // getWindowManager().getDefaultDisplay().getRotation();
  }

  @Override
  public void onStop() {
    logger.info( "onStop" );
    super.onStop();
  }

  @Override
  protected void onRestart() {
    logger.info( "onRestart" );
    super.onRestart();
  }

  @Override
  protected void onResume() {
    super.onResume();
    mIsRunning = true;
  }

  @Override
  protected void onPause() {
    super.onPause();
    mIsRunning = false;
    // here we can tweak the default android animation between activities
  }

  /**
   * Current Activity is visible and active ( ie. within the {@link #onResume()} and the
   * {@link #onPause()} methods )
   * 
   * @return
   */
  public boolean isActive() {
    return mIsRunning;
  }

  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 );
  }

  class ListAdapter extends ArrayAdapter<ToolEntry> {

    static final int TYPE_TOOL = 0;
    static final int TYPE_FEDDBACK = 1;

    Object mLock = new Object();
    LayoutInflater mInflater;
    List<ToolEntry> mObjects;
    int mViewWidth;
    int mToolViewWidth;
    boolean mWhiteLabel;

    public ListAdapter ( Context context, List<ToolEntry> objects, boolean whiteLabel ) {
      super( context, -1 );
      mViewWidth = context.getResources().getDisplayMetrics().widthPixels;
      mToolViewWidth = -1;

      mInflater = LayoutInflater.from( context );
      mObjects = objects;
      mWhiteLabel = whiteLabel;
    }

    @Override
    public int getViewTypeCount() {
      if ( mWhiteLabel ) return 1;
      return 2;
    }

    @Override
    public int getItemViewType( int position ) {
      if ( mWhiteLabel ) return TYPE_TOOL;
      return position == ( getCount() - 1 ) ? TYPE_FEDDBACK : TYPE_TOOL;
    }

    @Override
    public View getView( int position, View convertView, ViewGroup parent ) {
      final int type = getItemViewType( position );

      if ( null == convertView ) {
        if ( type == TYPE_TOOL ) {

          convertView = mInflater.inflate( R.layout.aviary_tool_layout, parent, false );
          LayoutParams params = convertView.getLayoutParams();

          if ( mToolViewWidth == -1 ) {
            int[] sizes = mToolsList.measureChild( convertView );

            logger.log( "child size: " + sizes[0] + "x" + sizes[1] );

            double numberOfItems = Math.floor( (double) mViewWidth / sizes[0] ) + 0.5;
            logger.log( "new number of items: " + numberOfItems );

            mToolViewWidth = (int) ( (double) mViewWidth / numberOfItems );
            logger.log( "new size will be: " + mToolViewWidth );
          }

          if ( null != params ) {
            params.width = mToolViewWidth;
            convertView.setLayoutParams( params );
          }

        } else {
          convertView = mInflater.inflate( R.layout.aviary_tool_feedback_layout, parent, false );
        }
      }

      if ( type == TYPE_TOOL ) {
        final ToolEntry item = getItem( position );
        convertView.setTag( item );
      }
      return convertView;
    }

    @Override
    public ToolEntry getItem( int position ) {
      return mObjects.get( position );
    }

    @Override
    public int getCount() {
      if ( mWhiteLabel ) return mObjects.size();
      return mObjects.size() + 1;
    }

    @Override
    public long getItemId( int position ) {
      return 0;
    }
  }

  /**
   * Returns the main controller
   * 
   * @return
   */
  public AviaryMainController getMainController() {
    return mMainController;
  }

  @SuppressWarnings ( "deprecation" )
  @Override
  public void onItemClick( AdapterView<?> parent, View view, int position, long id ) {
    logger.info( "onItemClick: " + position );

    if ( null != view && parent.isEnabled() && parent.getAdapter() != null ) {
      int type = parent.getAdapter().getItemViewType( position );

      if ( type == ListAdapter.TYPE_TOOL ) {
        // tool
        final Object tag = parent.getAdapter().getItem( position );

        if ( tag instanceof ToolEntry ) {
          mUIHandler.postDelayed( new Runnable() {

            @Override
            public void run() {
              // mark the badge for this tool as read
              // ( (AviaryBadgeToolLayout) view ).markAsRead();

              mMainController.activateTool( (ToolEntry) tag );
            }
          }, TOOLS_OPEN_DELAY_TIME );
        }
      } else if ( type == ListAdapter.TYPE_FEDDBACK ) {
        // feedback
        showDialog( ALERT_FEEDBACK );
      }
    }
  }

  @Override
  public boolean onRestoreBegin() {
    logger.info( "onRestoreBegin" );

    if ( null != mMainController ) {
      if ( null != mMainController.getOriginalBitmap() ) {
        if ( !mMainController.getPanelIsRendering() && mMainController.getBitmapIsChangedOrChanging() ) {
          mImageRestore.setDisplayedChild( 1 );
          Tracker.recordTag( "prepost: Pressed" );
          return true;
        }
      }
    }

    return false;
  }

  @Override
  public void onRestoreChanged() {
    logger.info( "onRestoreChanged" );

    mToolbar.toggleRestore( true );
    Tracker.recordTag( "prepost: RestoreOriginalShown" );
    // nothing...
  }

  @Override
  public void onRestoreEnd() {
    logger.info( "onRestoreEnd" );
    mImageRestore.setDisplayedChild( 0 );
    mToolbar.toggleRestore( false );
  }
}




Java Source Code List

com.aviary.android.feather.AlertActivity.java
com.aviary.android.feather.AviaryMainController.java
com.aviary.android.feather.FeatherActivity.java
com.aviary.android.feather.async_tasks.AsyncImageManager.java
com.aviary.android.feather.async_tasks.DownloadImageAsyncTask.java
com.aviary.android.feather.async_tasks.ExifTask.java
com.aviary.android.feather.effects.AbstractContentPanel.java
com.aviary.android.feather.effects.AbstractOptionPanel.java
com.aviary.android.feather.effects.AbstractPanelLoaderService.java
com.aviary.android.feather.effects.AbstractPanel.java
com.aviary.android.feather.effects.AdjustEffectPanel.java
com.aviary.android.feather.effects.BordersPanel.java
com.aviary.android.feather.effects.ColorSplashPanel.java
com.aviary.android.feather.effects.CropPanel.java
com.aviary.android.feather.effects.DelayedSpotDrawPanel.java
com.aviary.android.feather.effects.DrawingPanel.java
com.aviary.android.feather.effects.EffectsPanel.java
com.aviary.android.feather.effects.EnhanceEffectPanel.java
com.aviary.android.feather.effects.MemePanel.java
com.aviary.android.feather.effects.NativeEffectRangePanel.java
com.aviary.android.feather.effects.SimpleStatusMachine.java
com.aviary.android.feather.effects.SliderEffectPanel.java
com.aviary.android.feather.effects.StickersPanel.java
com.aviary.android.feather.effects.TextPanel.java
com.aviary.android.feather.effects.TiltShiftPanel.java
com.aviary.android.feather.graphics.CdsPreviewTransformer.java
com.aviary.android.feather.graphics.GalleryBottomIndicatorDrawable.java
com.aviary.android.feather.graphics.GalleryTopIndicatorDrawable.java
com.aviary.android.feather.graphics.GlowBitmapDrawable.java
com.aviary.android.feather.graphics.GlowDrawable.java
com.aviary.android.feather.graphics.PluginDividerDrawable.java
com.aviary.android.feather.graphics.PreviewFillColorDrawable.java
com.aviary.android.feather.graphics.PreviewSpotDrawable.java
com.aviary.android.feather.graphics.RepeatableHorizontalDrawable.java
com.aviary.android.feather.opengl.AviaryGLSurfaceView.java
com.aviary.android.feather.utils.PackIconCallable.java
com.aviary.android.feather.utils.SimpleBitmapCache.java
com.aviary.android.feather.utils.ThreadUtils.java
com.aviary.android.feather.utils.TypefaceUtils.java
com.aviary.android.feather.utils.UIUtils.java
com.aviary.android.feather.widget.AdjustImageView.java
com.aviary.android.feather.widget.AviaryAbsSpinner.java
com.aviary.android.feather.widget.AviaryAdapterView.java
com.aviary.android.feather.widget.AviaryBadgeToolLayout.java
com.aviary.android.feather.widget.AviaryBottomBarViewFlipper.java
com.aviary.android.feather.widget.AviaryButton.java
com.aviary.android.feather.widget.AviaryEdgeEffect.java
com.aviary.android.feather.widget.AviaryGalleryTopIndicatorView.java
com.aviary.android.feather.widget.AviaryGallery.java
com.aviary.android.feather.widget.AviaryHighlightImageButton.java
com.aviary.android.feather.widget.AviaryImageRestoreSwitcher.java
com.aviary.android.feather.widget.AviaryImageSwitcher.java
com.aviary.android.feather.widget.AviaryNavBarViewFlipper.java
com.aviary.android.feather.widget.AviarySeekBar.java
com.aviary.android.feather.widget.AviaryTextView.java
com.aviary.android.feather.widget.AviaryToast.java
com.aviary.android.feather.widget.AviaryToggleButton.java
com.aviary.android.feather.widget.AviaryWheel.java
com.aviary.android.feather.widget.AviaryWorkspaceIndicator.java
com.aviary.android.feather.widget.AviaryWorkspace.java
com.aviary.android.feather.widget.CellLayout.java
com.aviary.android.feather.widget.CropImageView.java
com.aviary.android.feather.widget.DrawableHighlightView.java
com.aviary.android.feather.widget.EffectThumbLayout.java
com.aviary.android.feather.widget.HighlightView.java
com.aviary.android.feather.widget.IAPBuyButton.java
com.aviary.android.feather.widget.IAPDialogDetail.java
com.aviary.android.feather.widget.IAPDialogList.java
com.aviary.android.feather.widget.IAPDialogMain.java
com.aviary.android.feather.widget.ImageViewDrawableOverlay.java
com.aviary.android.feather.widget.ImageViewSpotDraw.java
com.aviary.android.feather.widget.ImageViewTiltiShiftTouch.java
com.aviary.android.feather.widget.ImageViewTouchAndDraw.java
com.aviary.android.feather.widget.PointCloud.java
com.aviary.android.feather.widget.ScrollerRunnable.java
com.aviary.android.feather.widget.VibrationHelper.java
com.aviary.android.feather.widget.VibrationWidget.java