OSDN Git Service

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