Home | History | Annotate | Download | only in app
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.gallery3d.app;
     18 
     19 import android.annotation.TargetApi;
     20 import android.app.ActionBar.OnMenuVisibilityListener;
     21 import android.app.Activity;
     22 import android.content.ActivityNotFoundException;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.pm.PackageManager;
     26 import android.content.res.Configuration;
     27 import android.graphics.Rect;
     28 import android.net.Uri;
     29 import android.nfc.NfcAdapter;
     30 import android.nfc.NfcAdapter.CreateBeamUrisCallback;
     31 import android.nfc.NfcEvent;
     32 import android.os.Bundle;
     33 import android.os.Handler;
     34 import android.os.Message;
     35 import android.os.SystemClock;
     36 import android.view.Menu;
     37 import android.view.MenuItem;
     38 import android.widget.RelativeLayout;
     39 import android.widget.ShareActionProvider;
     40 import android.widget.Toast;
     41 
     42 import com.android.camera.CameraActivity;
     43 import com.android.camera.ProxyLauncher;
     44 import com.android.gallery3d.R;
     45 import com.android.gallery3d.common.ApiHelper;
     46 import com.android.gallery3d.data.ComboAlbum;
     47 import com.android.gallery3d.data.DataManager;
     48 import com.android.gallery3d.data.FilterDeleteSet;
     49 import com.android.gallery3d.data.FilterSource;
     50 import com.android.gallery3d.data.LocalImage;
     51 import com.android.gallery3d.data.MediaDetails;
     52 import com.android.gallery3d.data.MediaItem;
     53 import com.android.gallery3d.data.MediaObject;
     54 import com.android.gallery3d.data.MediaObject.PanoramaSupportCallback;
     55 import com.android.gallery3d.data.MediaSet;
     56 import com.android.gallery3d.data.Path;
     57 import com.android.gallery3d.data.SecureAlbum;
     58 import com.android.gallery3d.data.SecureSource;
     59 import com.android.gallery3d.data.SnailAlbum;
     60 import com.android.gallery3d.data.SnailItem;
     61 import com.android.gallery3d.data.SnailSource;
     62 import com.android.gallery3d.filtershow.FilterShowActivity;
     63 import com.android.gallery3d.filtershow.crop.CropActivity;
     64 import com.android.gallery3d.picasasource.PicasaSource;
     65 import com.android.gallery3d.ui.DetailsHelper;
     66 import com.android.gallery3d.ui.DetailsHelper.CloseListener;
     67 import com.android.gallery3d.ui.DetailsHelper.DetailsSource;
     68 import com.android.gallery3d.ui.GLView;
     69 import com.android.gallery3d.ui.MenuExecutor;
     70 import com.android.gallery3d.ui.PhotoView;
     71 import com.android.gallery3d.ui.SelectionManager;
     72 import com.android.gallery3d.ui.SynchronizedHandler;
     73 import com.android.gallery3d.util.GalleryUtils;
     74 import com.android.gallery3d.util.UsageStatistics;
     75 
     76 public abstract class PhotoPage extends ActivityState implements
     77         PhotoView.Listener, AppBridge.Server, ShareActionProvider.OnShareTargetSelectedListener,
     78         PhotoPageBottomControls.Delegate, GalleryActionBar.OnAlbumModeSelectedListener {
     79     private static final String TAG = "PhotoPage";
     80 
     81     private static final int MSG_HIDE_BARS = 1;
     82     private static final int MSG_ON_FULL_SCREEN_CHANGED = 4;
     83     private static final int MSG_UPDATE_ACTION_BAR = 5;
     84     private static final int MSG_UNFREEZE_GLROOT = 6;
     85     private static final int MSG_WANT_BARS = 7;
     86     private static final int MSG_REFRESH_BOTTOM_CONTROLS = 8;
     87     private static final int MSG_ON_CAMERA_CENTER = 9;
     88     private static final int MSG_ON_PICTURE_CENTER = 10;
     89     private static final int MSG_REFRESH_IMAGE = 11;
     90     private static final int MSG_UPDATE_PHOTO_UI = 12;
     91     private static final int MSG_UPDATE_PROGRESS = 13;
     92     private static final int MSG_UPDATE_DEFERRED = 14;
     93     private static final int MSG_UPDATE_SHARE_URI = 15;
     94     private static final int MSG_UPDATE_PANORAMA_UI = 16;
     95 
     96     private static final int HIDE_BARS_TIMEOUT = 3500;
     97     private static final int UNFREEZE_GLROOT_TIMEOUT = 250;
     98 
     99     private static final int REQUEST_SLIDESHOW = 1;
    100     private static final int REQUEST_CROP = 2;
    101     private static final int REQUEST_CROP_PICASA = 3;
    102     private static final int REQUEST_EDIT = 4;
    103     private static final int REQUEST_PLAY_VIDEO = 5;
    104     private static final int REQUEST_TRIM = 6;
    105 
    106     public static final String KEY_MEDIA_SET_PATH = "media-set-path";
    107     public static final String KEY_MEDIA_ITEM_PATH = "media-item-path";
    108     public static final String KEY_INDEX_HINT = "index-hint";
    109     public static final String KEY_OPEN_ANIMATION_RECT = "open-animation-rect";
    110     public static final String KEY_APP_BRIDGE = "app-bridge";
    111     public static final String KEY_TREAT_BACK_AS_UP = "treat-back-as-up";
    112     public static final String KEY_START_IN_FILMSTRIP = "start-in-filmstrip";
    113     public static final String KEY_RETURN_INDEX_HINT = "return-index-hint";
    114     public static final String KEY_SHOW_WHEN_LOCKED = "show_when_locked";
    115     public static final String KEY_IN_CAMERA_ROLL = "in_camera_roll";
    116 
    117     public static final String KEY_ALBUMPAGE_TRANSITION = "albumpage-transition";
    118     public static final int MSG_ALBUMPAGE_NONE = 0;
    119     public static final int MSG_ALBUMPAGE_STARTED = 1;
    120     public static final int MSG_ALBUMPAGE_RESUMED = 2;
    121     public static final int MSG_ALBUMPAGE_PICKED = 4;
    122 
    123     public static final String ACTION_NEXTGEN_EDIT = "action_nextgen_edit";
    124     public static final String ACTION_SIMPLE_EDIT = "action_simple_edit";
    125 
    126     private GalleryApp mApplication;
    127     private SelectionManager mSelectionManager;
    128 
    129     private PhotoView mPhotoView;
    130     private PhotoPage.Model mModel;
    131     private DetailsHelper mDetailsHelper;
    132     private boolean mShowDetails;
    133 
    134     // mMediaSet could be null if there is no KEY_MEDIA_SET_PATH supplied.
    135     // E.g., viewing a photo in gmail attachment
    136     private FilterDeleteSet mMediaSet;
    137 
    138     // The mediaset used by camera launched from secure lock screen.
    139     private SecureAlbum mSecureAlbum;
    140 
    141     private int mCurrentIndex = 0;
    142     private Handler mHandler;
    143     private boolean mShowBars = true;
    144     private volatile boolean mActionBarAllowed = true;
    145     private GalleryActionBar mActionBar;
    146     private boolean mIsMenuVisible;
    147     private boolean mHaveImageEditor;
    148     private PhotoPageBottomControls mBottomControls;
    149     private PhotoPageProgressBar mProgressBar;
    150     private MediaItem mCurrentPhoto = null;
    151     private MenuExecutor mMenuExecutor;
    152     private boolean mIsActive;
    153     private boolean mShowSpinner;
    154     private String mSetPathString;
    155     // This is the original mSetPathString before adding the camera preview item.
    156     private String mOriginalSetPathString;
    157     private AppBridge mAppBridge;
    158     private SnailItem mScreenNailItem;
    159     private SnailAlbum mScreenNailSet;
    160     private OrientationManager mOrientationManager;
    161     private boolean mTreatBackAsUp;
    162     private boolean mStartInFilmstrip;
    163     private boolean mHasCameraScreennailOrPlaceholder = false;
    164     private boolean mRecenterCameraOnResume = true;
    165 
    166     // These are only valid after the panorama callback
    167     private boolean mIsPanorama;
    168     private boolean mIsPanorama360;
    169 
    170     private long mCameraSwitchCutoff = 0;
    171     private boolean mSkipUpdateCurrentPhoto = false;
    172     private static final long CAMERA_SWITCH_CUTOFF_THRESHOLD_MS = 300;
    173 
    174     private static final long DEFERRED_UPDATE_MS = 250;
    175     private boolean mDeferredUpdateWaiting = false;
    176     private long mDeferUpdateUntil = Long.MAX_VALUE;
    177 
    178     // The item that is deleted (but it can still be undeleted before commiting)
    179     private Path mDeletePath;
    180     private boolean mDeleteIsFocus;  // whether the deleted item was in focus
    181 
    182     private Uri[] mNfcPushUris = new Uri[1];
    183 
    184     private final MyMenuVisibilityListener mMenuVisibilityListener =
    185             new MyMenuVisibilityListener();
    186     private UpdateProgressListener mProgressListener;
    187 
    188     private final PanoramaSupportCallback mUpdatePanoramaMenuItemsCallback = new PanoramaSupportCallback() {
    189         @Override
    190         public void panoramaInfoAvailable(MediaObject mediaObject, boolean isPanorama,
    191                 boolean isPanorama360) {
    192             if (mediaObject == mCurrentPhoto) {
    193                 mHandler.obtainMessage(MSG_UPDATE_PANORAMA_UI, isPanorama360 ? 1 : 0, 0,
    194                         mediaObject).sendToTarget();
    195             }
    196         }
    197     };
    198 
    199     private final PanoramaSupportCallback mRefreshBottomControlsCallback = new PanoramaSupportCallback() {
    200         @Override
    201         public void panoramaInfoAvailable(MediaObject mediaObject, boolean isPanorama,
    202                 boolean isPanorama360) {
    203             if (mediaObject == mCurrentPhoto) {
    204                 mHandler.obtainMessage(MSG_REFRESH_BOTTOM_CONTROLS, isPanorama ? 1 : 0, isPanorama360 ? 1 : 0,
    205                         mediaObject).sendToTarget();
    206             }
    207         }
    208     };
    209 
    210     private final PanoramaSupportCallback mUpdateShareURICallback = new PanoramaSupportCallback() {
    211         @Override
    212         public void panoramaInfoAvailable(MediaObject mediaObject, boolean isPanorama,
    213                 boolean isPanorama360) {
    214             if (mediaObject == mCurrentPhoto) {
    215                 mHandler.obtainMessage(MSG_UPDATE_SHARE_URI, isPanorama360 ? 1 : 0, 0, mediaObject)
    216                         .sendToTarget();
    217             }
    218         }
    219     };
    220 
    221     public static interface Model extends PhotoView.Model {
    222         public void resume();
    223         public void pause();
    224         public boolean isEmpty();
    225         public void setCurrentPhoto(Path path, int indexHint);
    226     }
    227 
    228     private class MyMenuVisibilityListener implements OnMenuVisibilityListener {
    229         @Override
    230         public void onMenuVisibilityChanged(boolean isVisible) {
    231             mIsMenuVisible = isVisible;
    232             refreshHidingMessage();
    233         }
    234     }
    235 
    236     private class UpdateProgressListener implements StitchingChangeListener {
    237 
    238         @Override
    239         public void onStitchingResult(Uri uri) {
    240             sendUpdate(uri, MSG_REFRESH_IMAGE);
    241         }
    242 
    243         @Override
    244         public void onStitchingQueued(Uri uri) {
    245             sendUpdate(uri, MSG_UPDATE_PROGRESS);
    246         }
    247 
    248         @Override
    249         public void onStitchingProgress(Uri uri, final int progress) {
    250             sendUpdate(uri, MSG_UPDATE_PROGRESS);
    251         }
    252 
    253         private void sendUpdate(Uri uri, int message) {
    254             MediaObject currentPhoto = mCurrentPhoto;
    255             boolean isCurrentPhoto = currentPhoto instanceof LocalImage
    256                     && currentPhoto.getContentUri().equals(uri);
    257             if (isCurrentPhoto) {
    258                 mHandler.sendEmptyMessage(message);
    259             }
    260         }
    261     };
    262 
    263     @Override
    264     protected int getBackgroundColorId() {
    265         return R.color.photo_background;
    266     }
    267 
    268     private final GLView mRootPane = new GLView() {
    269         @Override
    270         protected void onLayout(
    271                 boolean changed, int left, int top, int right, int bottom) {
    272             mPhotoView.layout(0, 0, right - left, bottom - top);
    273             if (mShowDetails) {
    274                 mDetailsHelper.layout(left, mActionBar.getHeight(), right, bottom);
    275             }
    276         }
    277     };
    278 
    279     @Override
    280     public void onCreate(Bundle data, Bundle restoreState) {
    281         super.onCreate(data, restoreState);
    282         mActionBar = mActivity.getGalleryActionBar();
    283         mSelectionManager = new SelectionManager(mActivity, false);
    284         mMenuExecutor = new MenuExecutor(mActivity, mSelectionManager);
    285 
    286         mPhotoView = new PhotoView(mActivity);
    287         mPhotoView.setListener(this);
    288         mRootPane.addComponent(mPhotoView);
    289         mApplication = (GalleryApp) ((Activity) mActivity).getApplication();
    290         mOrientationManager = mActivity.getOrientationManager();
    291         mActivity.getGLRoot().setOrientationSource(mOrientationManager);
    292 
    293         mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
    294             @Override
    295             public void handleMessage(Message message) {
    296                 switch (message.what) {
    297                     case MSG_HIDE_BARS: {
    298                         hideBars();
    299                         break;
    300                     }
    301                     case MSG_REFRESH_BOTTOM_CONTROLS: {
    302                         if (mCurrentPhoto == message.obj && mBottomControls != null) {
    303                             mIsPanorama = message.arg1 == 1;
    304                             mIsPanorama360 = message.arg2 == 1;
    305                             mBottomControls.refresh();
    306                         }
    307                         break;
    308                     }
    309                     case MSG_ON_FULL_SCREEN_CHANGED: {
    310                         if (mAppBridge != null) {
    311                             mAppBridge.onFullScreenChanged(message.arg1 == 1);
    312                         }
    313                         break;
    314                     }
    315                     case MSG_UPDATE_ACTION_BAR: {
    316                         updateBars();
    317                         break;
    318                     }
    319                     case MSG_WANT_BARS: {
    320                         wantBars();
    321                         break;
    322                     }
    323                     case MSG_UNFREEZE_GLROOT: {
    324                         mActivity.getGLRoot().unfreeze();
    325                         break;
    326                     }
    327                     case MSG_UPDATE_DEFERRED: {
    328                         long nextUpdate = mDeferUpdateUntil - SystemClock.uptimeMillis();
    329                         if (nextUpdate <= 0) {
    330                             mDeferredUpdateWaiting = false;
    331                             updateUIForCurrentPhoto();
    332                         } else {
    333                             mHandler.sendEmptyMessageDelayed(MSG_UPDATE_DEFERRED, nextUpdate);
    334                         }
    335                         break;
    336                     }
    337                     case MSG_ON_CAMERA_CENTER: {
    338                         mSkipUpdateCurrentPhoto = false;
    339                         boolean stayedOnCamera = false;
    340                         if (!mPhotoView.getFilmMode()) {
    341                             stayedOnCamera = true;
    342                         } else if (SystemClock.uptimeMillis() < mCameraSwitchCutoff &&
    343                                 mMediaSet.getMediaItemCount() > 1) {
    344                             mPhotoView.switchToImage(1);
    345                         } else {
    346                             if (mAppBridge != null) mPhotoView.setFilmMode(false);
    347                             stayedOnCamera = true;
    348                         }
    349 
    350                         if (stayedOnCamera) {
    351                             if (mAppBridge == null && mMediaSet.getTotalMediaItemCount() > 1) {
    352                                 launchCamera();
    353                                 /* We got here by swiping from photo 1 to the
    354                                    placeholder, so make it be the thing that
    355                                    is in focus when the user presses back from
    356                                    the camera app */
    357                                 mPhotoView.switchToImage(1);
    358                             } else {
    359                                 updateBars();
    360                                 updateCurrentPhoto(mModel.getMediaItem(0));
    361                             }
    362                         }
    363                         break;
    364                     }
    365                     case MSG_ON_PICTURE_CENTER: {
    366                         if (!mPhotoView.getFilmMode() && mCurrentPhoto != null
    367                                 && (mCurrentPhoto.getSupportedOperations() & MediaObject.SUPPORT_ACTION) != 0) {
    368                             mPhotoView.setFilmMode(true);
    369                         }
    370                         break;
    371                     }
    372                     case MSG_REFRESH_IMAGE: {
    373                         final MediaItem photo = mCurrentPhoto;
    374                         mCurrentPhoto = null;
    375                         updateCurrentPhoto(photo);
    376                         break;
    377                     }
    378                     case MSG_UPDATE_PHOTO_UI: {
    379                         updateUIForCurrentPhoto();
    380                         break;
    381                     }
    382                     case MSG_UPDATE_PROGRESS: {
    383                         updateProgressBar();
    384                         break;
    385                     }
    386                     case MSG_UPDATE_SHARE_URI: {
    387                         if (mCurrentPhoto == message.obj) {
    388                             boolean isPanorama360 = message.arg1 != 0;
    389                             Uri contentUri = mCurrentPhoto.getContentUri();
    390                             Intent panoramaIntent = null;
    391                             if (isPanorama360) {
    392                                 panoramaIntent = createSharePanoramaIntent(contentUri);
    393                             }
    394                             Intent shareIntent = createShareIntent(mCurrentPhoto);
    395 
    396                             mActionBar.setShareIntents(panoramaIntent, shareIntent, PhotoPage.this);
    397                             setNfcBeamPushUri(contentUri);
    398                         }
    399                         break;
    400                     }
    401                     case MSG_UPDATE_PANORAMA_UI: {
    402                         if (mCurrentPhoto == message.obj) {
    403                             boolean isPanorama360 = message.arg1 != 0;
    404                             updatePanoramaUI(isPanorama360);
    405                         }
    406                         break;
    407                     }
    408                     default: throw new AssertionError(message.what);
    409                 }
    410             }
    411         };
    412 
    413         mSetPathString = data.getString(KEY_MEDIA_SET_PATH);
    414         mOriginalSetPathString = mSetPathString;
    415         setupNfcBeamPush();
    416         String itemPathString = data.getString(KEY_MEDIA_ITEM_PATH);
    417         Path itemPath = itemPathString != null ?
    418                 Path.fromString(data.getString(KEY_MEDIA_ITEM_PATH)) :
    419                     null;
    420         mTreatBackAsUp = data.getBoolean(KEY_TREAT_BACK_AS_UP, false);
    421         mStartInFilmstrip = data.getBoolean(KEY_START_IN_FILMSTRIP, false);
    422         boolean inCameraRoll = data.getBoolean(KEY_IN_CAMERA_ROLL, false);
    423         mCurrentIndex = data.getInt(KEY_INDEX_HINT, 0);
    424         if (mSetPathString != null) {
    425             mShowSpinner = true;
    426             mAppBridge = (AppBridge) data.getParcelable(KEY_APP_BRIDGE);
    427             if (mAppBridge != null) {
    428                 mShowBars = false;
    429                 mHasCameraScreennailOrPlaceholder = true;
    430                 mAppBridge.setServer(this);
    431 
    432                 // Get the ScreenNail from AppBridge and register it.
    433                 int id = SnailSource.newId();
    434                 Path screenNailSetPath = SnailSource.getSetPath(id);
    435                 Path screenNailItemPath = SnailSource.getItemPath(id);
    436                 mScreenNailSet = (SnailAlbum) mActivity.getDataManager()
    437                         .getMediaObject(screenNailSetPath);
    438                 mScreenNailItem = (SnailItem) mActivity.getDataManager()
    439                         .getMediaObject(screenNailItemPath);
    440                 mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail());
    441 
    442                 if (data.getBoolean(KEY_SHOW_WHEN_LOCKED, false)) {
    443                     // Set the flag to be on top of the lock screen.
    444                     mFlags |= FLAG_SHOW_WHEN_LOCKED;
    445                 }
    446 
    447                 // Don't display "empty album" action item for capture intents.
    448                 if (!mSetPathString.equals("/local/all/0")) {
    449                     // Check if the path is a secure album.
    450                     if (SecureSource.isSecurePath(mSetPathString)) {
    451                         mSecureAlbum = (SecureAlbum) mActivity.getDataManager()
    452                                 .getMediaSet(mSetPathString);
    453                         mShowSpinner = false;
    454                     }
    455                     mSetPathString = "/filter/empty/{"+mSetPathString+"}";
    456                 }
    457 
    458                 // Combine the original MediaSet with the one for ScreenNail
    459                 // from AppBridge.
    460                 mSetPathString = "/combo/item/{" + screenNailSetPath +
    461                         "," + mSetPathString + "}";
    462 
    463                 // Start from the screen nail.
    464                 itemPath = screenNailItemPath;
    465             } else if (inCameraRoll && GalleryUtils.isCameraAvailable(mActivity)) {
    466                 mSetPathString = "/combo/item/{" + FilterSource.FILTER_CAMERA_SHORTCUT +
    467                         "," + mSetPathString + "}";
    468                 mCurrentIndex++;
    469                 mHasCameraScreennailOrPlaceholder = true;
    470             }
    471 
    472             MediaSet originalSet = mActivity.getDataManager()
    473                     .getMediaSet(mSetPathString);
    474             if (mHasCameraScreennailOrPlaceholder && originalSet instanceof ComboAlbum) {
    475                 // Use the name of the camera album rather than the default
    476                 // ComboAlbum behavior
    477                 ((ComboAlbum) originalSet).useNameOfChild(1);
    478             }
    479             mSelectionManager.setSourceMediaSet(originalSet);
    480             mSetPathString = "/filter/delete/{" + mSetPathString + "}";
    481             mMediaSet = (FilterDeleteSet) mActivity.getDataManager()
    482                     .getMediaSet(mSetPathString);
    483             if (mMediaSet == null) {
    484                 Log.w(TAG, "failed to restore " + mSetPathString);
    485             }
    486             if (itemPath == null) {
    487                 int mediaItemCount = mMediaSet.getMediaItemCount();
    488                 if (mediaItemCount > 0) {
    489                     if (mCurrentIndex >= mediaItemCount) mCurrentIndex = 0;
    490                     itemPath = mMediaSet.getMediaItem(mCurrentIndex, 1)
    491                         .get(0).getPath();
    492                 } else {
    493                     // Bail out, PhotoPage can't load on an empty album
    494                     return;
    495                 }
    496             }
    497             PhotoDataAdapter pda = new PhotoDataAdapter(
    498                     mActivity, mPhotoView, mMediaSet, itemPath, mCurrentIndex,
    499                     mAppBridge == null ? -1 : 0,
    500                     mAppBridge == null ? false : mAppBridge.isPanorama(),
    501                     mAppBridge == null ? false : mAppBridge.isStaticCamera());
    502             mModel = pda;
    503             mPhotoView.setModel(mModel);
    504 
    505             pda.setDataListener(new PhotoDataAdapter.DataListener() {
    506 
    507                 @Override
    508                 public void onPhotoChanged(int index, Path item) {
    509                     int oldIndex = mCurrentIndex;
    510                     mCurrentIndex = index;
    511 
    512                     if (mHasCameraScreennailOrPlaceholder) {
    513                         if (mCurrentIndex > 0) {
    514                             mSkipUpdateCurrentPhoto = false;
    515                         }
    516 
    517                         if (oldIndex == 0 && mCurrentIndex > 0
    518                                 && !mPhotoView.getFilmMode()) {
    519                             mPhotoView.setFilmMode(true);
    520                             if (mAppBridge != null) {
    521                                 UsageStatistics.onEvent("CameraToFilmstrip",
    522                                         UsageStatistics.TRANSITION_SWIPE, null);
    523                             }
    524                         } else if (oldIndex == 2 && mCurrentIndex == 1) {
    525                             mCameraSwitchCutoff = SystemClock.uptimeMillis() +
    526                                     CAMERA_SWITCH_CUTOFF_THRESHOLD_MS;
    527                             mPhotoView.stopScrolling();
    528                         } else if (oldIndex >= 1 && mCurrentIndex == 0) {
    529                             mPhotoView.setWantPictureCenterCallbacks(true);
    530                             mSkipUpdateCurrentPhoto = true;
    531                         }
    532                     }
    533                     if (!mSkipUpdateCurrentPhoto) {
    534                         if (item != null) {
    535                             MediaItem photo = mModel.getMediaItem(0);
    536                             if (photo != null) updateCurrentPhoto(photo);
    537                         }
    538                         updateBars();
    539                     }
    540                     // Reset the timeout for the bars after a swipe
    541                     refreshHidingMessage();
    542                 }
    543 
    544                 @Override
    545                 public void onLoadingFinished(boolean loadingFailed) {
    546                     if (!mModel.isEmpty()) {
    547                         MediaItem photo = mModel.getMediaItem(0);
    548                         if (photo != null) updateCurrentPhoto(photo);
    549                     } else if (mIsActive) {
    550                         // We only want to finish the PhotoPage if there is no
    551                         // deletion that the user can undo.
    552                         if (mMediaSet.getNumberOfDeletions() == 0) {
    553                             mActivity.getStateManager().finishState(
    554                                     PhotoPage.this);
    555                         }
    556                     }
    557                 }
    558 
    559                 @Override
    560                 public void onLoadingStarted() {
    561                 }
    562             });
    563         } else {
    564             // Get default media set by the URI
    565             MediaItem mediaItem = (MediaItem)
    566                     mActivity.getDataManager().getMediaObject(itemPath);
    567             mModel = new SinglePhotoDataAdapter(mActivity, mPhotoView, mediaItem);
    568             mPhotoView.setModel(mModel);
    569             updateCurrentPhoto(mediaItem);
    570             mShowSpinner = false;
    571         }
    572 
    573         mPhotoView.setFilmMode(mStartInFilmstrip && mMediaSet.getMediaItemCount() > 1);
    574         RelativeLayout galleryRoot = (RelativeLayout) ((Activity) mActivity)
    575                 .findViewById(mAppBridge != null ? R.id.content : R.id.gallery_root);
    576         if (galleryRoot != null) {
    577             if (mSecureAlbum == null) {
    578                 mBottomControls = new PhotoPageBottomControls(this, mActivity, galleryRoot);
    579             }
    580             StitchingProgressManager progressManager = mApplication.getStitchingProgressManager();
    581             if (progressManager != null) {
    582                 mProgressBar = new PhotoPageProgressBar(mActivity, galleryRoot);
    583                 mProgressListener = new UpdateProgressListener();
    584                 progressManager.addChangeListener(mProgressListener);
    585                 if (mSecureAlbum != null) {
    586                     progressManager.addChangeListener(mSecureAlbum);
    587                 }
    588             }
    589         }
    590     }
    591 
    592     @Override
    593     public void onPictureCenter(boolean isCamera) {
    594         isCamera = isCamera || (mHasCameraScreennailOrPlaceholder && mAppBridge == null);
    595         mPhotoView.setWantPictureCenterCallbacks(false);
    596         mHandler.removeMessages(MSG_ON_CAMERA_CENTER);
    597         mHandler.removeMessages(MSG_ON_PICTURE_CENTER);
    598         mHandler.sendEmptyMessage(isCamera ? MSG_ON_CAMERA_CENTER : MSG_ON_PICTURE_CENTER);
    599     }
    600 
    601     @Override
    602     public boolean canDisplayBottomControls() {
    603         return mIsActive && !mPhotoView.canUndo();
    604     }
    605 
    606     @Override
    607     public boolean canDisplayBottomControl(int control) {
    608         if (mCurrentPhoto == null) {
    609             return false;
    610         }
    611         switch(control) {
    612             case R.id.photopage_bottom_control_edit:
    613                 return mHaveImageEditor && mShowBars
    614                         && !mPhotoView.getFilmMode()
    615                         && (mCurrentPhoto.getSupportedOperations() & MediaItem.SUPPORT_EDIT) != 0
    616                         && mCurrentPhoto.getMediaType() == MediaObject.MEDIA_TYPE_IMAGE;
    617             case R.id.photopage_bottom_control_panorama:
    618                 return mIsPanorama;
    619             case R.id.photopage_bottom_control_tiny_planet:
    620                 return mHaveImageEditor && mShowBars
    621                         && mIsPanorama360 && !mPhotoView.getFilmMode();
    622             default:
    623                 return false;
    624         }
    625     }
    626 
    627     @Override
    628     public void onBottomControlClicked(int control) {
    629         switch(control) {
    630             case R.id.photopage_bottom_control_edit:
    631                 launchPhotoEditor();
    632                 return;
    633             case R.id.photopage_bottom_control_panorama:
    634                 mActivity.getPanoramaViewHelper()
    635                         .showPanorama(mCurrentPhoto.getContentUri());
    636                 return;
    637             case R.id.photopage_bottom_control_tiny_planet:
    638                 launchTinyPlanet();
    639                 return;
    640             default:
    641                 return;
    642         }
    643     }
    644 
    645     @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
    646     private void setupNfcBeamPush() {
    647         if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS) return;
    648 
    649         NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mActivity);
    650         if (adapter != null) {
    651             adapter.setBeamPushUris(null, mActivity);
    652             adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
    653                 @Override
    654                 public Uri[] createBeamUris(NfcEvent event) {
    655                     return mNfcPushUris;
    656                 }
    657             }, mActivity);
    658         }
    659     }
    660 
    661     private void setNfcBeamPushUri(Uri uri) {
    662         mNfcPushUris[0] = uri;
    663     }
    664 
    665     private static Intent createShareIntent(MediaObject mediaObject) {
    666         int type = mediaObject.getMediaType();
    667         return new Intent(Intent.ACTION_SEND)
    668                 .setType(MenuExecutor.getMimeType(type))
    669                 .putExtra(Intent.EXTRA_STREAM, mediaObject.getContentUri())
    670                 .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    671     }
    672 
    673     private static Intent createSharePanoramaIntent(Uri contentUri) {
    674         return new Intent(Intent.ACTION_SEND)
    675                 .setType(GalleryUtils.MIME_TYPE_PANORAMA360)
    676                 .putExtra(Intent.EXTRA_STREAM, contentUri)
    677                 .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    678     }
    679 
    680     private void overrideTransitionToEditor() {
    681         ((Activity) mActivity).overridePendingTransition(android.R.anim.fade_in,
    682                 android.R.anim.fade_out);
    683     }
    684 
    685     private void launchTinyPlanet() {
    686         // Deep link into tiny planet
    687         MediaItem current = mModel.getMediaItem(0);
    688         Intent intent = new Intent(FilterShowActivity.TINY_PLANET_ACTION);
    689         intent.setClass(mActivity, FilterShowActivity.class);
    690         intent.setDataAndType(current.getContentUri(), current.getMimeType())
    691             .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    692         intent.putExtra(FilterShowActivity.LAUNCH_FULLSCREEN,
    693                 mActivity.isFullscreen());
    694         mActivity.startActivityForResult(intent, REQUEST_EDIT);
    695         overrideTransitionToEditor();
    696     }
    697 
    698     private void launchCamera() {
    699         Intent intent = new Intent(mActivity, CameraActivity.class)
    700             .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    701         mRecenterCameraOnResume = false;
    702         mActivity.startActivity(intent);
    703     }
    704 
    705     private void launchPhotoEditor() {
    706         MediaItem current = mModel.getMediaItem(0);
    707         if (current == null || (current.getSupportedOperations()
    708                 & MediaObject.SUPPORT_EDIT) == 0) {
    709             return;
    710         }
    711 
    712         Intent intent = new Intent(ACTION_NEXTGEN_EDIT);
    713 
    714         intent.setDataAndType(current.getContentUri(), current.getMimeType())
    715                 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    716         if (mActivity.getPackageManager()
    717                 .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() == 0) {
    718             intent.setAction(Intent.ACTION_EDIT);
    719         }
    720         intent.putExtra(FilterShowActivity.LAUNCH_FULLSCREEN,
    721                 mActivity.isFullscreen());
    722         ((Activity) mActivity).startActivityForResult(Intent.createChooser(intent, null),
    723                 REQUEST_EDIT);
    724         overrideTransitionToEditor();
    725     }
    726 
    727     private void launchSimpleEditor() {
    728         MediaItem current = mModel.getMediaItem(0);
    729         if (current == null || (current.getSupportedOperations()
    730                 & MediaObject.SUPPORT_EDIT) == 0) {
    731             return;
    732         }
    733 
    734         Intent intent = new Intent(ACTION_SIMPLE_EDIT);
    735 
    736         intent.setDataAndType(current.getContentUri(), current.getMimeType())
    737                 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    738         if (mActivity.getPackageManager()
    739                 .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() == 0) {
    740             intent.setAction(Intent.ACTION_EDIT);
    741         }
    742         intent.putExtra(FilterShowActivity.LAUNCH_FULLSCREEN,
    743                 mActivity.isFullscreen());
    744         ((Activity) mActivity).startActivityForResult(Intent.createChooser(intent, null),
    745                 REQUEST_EDIT);
    746         overrideTransitionToEditor();
    747     }
    748 
    749     private void requestDeferredUpdate() {
    750         mDeferUpdateUntil = SystemClock.uptimeMillis() + DEFERRED_UPDATE_MS;
    751         if (!mDeferredUpdateWaiting) {
    752             mDeferredUpdateWaiting = true;
    753             mHandler.sendEmptyMessageDelayed(MSG_UPDATE_DEFERRED, DEFERRED_UPDATE_MS);
    754         }
    755     }
    756 
    757     private void updateUIForCurrentPhoto() {
    758         if (mCurrentPhoto == null) return;
    759 
    760         // If by swiping or deletion the user ends up on an action item
    761         // and zoomed in, zoom out so that the context of the action is
    762         // more clear
    763         if ((mCurrentPhoto.getSupportedOperations() & MediaObject.SUPPORT_ACTION) != 0
    764                 && !mPhotoView.getFilmMode()) {
    765             mPhotoView.setWantPictureCenterCallbacks(true);
    766         }
    767 
    768         updateMenuOperations();
    769         refreshBottomControlsWhenReady();
    770         if (mShowDetails) {
    771             mDetailsHelper.reloadDetails();
    772         }
    773         if ((mSecureAlbum == null)
    774                 && (mCurrentPhoto.getSupportedOperations() & MediaItem.SUPPORT_SHARE) != 0) {
    775             mCurrentPhoto.getPanoramaSupport(mUpdateShareURICallback);
    776         }
    777         updateProgressBar();
    778     }
    779 
    780     private void updateCurrentPhoto(MediaItem photo) {
    781         if (mCurrentPhoto == photo) return;
    782         mCurrentPhoto = photo;
    783         if (mPhotoView.getFilmMode()) {
    784             requestDeferredUpdate();
    785         } else {
    786             updateUIForCurrentPhoto();
    787         }
    788     }
    789 
    790     private void updateProgressBar() {
    791         if (mProgressBar != null) {
    792             mProgressBar.hideProgress();
    793             StitchingProgressManager progressManager = mApplication.getStitchingProgressManager();
    794             if (progressManager != null && mCurrentPhoto instanceof LocalImage) {
    795                 Integer progress = progressManager.getProgress(mCurrentPhoto.getContentUri());
    796                 if (progress != null) {
    797                     mProgressBar.setProgress(progress);
    798                 }
    799             }
    800         }
    801     }
    802 
    803     private void updateMenuOperations() {
    804         Menu menu = mActionBar.getMenu();
    805 
    806         // it could be null if onCreateActionBar has not been called yet
    807         if (menu == null) return;
    808 
    809         MenuItem item = menu.findItem(R.id.action_slideshow);
    810         if (item != null) {
    811             item.setVisible((mSecureAlbum == null) && canDoSlideShow());
    812         }
    813         if (mCurrentPhoto == null) return;
    814 
    815         int supportedOperations = mCurrentPhoto.getSupportedOperations();
    816         if (mSecureAlbum != null) {
    817             supportedOperations &= MediaObject.SUPPORT_DELETE;
    818         } else {
    819             mCurrentPhoto.getPanoramaSupport(mUpdatePanoramaMenuItemsCallback);
    820             if (!mHaveImageEditor) {
    821                 supportedOperations &= ~MediaObject.SUPPORT_EDIT;
    822             }
    823         }
    824         MenuExecutor.updateMenuOperation(menu, supportedOperations);
    825     }
    826 
    827     private boolean canDoSlideShow() {
    828         if (mMediaSet == null || mCurrentPhoto == null) {
    829             return false;
    830         }
    831         if (mCurrentPhoto.getMediaType() != MediaObject.MEDIA_TYPE_IMAGE) {
    832             return false;
    833         }
    834         return true;
    835     }
    836 
    837     //////////////////////////////////////////////////////////////////////////
    838     //  Action Bar show/hide management
    839     //////////////////////////////////////////////////////////////////////////
    840 
    841     private void showBars() {
    842         if (mShowBars) return;
    843         mShowBars = true;
    844         mOrientationManager.unlockOrientation();
    845         mActionBar.show();
    846         mActivity.getGLRoot().setLightsOutMode(false);
    847         refreshHidingMessage();
    848         refreshBottomControlsWhenReady();
    849     }
    850 
    851     private void hideBars() {
    852         if (!mShowBars) return;
    853         mShowBars = false;
    854         mActionBar.hide();
    855         mActivity.getGLRoot().setLightsOutMode(true);
    856         mHandler.removeMessages(MSG_HIDE_BARS);
    857         refreshBottomControlsWhenReady();
    858     }
    859 
    860     private void refreshHidingMessage() {
    861         mHandler.removeMessages(MSG_HIDE_BARS);
    862         if (!mIsMenuVisible && !mPhotoView.getFilmMode()) {
    863             mHandler.sendEmptyMessageDelayed(MSG_HIDE_BARS, HIDE_BARS_TIMEOUT);
    864         }
    865     }
    866 
    867     private boolean canShowBars() {
    868         // No bars if we are showing camera preview.
    869         if (mAppBridge != null && mCurrentIndex == 0
    870                 && !mPhotoView.getFilmMode()) return false;
    871 
    872         // No bars if it's not allowed.
    873         if (!mActionBarAllowed) return false;
    874 
    875         Configuration config = mActivity.getResources().getConfiguration();
    876         if (config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH) {
    877             return false;
    878         }
    879 
    880         return true;
    881     }
    882 
    883     private void wantBars() {
    884         if (canShowBars()) showBars();
    885     }
    886 
    887     private void toggleBars() {
    888         if (mShowBars) {
    889             hideBars();
    890         } else {
    891             if (canShowBars()) showBars();
    892         }
    893     }
    894 
    895     private void updateBars() {
    896         if (!canShowBars()) {
    897             hideBars();
    898         }
    899     }
    900 
    901     @Override
    902     protected void onBackPressed() {
    903         if (mShowDetails) {
    904             hideDetails();
    905         } else if (mAppBridge == null || !switchWithCaptureAnimation(-1)) {
    906             // We are leaving this page. Set the result now.
    907             setResult();
    908             if (mStartInFilmstrip && !mPhotoView.getFilmMode()) {
    909                 mPhotoView.setFilmMode(true);
    910             } else if (mTreatBackAsUp) {
    911                 onUpPressed();
    912             } else {
    913                 super.onBackPressed();
    914             }
    915         }
    916     }
    917 
    918     private void onUpPressed() {
    919         if ((mStartInFilmstrip || mAppBridge != null)
    920                 && !mPhotoView.getFilmMode()) {
    921             mPhotoView.setFilmMode(true);
    922             return;
    923         }
    924 
    925         if (mActivity.getStateManager().getStateCount() > 1) {
    926             setResult();
    927             super.onBackPressed();
    928             return;
    929         }
    930 
    931         if (mOriginalSetPathString == null) return;
    932 
    933         if (mAppBridge == null) {
    934             // We're in view mode so set up the stacks on our own.
    935             Bundle data = new Bundle(getData());
    936             data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString);
    937             data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH,
    938                     mActivity.getDataManager().getTopSetPath(
    939                             DataManager.INCLUDE_ALL));
    940             mActivity.getStateManager().switchState(this, AlbumPage.class, data);
    941         } else {
    942             GalleryUtils.startGalleryActivity(mActivity);
    943         }
    944     }
    945 
    946     private void setResult() {
    947         Intent result = null;
    948         result = new Intent();
    949         result.putExtra(KEY_RETURN_INDEX_HINT, mCurrentIndex);
    950         setStateResult(Activity.RESULT_OK, result);
    951     }
    952 
    953     //////////////////////////////////////////////////////////////////////////
    954     //  AppBridge.Server interface
    955     //////////////////////////////////////////////////////////////////////////
    956 
    957     @Override
    958     public void setCameraRelativeFrame(Rect frame) {
    959         mPhotoView.setCameraRelativeFrame(frame);
    960     }
    961 
    962     @Override
    963     public boolean switchWithCaptureAnimation(int offset) {
    964         return mPhotoView.switchWithCaptureAnimation(offset);
    965     }
    966 
    967     @Override
    968     public void setSwipingEnabled(boolean enabled) {
    969         mPhotoView.setSwipingEnabled(enabled);
    970     }
    971 
    972     @Override
    973     public void notifyScreenNailChanged() {
    974         mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail());
    975         mScreenNailSet.notifyChange();
    976     }
    977 
    978     @Override
    979     public void addSecureAlbumItem(boolean isVideo, int id) {
    980         mSecureAlbum.addMediaItem(isVideo, id);
    981     }
    982 
    983     @Override
    984     protected boolean onCreateActionBar(Menu menu) {
    985         mActionBar.createActionBarMenu(R.menu.photo, menu);
    986         mHaveImageEditor = GalleryUtils.isEditorAvailable(mActivity, "image/*");
    987         updateMenuOperations();
    988         mActionBar.setTitle(mMediaSet != null ? mMediaSet.getName() : "");
    989         return true;
    990     }
    991 
    992     private MenuExecutor.ProgressListener mConfirmDialogListener =
    993             new MenuExecutor.ProgressListener() {
    994         @Override
    995         public void onProgressUpdate(int index) {}
    996 
    997         @Override
    998         public void onProgressComplete(int result) {}
    999 
   1000         @Override
   1001         public void onConfirmDialogShown() {
   1002             mHandler.removeMessages(MSG_HIDE_BARS);
   1003         }
   1004 
   1005         @Override
   1006         public void onConfirmDialogDismissed(boolean confirmed) {
   1007             refreshHidingMessage();
   1008         }
   1009 
   1010         @Override
   1011         public void onProgressStart() {}
   1012     };
   1013 
   1014     private void switchToGrid() {
   1015         if (mActivity.getStateManager().hasStateClass(AlbumPage.class)) {
   1016             onUpPressed();
   1017         } else {
   1018             if (mOriginalSetPathString == null) return;
   1019             if (mProgressBar != null) {
   1020                 updateCurrentPhoto(null);
   1021                 mProgressBar.hideProgress();
   1022             }
   1023             Bundle data = new Bundle(getData());
   1024             data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString);
   1025             data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH,
   1026                     mActivity.getDataManager().getTopSetPath(
   1027                             DataManager.INCLUDE_ALL));
   1028 
   1029             // We only show cluster menu in the first AlbumPage in stack
   1030             // TODO: Enable this when running from the camera app
   1031             boolean inAlbum = mActivity.getStateManager().hasStateClass(AlbumPage.class);
   1032             data.putBoolean(AlbumPage.KEY_SHOW_CLUSTER_MENU, !inAlbum
   1033                     && mAppBridge == null);
   1034 
   1035             data.putBoolean(PhotoPage.KEY_APP_BRIDGE, mAppBridge != null);
   1036 
   1037             // Account for live preview being first item
   1038             mActivity.getTransitionStore().put(KEY_RETURN_INDEX_HINT,
   1039                     mAppBridge != null ? mCurrentIndex - 1 : mCurrentIndex);
   1040 
   1041             if (mHasCameraScreennailOrPlaceholder && mAppBridge != null) {
   1042                 mActivity.getStateManager().startState(AlbumPage.class, data);
   1043             } else {
   1044                 mActivity.getStateManager().switchState(this, AlbumPage.class, data);
   1045             }
   1046         }
   1047     }
   1048 
   1049     @Override
   1050     protected boolean onItemSelected(MenuItem item) {
   1051         if (mModel == null) return true;
   1052         refreshHidingMessage();
   1053         MediaItem current = mModel.getMediaItem(0);
   1054 
   1055         // This is a shield for monkey when it clicks the action bar
   1056         // menu when transitioning from filmstrip to camera
   1057         if (current instanceof SnailItem) return true;
   1058         // TODO: We should check the current photo against the MediaItem
   1059         // that the menu was initially created for. We need to fix this
   1060         // after PhotoPage being refactored.
   1061         if (current == null) {
   1062             // item is not ready, ignore
   1063             return true;
   1064         }
   1065         int currentIndex = mModel.getCurrentIndex();
   1066         Path path = current.getPath();
   1067 
   1068         DataManager manager = mActivity.getDataManager();
   1069         int action = item.getItemId();
   1070         String confirmMsg = null;
   1071         switch (action) {
   1072             case android.R.id.home: {
   1073                 onUpPressed();
   1074                 return true;
   1075             }
   1076             case R.id.action_slideshow: {
   1077                 Bundle data = new Bundle();
   1078                 data.putString(SlideshowPage.KEY_SET_PATH, mMediaSet.getPath().toString());
   1079                 data.putString(SlideshowPage.KEY_ITEM_PATH, path.toString());
   1080                 data.putInt(SlideshowPage.KEY_PHOTO_INDEX, currentIndex);
   1081                 data.putBoolean(SlideshowPage.KEY_REPEAT, true);
   1082                 mActivity.getStateManager().startStateForResult(
   1083                         SlideshowPage.class, REQUEST_SLIDESHOW, data);
   1084                 return true;
   1085             }
   1086             case R.id.action_crop: {
   1087                 Activity activity = mActivity;
   1088                 Intent intent = new Intent(CropActivity.CROP_ACTION);
   1089                 intent.setClass(activity, CropActivity.class);
   1090                 intent.setDataAndType(manager.getContentUri(path), current.getMimeType())
   1091                     .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
   1092                 activity.startActivityForResult(intent, PicasaSource.isPicasaImage(current)
   1093                         ? REQUEST_CROP_PICASA
   1094                         : REQUEST_CROP);
   1095                 return true;
   1096             }
   1097             case R.id.action_trim: {
   1098                 Intent intent = new Intent(mActivity, TrimVideo.class);
   1099                 intent.setData(manager.getContentUri(path));
   1100                 // We need the file path to wrap this into a RandomAccessFile.
   1101                 intent.putExtra(KEY_MEDIA_ITEM_PATH, current.getFilePath());
   1102                 mActivity.startActivityForResult(intent, REQUEST_TRIM);
   1103                 return true;
   1104             }
   1105             case R.id.action_mute: {
   1106                 MuteVideo muteVideo = new MuteVideo(current.getFilePath(),
   1107                         manager.getContentUri(path), mActivity);
   1108                 muteVideo.muteInBackground();
   1109                 return true;
   1110             }
   1111             case R.id.action_edit: {
   1112                 launchPhotoEditor();
   1113                 return true;
   1114             }
   1115             case R.id.action_simple_edit: {
   1116                 launchSimpleEditor();
   1117                 return true;
   1118             }
   1119             case R.id.action_details: {
   1120                 if (mShowDetails) {
   1121                     hideDetails();
   1122                 } else {
   1123                     showDetails();
   1124                 }
   1125                 return true;
   1126             }
   1127             case R.id.action_delete:
   1128                 confirmMsg = mActivity.getResources().getQuantityString(
   1129                         R.plurals.delete_selection, 1);
   1130             case R.id.action_setas:
   1131             case R.id.action_rotate_ccw:
   1132             case R.id.action_rotate_cw:
   1133             case R.id.action_show_on_map:
   1134                 mSelectionManager.deSelectAll();
   1135                 mSelectionManager.toggle(path);
   1136                 mMenuExecutor.onMenuClicked(item, confirmMsg, mConfirmDialogListener);
   1137                 return true;
   1138             default :
   1139                 return false;
   1140         }
   1141     }
   1142 
   1143     private void hideDetails() {
   1144         mShowDetails = false;
   1145         mDetailsHelper.hide();
   1146     }
   1147 
   1148     private void showDetails() {
   1149         mShowDetails = true;
   1150         if (mDetailsHelper == null) {
   1151             mDetailsHelper = new DetailsHelper(mActivity, mRootPane, new MyDetailsSource());
   1152             mDetailsHelper.setCloseListener(new CloseListener() {
   1153                 @Override
   1154                 public void onClose() {
   1155                     hideDetails();
   1156                 }
   1157             });
   1158         }
   1159         mDetailsHelper.show();
   1160     }
   1161 
   1162     ////////////////////////////////////////////////////////////////////////////
   1163     //  Callbacks from PhotoView
   1164     ////////////////////////////////////////////////////////////////////////////
   1165     @Override
   1166     public void onSingleTapUp(int x, int y) {
   1167         if (mAppBridge != null) {
   1168             if (mAppBridge.onSingleTapUp(x, y)) return;
   1169         }
   1170 
   1171         MediaItem item = mModel.getMediaItem(0);
   1172         if (item == null || item == mScreenNailItem) {
   1173             // item is not ready or it is camera preview, ignore
   1174             return;
   1175         }
   1176 
   1177         int supported = item.getSupportedOperations();
   1178         boolean playVideo = ((supported & MediaItem.SUPPORT_PLAY) != 0);
   1179         boolean unlock = ((supported & MediaItem.SUPPORT_UNLOCK) != 0);
   1180         boolean goBack = ((supported & MediaItem.SUPPORT_BACK) != 0);
   1181         boolean launchCamera = ((supported & MediaItem.SUPPORT_CAMERA_SHORTCUT) != 0);
   1182 
   1183         if (playVideo) {
   1184             // determine if the point is at center (1/6) of the photo view.
   1185             // (The position of the "play" icon is at center (1/6) of the photo)
   1186             int w = mPhotoView.getWidth();
   1187             int h = mPhotoView.getHeight();
   1188             playVideo = (Math.abs(x - w / 2) * 12 <= w)
   1189                 && (Math.abs(y - h / 2) * 12 <= h);
   1190         }
   1191 
   1192         if (playVideo) {
   1193             if (mSecureAlbum == null) {
   1194                 playVideo(mActivity, item.getPlayUri(), item.getName());
   1195             } else {
   1196                 mActivity.getStateManager().finishState(this);
   1197             }
   1198         } else if (goBack) {
   1199             onBackPressed();
   1200         } else if (unlock) {
   1201             Intent intent = new Intent(mActivity, Gallery.class);
   1202             intent.putExtra(Gallery.KEY_DISMISS_KEYGUARD, true);
   1203             mActivity.startActivity(intent);
   1204         } else if (launchCamera) {
   1205             launchCamera();
   1206         } else {
   1207             toggleBars();
   1208         }
   1209     }
   1210 
   1211     @Override
   1212     public void onActionBarAllowed(boolean allowed) {
   1213         mActionBarAllowed = allowed;
   1214         mHandler.sendEmptyMessage(MSG_UPDATE_ACTION_BAR);
   1215     }
   1216 
   1217     @Override
   1218     public void onActionBarWanted() {
   1219         mHandler.sendEmptyMessage(MSG_WANT_BARS);
   1220     }
   1221 
   1222     @Override
   1223     public void onFullScreenChanged(boolean full) {
   1224         Message m = mHandler.obtainMessage(
   1225                 MSG_ON_FULL_SCREEN_CHANGED, full ? 1 : 0, 0);
   1226         m.sendToTarget();
   1227     }
   1228 
   1229     // How we do delete/undo:
   1230     //
   1231     // When the user choose to delete a media item, we just tell the
   1232     // FilterDeleteSet to hide that item. If the user choose to undo it, we
   1233     // again tell FilterDeleteSet not to hide it. If the user choose to commit
   1234     // the deletion, we then actually delete the media item.
   1235     @Override
   1236     public void onDeleteImage(Path path, int offset) {
   1237         onCommitDeleteImage();  // commit the previous deletion
   1238         mDeletePath = path;
   1239         mDeleteIsFocus = (offset == 0);
   1240         mMediaSet.addDeletion(path, mCurrentIndex + offset);
   1241     }
   1242 
   1243     @Override
   1244     public void onUndoDeleteImage() {
   1245         if (mDeletePath == null) return;
   1246         // If the deletion was done on the focused item, we want the model to
   1247         // focus on it when it is undeleted.
   1248         if (mDeleteIsFocus) mModel.setFocusHintPath(mDeletePath);
   1249         mMediaSet.removeDeletion(mDeletePath);
   1250         mDeletePath = null;
   1251     }
   1252 
   1253     @Override
   1254     public void onCommitDeleteImage() {
   1255         if (mDeletePath == null) return;
   1256         mMenuExecutor.startSingleItemAction(R.id.action_delete, mDeletePath);
   1257         mDeletePath = null;
   1258     }
   1259 
   1260     public void playVideo(Activity activity, Uri uri, String title) {
   1261         try {
   1262             Intent intent = new Intent(Intent.ACTION_VIEW)
   1263                     .setDataAndType(uri, "video/*")
   1264                     .putExtra(Intent.EXTRA_TITLE, title)
   1265                     .putExtra(MovieActivity.KEY_TREAT_UP_AS_BACK, true);
   1266             activity.startActivityForResult(intent, REQUEST_PLAY_VIDEO);
   1267         } catch (ActivityNotFoundException e) {
   1268             Toast.makeText(activity, activity.getString(R.string.video_err),
   1269                     Toast.LENGTH_SHORT).show();
   1270         }
   1271     }
   1272 
   1273     private void setCurrentPhotoByIntent(Intent intent) {
   1274         if (intent == null) return;
   1275         Path path = mApplication.getDataManager()
   1276                 .findPathByUri(intent.getData(), intent.getType());
   1277         if (path != null) {
   1278             Path albumPath = mApplication.getDataManager().getDefaultSetOf(path);
   1279             if (!albumPath.equalsIgnoreCase(mOriginalSetPathString)) {
   1280                 // If the edited image is stored in a different album, we need
   1281                 // to start a new activity state to show the new image
   1282                 Bundle data = new Bundle(getData());
   1283                 data.putString(KEY_MEDIA_SET_PATH, albumPath.toString());
   1284                 data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path.toString());
   1285                 mActivity.getStateManager().startState(SinglePhotoPage.class, data);
   1286                 return;
   1287             }
   1288             mModel.setCurrentPhoto(path, mCurrentIndex);
   1289         }
   1290     }
   1291 
   1292     @Override
   1293     protected void onStateResult(int requestCode, int resultCode, Intent data) {
   1294         if (resultCode == Activity.RESULT_CANCELED) {
   1295             // This is a reset, not a canceled
   1296             return;
   1297         }
   1298         if (resultCode == ProxyLauncher.RESULT_USER_CANCELED) {
   1299             // Unmap reset vs. canceled
   1300             resultCode = Activity.RESULT_CANCELED;
   1301         }
   1302         mRecenterCameraOnResume = false;
   1303         switch (requestCode) {
   1304             case REQUEST_EDIT:
   1305                 setCurrentPhotoByIntent(data);
   1306                 break;
   1307             case REQUEST_CROP:
   1308                 if (resultCode == Activity.RESULT_OK) {
   1309                     setCurrentPhotoByIntent(data);
   1310                 }
   1311                 break;
   1312             case REQUEST_CROP_PICASA: {
   1313                 if (resultCode == Activity.RESULT_OK) {
   1314                     Context context = mActivity.getAndroidContext();
   1315                     String message = context.getString(R.string.crop_saved,
   1316                             context.getString(R.string.folder_edited_online_photos));
   1317                     Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
   1318                 }
   1319                 break;
   1320             }
   1321             case REQUEST_SLIDESHOW: {
   1322                 if (data == null) break;
   1323                 String path = data.getStringExtra(SlideshowPage.KEY_ITEM_PATH);
   1324                 int index = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0);
   1325                 if (path != null) {
   1326                     mModel.setCurrentPhoto(Path.fromString(path), index);
   1327                 }
   1328             }
   1329         }
   1330     }
   1331 
   1332     @Override
   1333     public void onPause() {
   1334         super.onPause();
   1335         mIsActive = false;
   1336 
   1337         mActivity.getGLRoot().unfreeze();
   1338         mHandler.removeMessages(MSG_UNFREEZE_GLROOT);
   1339 
   1340         DetailsHelper.pause();
   1341         // Hide the detail dialog on exit
   1342         if (mShowDetails) hideDetails();
   1343         if (mModel != null) {
   1344             mModel.pause();
   1345         }
   1346         mPhotoView.pause();
   1347         mHandler.removeMessages(MSG_HIDE_BARS);
   1348         mHandler.removeMessages(MSG_REFRESH_BOTTOM_CONTROLS);
   1349         refreshBottomControlsWhenReady();
   1350         mActionBar.removeOnMenuVisibilityListener(mMenuVisibilityListener);
   1351         if (mShowSpinner) {
   1352             mActionBar.disableAlbumModeMenu(true);
   1353         }
   1354         onCommitDeleteImage();
   1355         mMenuExecutor.pause();
   1356         if (mMediaSet != null) mMediaSet.clearDeletion();
   1357     }
   1358 
   1359     @Override
   1360     public void onCurrentImageUpdated() {
   1361         mActivity.getGLRoot().unfreeze();
   1362     }
   1363 
   1364     @Override
   1365     public void onFilmModeChanged(boolean enabled) {
   1366         refreshBottomControlsWhenReady();
   1367         if (mShowSpinner) {
   1368             if (enabled) {
   1369                 mActionBar.enableAlbumModeMenu(
   1370                         GalleryActionBar.ALBUM_FILMSTRIP_MODE_SELECTED, this);
   1371             } else {
   1372                 mActionBar.disableAlbumModeMenu(true);
   1373             }
   1374         }
   1375         if (enabled) {
   1376             mHandler.removeMessages(MSG_HIDE_BARS);
   1377             UsageStatistics.onContentViewChanged(
   1378                     UsageStatistics.COMPONENT_GALLERY, "FilmstripPage");
   1379         } else {
   1380             refreshHidingMessage();
   1381             if (mAppBridge == null || mCurrentIndex > 0) {
   1382                 UsageStatistics.onContentViewChanged(
   1383                         UsageStatistics.COMPONENT_GALLERY, "SinglePhotoPage");
   1384             } else {
   1385                 UsageStatistics.onContentViewChanged(
   1386                         UsageStatistics.COMPONENT_CAMERA, "Unknown"); // TODO
   1387             }
   1388         }
   1389     }
   1390 
   1391     private void transitionFromAlbumPageIfNeeded() {
   1392         TransitionStore transitions = mActivity.getTransitionStore();
   1393 
   1394         int albumPageTransition = transitions.get(
   1395                 KEY_ALBUMPAGE_TRANSITION, MSG_ALBUMPAGE_NONE);
   1396 
   1397         if (albumPageTransition == MSG_ALBUMPAGE_NONE && mAppBridge != null
   1398                 && mRecenterCameraOnResume) {
   1399             // Generally, resuming the PhotoPage when in Camera should
   1400             // reset to the capture mode to allow quick photo taking
   1401             mCurrentIndex = 0;
   1402             mPhotoView.resetToFirstPicture();
   1403         } else {
   1404             int resumeIndex = transitions.get(KEY_INDEX_HINT, -1);
   1405             if (resumeIndex >= 0) {
   1406                 if (mHasCameraScreennailOrPlaceholder) {
   1407                     // Account for preview/placeholder being the first item
   1408                     resumeIndex++;
   1409                 }
   1410                 if (resumeIndex < mMediaSet.getMediaItemCount()) {
   1411                     mCurrentIndex = resumeIndex;
   1412                     mModel.moveTo(mCurrentIndex);
   1413                 }
   1414             }
   1415         }
   1416 
   1417         if (albumPageTransition == MSG_ALBUMPAGE_RESUMED) {
   1418             mPhotoView.setFilmMode(mStartInFilmstrip || mAppBridge != null);
   1419         } else if (albumPageTransition == MSG_ALBUMPAGE_PICKED) {
   1420             mPhotoView.setFilmMode(false);
   1421         }
   1422     }
   1423 
   1424     @Override
   1425     protected void onResume() {
   1426         super.onResume();
   1427 
   1428         if (mModel == null) {
   1429             mActivity.getStateManager().finishState(this);
   1430             return;
   1431         }
   1432         transitionFromAlbumPageIfNeeded();
   1433 
   1434         mActivity.getGLRoot().freeze();
   1435         mIsActive = true;
   1436         setContentPane(mRootPane);
   1437 
   1438         mModel.resume();
   1439         mPhotoView.resume();
   1440         mActionBar.setDisplayOptions(
   1441                 ((mSecureAlbum == null) && (mSetPathString != null)), false);
   1442         mActionBar.addOnMenuVisibilityListener(mMenuVisibilityListener);
   1443         refreshBottomControlsWhenReady();
   1444         if (mShowSpinner && mPhotoView.getFilmMode()) {
   1445             mActionBar.enableAlbumModeMenu(
   1446                     GalleryActionBar.ALBUM_FILMSTRIP_MODE_SELECTED, this);
   1447         }
   1448         if (!mShowBars) {
   1449             mActionBar.hide();
   1450             mActivity.getGLRoot().setLightsOutMode(true);
   1451         }
   1452         boolean haveImageEditor = GalleryUtils.isEditorAvailable(mActivity, "image/*");
   1453         if (haveImageEditor != mHaveImageEditor) {
   1454             mHaveImageEditor = haveImageEditor;
   1455             updateMenuOperations();
   1456         }
   1457 
   1458         mRecenterCameraOnResume = true;
   1459         mHandler.sendEmptyMessageDelayed(MSG_UNFREEZE_GLROOT, UNFREEZE_GLROOT_TIMEOUT);
   1460     }
   1461 
   1462     @Override
   1463     protected void onDestroy() {
   1464         if (mAppBridge != null) {
   1465             mAppBridge.setServer(null);
   1466             mScreenNailItem.setScreenNail(null);
   1467             mAppBridge.detachScreenNail();
   1468             mAppBridge = null;
   1469             mScreenNailSet = null;
   1470             mScreenNailItem = null;
   1471         }
   1472         mActivity.getGLRoot().setOrientationSource(null);
   1473         if (mBottomControls != null) mBottomControls.cleanup();
   1474 
   1475         // Remove all pending messages.
   1476         mHandler.removeCallbacksAndMessages(null);
   1477         super.onDestroy();
   1478     }
   1479 
   1480     private class MyDetailsSource implements DetailsSource {
   1481 
   1482         @Override
   1483         public MediaDetails getDetails() {
   1484             return mModel.getMediaItem(0).getDetails();
   1485         }
   1486 
   1487         @Override
   1488         public int size() {
   1489             return mMediaSet != null ? mMediaSet.getMediaItemCount() : 1;
   1490         }
   1491 
   1492         @Override
   1493         public int setIndex() {
   1494             return mModel.getCurrentIndex();
   1495         }
   1496     }
   1497 
   1498     @Override
   1499     public void onAlbumModeSelected(int mode) {
   1500         if (mode == GalleryActionBar.ALBUM_GRID_MODE_SELECTED) {
   1501             switchToGrid();
   1502         }
   1503     }
   1504 
   1505     @Override
   1506     public void refreshBottomControlsWhenReady() {
   1507         if (mBottomControls == null) {
   1508             return;
   1509         }
   1510         MediaObject currentPhoto = mCurrentPhoto;
   1511         if (currentPhoto == null) {
   1512             mHandler.obtainMessage(MSG_REFRESH_BOTTOM_CONTROLS, 0, 0, currentPhoto).sendToTarget();
   1513         } else {
   1514             currentPhoto.getPanoramaSupport(mRefreshBottomControlsCallback);
   1515         }
   1516     }
   1517 
   1518     private void updatePanoramaUI(boolean isPanorama360) {
   1519         Menu menu = mActionBar.getMenu();
   1520 
   1521         // it could be null if onCreateActionBar has not been called yet
   1522         if (menu == null) {
   1523             return;
   1524         }
   1525 
   1526         MenuExecutor.updateMenuForPanorama(menu, isPanorama360, isPanorama360);
   1527 
   1528         if (isPanorama360) {
   1529             MenuItem item = menu.findItem(R.id.action_share);
   1530             if (item != null) {
   1531                 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
   1532                 item.setTitle(mActivity.getResources().getString(R.string.share_as_photo));
   1533             }
   1534         } else if ((mCurrentPhoto.getSupportedOperations() & MediaObject.SUPPORT_SHARE) != 0) {
   1535             MenuItem item = menu.findItem(R.id.action_share);
   1536             if (item != null) {
   1537                 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
   1538                 item.setTitle(mActivity.getResources().getString(R.string.share));
   1539             }
   1540         }
   1541     }
   1542 
   1543     @Override
   1544     public void onUndoBarVisibilityChanged(boolean visible) {
   1545         refreshBottomControlsWhenReady();
   1546     }
   1547 
   1548     @Override
   1549     public boolean onShareTargetSelected(ShareActionProvider source, Intent intent) {
   1550         final long timestampMillis = mCurrentPhoto.getDateInMs();
   1551         final String mediaType = getMediaTypeString(mCurrentPhoto);
   1552         UsageStatistics.onEvent(UsageStatistics.COMPONENT_GALLERY,
   1553                 UsageStatistics.ACTION_SHARE,
   1554                 mediaType,
   1555                         timestampMillis > 0
   1556                         ? System.currentTimeMillis() - timestampMillis
   1557                         : -1);
   1558         return false;
   1559     }
   1560 
   1561     private static String getMediaTypeString(MediaItem item) {
   1562         if (item.getMediaType() == MediaObject.MEDIA_TYPE_VIDEO) {
   1563             return "Video";
   1564         } else if (item.getMediaType() == MediaObject.MEDIA_TYPE_IMAGE) {
   1565             return "Photo";
   1566         } else {
   1567             return "Unknown:" + item.getMediaType();
   1568         }
   1569     }
   1570 
   1571 }
   1572