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