OSDN Git Service

c110a8ccceacf109b9d7c1f840388264abffdf19
[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.content.res.Configuration;
27 import android.graphics.Rect;
28 import android.net.Uri;
29 import android.nfc.NfcAdapter;
30 import android.nfc.NfcAdapter.CreateBeamUrisCallback;
31 import android.nfc.NfcEvent;
32 import android.os.Bundle;
33 import android.os.Handler;
34 import android.os.Message;
35 import android.os.SystemClock;
36 import android.view.Menu;
37 import android.view.MenuItem;
38 import android.widget.RelativeLayout;
39 import android.widget.Toast;
40
41 import com.android.camera.CameraActivity;
42 import com.android.camera.ProxyLauncher;
43 import com.android.gallery3d.R;
44 import com.android.gallery3d.common.ApiHelper;
45 import com.android.gallery3d.data.ComboAlbum;
46 import com.android.gallery3d.data.DataManager;
47 import com.android.gallery3d.data.FilterDeleteSet;
48 import com.android.gallery3d.data.FilterSource;
49 import com.android.gallery3d.data.LocalImage;
50 import com.android.gallery3d.data.MediaDetails;
51 import com.android.gallery3d.data.MediaItem;
52 import com.android.gallery3d.data.MediaObject;
53 import com.android.gallery3d.data.MediaObject.PanoramaSupportCallback;
54 import com.android.gallery3d.data.MediaSet;
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.MenuExecutor;
68 import com.android.gallery3d.ui.PhotoView;
69 import com.android.gallery3d.ui.SelectionManager;
70 import com.android.gallery3d.ui.SynchronizedHandler;
71 import com.android.gallery3d.util.GalleryUtils;
72
73 public abstract class PhotoPage extends ActivityState implements
74         PhotoView.Listener, AppBridge.Server,
75         PhotoPageBottomControls.Delegate, GalleryActionBar.OnAlbumModeSelectedListener {
76     private static final String TAG = "PhotoPage";
77
78     private static final int MSG_HIDE_BARS = 1;
79     private static final int MSG_ON_FULL_SCREEN_CHANGED = 4;
80     private static final int MSG_UPDATE_ACTION_BAR = 5;
81     private static final int MSG_UNFREEZE_GLROOT = 6;
82     private static final int MSG_WANT_BARS = 7;
83     private static final int MSG_REFRESH_BOTTOM_CONTROLS = 8;
84     private static final int MSG_ON_CAMERA_CENTER = 9;
85     private static final int MSG_ON_PICTURE_CENTER = 10;
86     private static final int MSG_REFRESH_IMAGE = 11;
87     private static final int MSG_UPDATE_PHOTO_UI = 12;
88     private static final int MSG_UPDATE_PROGRESS = 13;
89     private static final int MSG_UPDATE_DEFERRED = 14;
90     private static final int MSG_UPDATE_SHARE_URI = 15;
91     private static final int MSG_UPDATE_PANORAMA_UI = 16;
92
93     private static final int HIDE_BARS_TIMEOUT = 3500;
94     private static final int UNFREEZE_GLROOT_TIMEOUT = 250;
95
96     private static final int REQUEST_SLIDESHOW = 1;
97     private static final int REQUEST_CROP = 2;
98     private static final int REQUEST_CROP_PICASA = 3;
99     private static final int REQUEST_EDIT = 4;
100     private static final int REQUEST_PLAY_VIDEO = 5;
101     private static final int REQUEST_TRIM = 6;
102
103     public static final String KEY_MEDIA_SET_PATH = "media-set-path";
104     public static final String KEY_MEDIA_ITEM_PATH = "media-item-path";
105     public static final String KEY_INDEX_HINT = "index-hint";
106     public static final String KEY_OPEN_ANIMATION_RECT = "open-animation-rect";
107     public static final String KEY_APP_BRIDGE = "app-bridge";
108     public static final String KEY_TREAT_BACK_AS_UP = "treat-back-as-up";
109     public static final String KEY_START_IN_FILMSTRIP = "start-in-filmstrip";
110     public static final String KEY_RETURN_INDEX_HINT = "return-index-hint";
111     public static final String KEY_SHOW_WHEN_LOCKED = "show_when_locked";
112     public static final String KEY_IN_CAMERA_ROLL = "in_camera_roll";
113
114     public static final String KEY_ALBUMPAGE_TRANSITION = "albumpage-transition";
115     public static final int MSG_ALBUMPAGE_NONE = 0;
116     public static final int MSG_ALBUMPAGE_STARTED = 1;
117     public static final int MSG_ALBUMPAGE_RESUMED = 2;
118     public static final int MSG_ALBUMPAGE_PICKED = 4;
119
120     public static final String ACTION_NEXTGEN_EDIT = "action_nextgen_edit";
121
122     private GalleryApp mApplication;
123     private SelectionManager mSelectionManager;
124
125     private PhotoView mPhotoView;
126     private PhotoPage.Model mModel;
127     private DetailsHelper mDetailsHelper;
128     private boolean mShowDetails;
129
130     // mMediaSet could be null if there is no KEY_MEDIA_SET_PATH supplied.
131     // E.g., viewing a photo in gmail attachment
132     private FilterDeleteSet mMediaSet;
133
134     // The mediaset used by camera launched from secure lock screen.
135     private SecureAlbum mSecureAlbum;
136
137     private int mCurrentIndex = 0;
138     private Handler mHandler;
139     private boolean mShowBars = true;
140     private volatile boolean mActionBarAllowed = true;
141     private GalleryActionBar mActionBar;
142     private boolean mIsMenuVisible;
143     private boolean mHaveImageEditor;
144     private PhotoPageBottomControls mBottomControls;
145     private PhotoPageProgressBar mProgressBar;
146     private MediaItem mCurrentPhoto = null;
147     private MenuExecutor mMenuExecutor;
148     private boolean mIsActive;
149     private boolean mShowSpinner;
150     private String mSetPathString;
151     // This is the original mSetPathString before adding the camera preview item.
152     private String mOriginalSetPathString;
153     private AppBridge mAppBridge;
154     private SnailItem mScreenNailItem;
155     private SnailAlbum mScreenNailSet;
156     private OrientationManager mOrientationManager;
157     private boolean mTreatBackAsUp;
158     private boolean mStartInFilmstrip;
159     private boolean mHasCameraScreennailOrPlaceholder = false;
160     private boolean mRecenterCameraOnResume = true;
161
162     // These are only valid after the panorama callback
163     private boolean mIsPanorama;
164     private boolean mIsPanorama360;
165
166     private long mCameraSwitchCutoff = 0;
167     private boolean mSkipUpdateCurrentPhoto = false;
168     private static final long CAMERA_SWITCH_CUTOFF_THRESHOLD_MS = 300;
169
170     private static final long DEFERRED_UPDATE_MS = 250;
171     private boolean mDeferredUpdateWaiting = false;
172     private long mDeferUpdateUntil = Long.MAX_VALUE;
173
174     // The item that is deleted (but it can still be undeleted before commiting)
175     private Path mDeletePath;
176     private boolean mDeleteIsFocus;  // whether the deleted item was in focus
177
178     private Uri[] mNfcPushUris = new Uri[1];
179
180     private final MyMenuVisibilityListener mMenuVisibilityListener =
181             new MyMenuVisibilityListener();
182     private UpdateProgressListener mProgressListener;
183
184     private final PanoramaSupportCallback mUpdatePanoramaMenuItemsCallback = new PanoramaSupportCallback() {
185         @Override
186         public void panoramaInfoAvailable(MediaObject mediaObject, boolean isPanorama,
187                 boolean isPanorama360) {
188             if (mediaObject == mCurrentPhoto) {
189                 mHandler.obtainMessage(MSG_UPDATE_PANORAMA_UI, isPanorama360 ? 1 : 0, 0,
190                         mediaObject).sendToTarget();
191             }
192         }
193     };
194
195     private final PanoramaSupportCallback mRefreshBottomControlsCallback = new PanoramaSupportCallback() {
196         @Override
197         public void panoramaInfoAvailable(MediaObject mediaObject, boolean isPanorama,
198                 boolean isPanorama360) {
199             if (mediaObject == mCurrentPhoto) {
200                 mHandler.obtainMessage(MSG_REFRESH_BOTTOM_CONTROLS, isPanorama ? 1 : 0, isPanorama360 ? 1 : 0,
201                         mediaObject).sendToTarget();
202             }
203         }
204     };
205
206     private final PanoramaSupportCallback mUpdateShareURICallback = new PanoramaSupportCallback() {
207         @Override
208         public void panoramaInfoAvailable(MediaObject mediaObject, boolean isPanorama,
209                 boolean isPanorama360) {
210             if (mediaObject == mCurrentPhoto) {
211                 mHandler.obtainMessage(MSG_UPDATE_SHARE_URI, isPanorama360 ? 1 : 0, 0, mediaObject)
212                         .sendToTarget();
213             }
214         }
215     };
216
217     public static interface Model extends PhotoView.Model {
218         public void resume();
219         public void pause();
220         public boolean isEmpty();
221         public void setCurrentPhoto(Path path, int indexHint);
222     }
223
224     private class MyMenuVisibilityListener implements OnMenuVisibilityListener {
225         @Override
226         public void onMenuVisibilityChanged(boolean isVisible) {
227             mIsMenuVisible = isVisible;
228             refreshHidingMessage();
229         }
230     }
231
232     private class UpdateProgressListener implements StitchingChangeListener {
233
234         @Override
235         public void onStitchingResult(Uri uri) {
236             sendUpdate(uri, MSG_REFRESH_IMAGE);
237         }
238
239         @Override
240         public void onStitchingQueued(Uri uri) {
241             sendUpdate(uri, MSG_UPDATE_PROGRESS);
242         }
243
244         @Override
245         public void onStitchingProgress(Uri uri, final int progress) {
246             sendUpdate(uri, MSG_UPDATE_PROGRESS);
247         }
248
249         private void sendUpdate(Uri uri, int message) {
250             MediaObject currentPhoto = mCurrentPhoto;
251             boolean isCurrentPhoto = currentPhoto instanceof LocalImage
252                     && currentPhoto.getContentUri().equals(uri);
253             if (isCurrentPhoto) {
254                 mHandler.sendEmptyMessage(message);
255             }
256         }
257     };
258
259     @Override
260     protected int getBackgroundColorId() {
261         return R.color.photo_background;
262     }
263
264     private final GLView mRootPane = new GLView() {
265         @Override
266         protected void onLayout(
267                 boolean changed, int left, int top, int right, int bottom) {
268             mPhotoView.layout(0, 0, right - left, bottom - top);
269             if (mShowDetails) {
270                 mDetailsHelper.layout(left, mActionBar.getHeight(), right, bottom);
271             }
272         }
273     };
274
275     @Override
276     public void onCreate(Bundle data, Bundle restoreState) {
277         super.onCreate(data, restoreState);
278         mActionBar = mActivity.getGalleryActionBar();
279         mSelectionManager = new SelectionManager(mActivity, false);
280         mMenuExecutor = new MenuExecutor(mActivity, mSelectionManager);
281
282         mPhotoView = new PhotoView(mActivity);
283         mPhotoView.setListener(this);
284         mRootPane.addComponent(mPhotoView);
285         mApplication = (GalleryApp) ((Activity) mActivity).getApplication();
286         mOrientationManager = mActivity.getOrientationManager();
287         mActivity.getGLRoot().setOrientationSource(mOrientationManager);
288
289         mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
290             @Override
291             public void handleMessage(Message message) {
292                 switch (message.what) {
293                     case MSG_HIDE_BARS: {
294                         hideBars();
295                         break;
296                     }
297                     case MSG_REFRESH_BOTTOM_CONTROLS: {
298                         if (mCurrentPhoto == message.obj && mBottomControls != null) {
299                             mIsPanorama = message.arg1 == 1;
300                             mIsPanorama360 = message.arg2 == 1;
301                             mBottomControls.refresh();
302                         }
303                         break;
304                     }
305                     case MSG_ON_FULL_SCREEN_CHANGED: {
306                         if (mAppBridge != null) {
307                             mAppBridge.onFullScreenChanged(message.arg1 == 1);
308                         }
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 && mMediaSet.getTotalMediaItemCount() > 1) {
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         return true;
803     }
804
805     //////////////////////////////////////////////////////////////////////////
806     //  Action Bar show/hide management
807     //////////////////////////////////////////////////////////////////////////
808
809     private void showBars() {
810         if (mShowBars) return;
811         mShowBars = true;
812         mOrientationManager.unlockOrientation();
813         mActionBar.show();
814         mActivity.getGLRoot().setLightsOutMode(false);
815         refreshHidingMessage();
816         refreshBottomControlsWhenReady();
817     }
818
819     private void hideBars() {
820         if (!mShowBars) return;
821         mShowBars = false;
822         mActionBar.hide();
823         mActivity.getGLRoot().setLightsOutMode(true);
824         mHandler.removeMessages(MSG_HIDE_BARS);
825         refreshBottomControlsWhenReady();
826     }
827
828     private void refreshHidingMessage() {
829         mHandler.removeMessages(MSG_HIDE_BARS);
830         if (!mIsMenuVisible && !mPhotoView.getFilmMode()) {
831             mHandler.sendEmptyMessageDelayed(MSG_HIDE_BARS, HIDE_BARS_TIMEOUT);
832         }
833     }
834
835     private boolean canShowBars() {
836         // No bars if we are showing camera preview.
837         if (mAppBridge != null && mCurrentIndex == 0
838                 && !mPhotoView.getFilmMode()) return false;
839
840         // No bars if it's not allowed.
841         if (!mActionBarAllowed) return false;
842
843         Configuration config = mActivity.getResources().getConfiguration();
844         if (config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH) {
845             return false;
846         }
847
848         return true;
849     }
850
851     private void wantBars() {
852         if (canShowBars()) showBars();
853     }
854
855     private void toggleBars() {
856         if (mShowBars) {
857             hideBars();
858         } else {
859             if (canShowBars()) showBars();
860         }
861     }
862
863     private void updateBars() {
864         if (!canShowBars()) {
865             hideBars();
866         }
867     }
868
869     @Override
870     protected void onBackPressed() {
871         if (mShowDetails) {
872             hideDetails();
873         } else if (mAppBridge == null || !switchWithCaptureAnimation(-1)) {
874             // We are leaving this page. Set the result now.
875             setResult();
876             if (mStartInFilmstrip && !mPhotoView.getFilmMode()) {
877                 mPhotoView.setFilmMode(true);
878             } else if (mTreatBackAsUp) {
879                 onUpPressed();
880             } else {
881                 super.onBackPressed();
882             }
883         }
884     }
885
886     private void onUpPressed() {
887         if ((mStartInFilmstrip || mAppBridge != null)
888                 && !mPhotoView.getFilmMode()) {
889             mPhotoView.setFilmMode(true);
890             return;
891         }
892
893         if (mActivity.getStateManager().getStateCount() > 1) {
894             setResult();
895             super.onBackPressed();
896             return;
897         }
898
899         if (mOriginalSetPathString == null) return;
900
901         if (mAppBridge == null) {
902             // We're in view mode so set up the stacks on our own.
903             Bundle data = new Bundle(getData());
904             data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString);
905             data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH,
906                     mActivity.getDataManager().getTopSetPath(
907                             DataManager.INCLUDE_ALL));
908             mActivity.getStateManager().switchState(this, AlbumPage.class, data);
909         } else {
910             GalleryUtils.startGalleryActivity(mActivity);
911         }
912     }
913
914     private void setResult() {
915         Intent result = null;
916         result = new Intent();
917         result.putExtra(KEY_RETURN_INDEX_HINT, mCurrentIndex);
918         setStateResult(Activity.RESULT_OK, result);
919     }
920
921     //////////////////////////////////////////////////////////////////////////
922     //  AppBridge.Server interface
923     //////////////////////////////////////////////////////////////////////////
924
925     @Override
926     public void setCameraRelativeFrame(Rect frame) {
927         mPhotoView.setCameraRelativeFrame(frame);
928     }
929
930     @Override
931     public boolean switchWithCaptureAnimation(int offset) {
932         return mPhotoView.switchWithCaptureAnimation(offset);
933     }
934
935     @Override
936     public void setSwipingEnabled(boolean enabled) {
937         mPhotoView.setSwipingEnabled(enabled);
938     }
939
940     @Override
941     public void notifyScreenNailChanged() {
942         mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail());
943         mScreenNailSet.notifyChange();
944     }
945
946     @Override
947     public void addSecureAlbumItem(boolean isVideo, int id) {
948         mSecureAlbum.addMediaItem(isVideo, id);
949     }
950
951     @Override
952     protected boolean onCreateActionBar(Menu menu) {
953         mActionBar.createActionBarMenu(R.menu.photo, menu);
954         mHaveImageEditor = GalleryUtils.isEditorAvailable(mActivity, "image/*");
955         updateMenuOperations();
956         mActionBar.setTitle(mMediaSet != null ? mMediaSet.getName() : "");
957         return true;
958     }
959
960     private MenuExecutor.ProgressListener mConfirmDialogListener =
961             new MenuExecutor.ProgressListener() {
962         @Override
963         public void onProgressUpdate(int index) {}
964
965         @Override
966         public void onProgressComplete(int result) {}
967
968         @Override
969         public void onConfirmDialogShown() {
970             mHandler.removeMessages(MSG_HIDE_BARS);
971         }
972
973         @Override
974         public void onConfirmDialogDismissed(boolean confirmed) {
975             refreshHidingMessage();
976         }
977
978         @Override
979         public void onProgressStart() {}
980     };
981
982     private void switchToGrid() {
983         if (mActivity.getStateManager().hasStateClass(AlbumPage.class)) {
984             onUpPressed();
985         } else {
986             if (mOriginalSetPathString == null) return;
987             if (mProgressBar != null) {
988                 updateCurrentPhoto(null);
989                 mProgressBar.hideProgress();
990             }
991             Bundle data = new Bundle(getData());
992             data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString);
993             data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH,
994                     mActivity.getDataManager().getTopSetPath(
995                             DataManager.INCLUDE_ALL));
996
997             // We only show cluster menu in the first AlbumPage in stack
998             // TODO: Enable this when running from the camera app
999             boolean inAlbum = mActivity.getStateManager().hasStateClass(AlbumPage.class);
1000             data.putBoolean(AlbumPage.KEY_SHOW_CLUSTER_MENU, !inAlbum
1001                     && mAppBridge == null);
1002
1003             data.putBoolean(PhotoPage.KEY_APP_BRIDGE, mAppBridge != null);
1004
1005             // Account for live preview being first item
1006             mActivity.getTransitionStore().put(KEY_RETURN_INDEX_HINT,
1007                     mAppBridge != null ? mCurrentIndex - 1 : mCurrentIndex);
1008
1009             if (mHasCameraScreennailOrPlaceholder && mAppBridge != null) {
1010                 mActivity.getStateManager().startState(AlbumPage.class, data);
1011             } else {
1012                 mActivity.getStateManager().switchState(this, AlbumPage.class, data);
1013             }
1014         }
1015     }
1016
1017     @Override
1018     protected boolean onItemSelected(MenuItem item) {
1019         if (mModel == null) return true;
1020         refreshHidingMessage();
1021         MediaItem current = mModel.getMediaItem(0);
1022
1023         // This is a shield for monkey when it clicks the action bar
1024         // menu when transitioning from filmstrip to camera
1025         if (current instanceof SnailItem) return true;
1026         // TODO: We should check the current photo against the MediaItem
1027         // that the menu was initially created for. We need to fix this
1028         // after PhotoPage being refactored.
1029         if (current == null) {
1030             // item is not ready, ignore
1031             return true;
1032         }
1033         int currentIndex = mModel.getCurrentIndex();
1034         Path path = current.getPath();
1035
1036         DataManager manager = mActivity.getDataManager();
1037         int action = item.getItemId();
1038         String confirmMsg = null;
1039         switch (action) {
1040             case android.R.id.home: {
1041                 onUpPressed();
1042                 return true;
1043             }
1044             case R.id.action_slideshow: {
1045                 Bundle data = new Bundle();
1046                 data.putString(SlideshowPage.KEY_SET_PATH, mMediaSet.getPath().toString());
1047                 data.putString(SlideshowPage.KEY_ITEM_PATH, path.toString());
1048                 data.putInt(SlideshowPage.KEY_PHOTO_INDEX, currentIndex);
1049                 data.putBoolean(SlideshowPage.KEY_REPEAT, true);
1050                 mActivity.getStateManager().startStateForResult(
1051                         SlideshowPage.class, REQUEST_SLIDESHOW, data);
1052                 return true;
1053             }
1054             case R.id.action_crop: {
1055                 Activity activity = mActivity;
1056                 Intent intent = new Intent(FilterShowActivity.CROP_ACTION);
1057                 intent.setClass(activity, FilterShowActivity.class);
1058                 intent.setDataAndType(manager.getContentUri(path), current.getMimeType())
1059                     .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
1060                 activity.startActivityForResult(intent, PicasaSource.isPicasaImage(current)
1061                         ? REQUEST_CROP_PICASA
1062                         : REQUEST_CROP);
1063                 return true;
1064             }
1065             case R.id.action_trim: {
1066                 Intent intent = new Intent(mActivity, TrimVideo.class);
1067                 intent.setData(manager.getContentUri(path));
1068                 // We need the file path to wrap this into a RandomAccessFile.
1069                 intent.putExtra(KEY_MEDIA_ITEM_PATH, current.getFilePath());
1070                 mActivity.startActivityForResult(intent, REQUEST_TRIM);
1071                 return true;
1072             }
1073             case R.id.action_mute: {
1074                 MuteVideo muteVideo = new MuteVideo(current,
1075                         manager.getContentUri(path), mActivity);
1076                 muteVideo.muteInBackground();
1077                 return true;
1078             }
1079             case R.id.action_edit: {
1080                 launchPhotoEditor();
1081                 return true;
1082             }
1083             case R.id.action_details: {
1084                 if (mShowDetails) {
1085                     hideDetails();
1086                 } else {
1087                     showDetails();
1088                 }
1089                 return true;
1090             }
1091             case R.id.action_delete:
1092                 confirmMsg = mActivity.getResources().getQuantityString(
1093                         R.plurals.delete_selection, 1);
1094             case R.id.action_setas:
1095             case R.id.action_rotate_ccw:
1096             case R.id.action_rotate_cw:
1097             case R.id.action_show_on_map:
1098                 mSelectionManager.deSelectAll();
1099                 mSelectionManager.toggle(path);
1100                 mMenuExecutor.onMenuClicked(item, confirmMsg, mConfirmDialogListener);
1101                 return true;
1102             default :
1103                 return false;
1104         }
1105     }
1106
1107     private void hideDetails() {
1108         mShowDetails = false;
1109         mDetailsHelper.hide();
1110     }
1111
1112     private void showDetails() {
1113         mShowDetails = true;
1114         if (mDetailsHelper == null) {
1115             mDetailsHelper = new DetailsHelper(mActivity, mRootPane, new MyDetailsSource());
1116             mDetailsHelper.setCloseListener(new CloseListener() {
1117                 @Override
1118                 public void onClose() {
1119                     hideDetails();
1120                 }
1121             });
1122         }
1123         mDetailsHelper.show();
1124     }
1125
1126     ////////////////////////////////////////////////////////////////////////////
1127     //  Callbacks from PhotoView
1128     ////////////////////////////////////////////////////////////////////////////
1129     @Override
1130     public void onSingleTapUp(int x, int y) {
1131         if (mAppBridge != null) {
1132             if (mAppBridge.onSingleTapUp(x, y)) return;
1133         }
1134
1135         MediaItem item = mModel.getMediaItem(0);
1136         if (item == null || item == mScreenNailItem) {
1137             // item is not ready or it is camera preview, ignore
1138             return;
1139         }
1140
1141         int supported = item.getSupportedOperations();
1142         boolean playVideo = ((supported & MediaItem.SUPPORT_PLAY) != 0);
1143         boolean unlock = ((supported & MediaItem.SUPPORT_UNLOCK) != 0);
1144         boolean goBack = ((supported & MediaItem.SUPPORT_BACK) != 0);
1145         boolean launchCamera = ((supported & MediaItem.SUPPORT_CAMERA_SHORTCUT) != 0);
1146
1147         if (playVideo) {
1148             // determine if the point is at center (1/6) of the photo view.
1149             // (The position of the "play" icon is at center (1/6) of the photo)
1150             int w = mPhotoView.getWidth();
1151             int h = mPhotoView.getHeight();
1152             playVideo = (Math.abs(x - w / 2) * 12 <= w)
1153                 && (Math.abs(y - h / 2) * 12 <= h);
1154         }
1155
1156         if (playVideo) {
1157             if (mSecureAlbum == null) {
1158                 playVideo(mActivity, item.getPlayUri(), item.getName());
1159             } else {
1160                 mActivity.getStateManager().finishState(this);
1161             }
1162         } else if (goBack) {
1163             onBackPressed();
1164         } else if (unlock) {
1165             Intent intent = new Intent(mActivity, Gallery.class);
1166             intent.putExtra(Gallery.KEY_DISMISS_KEYGUARD, true);
1167             mActivity.startActivity(intent);
1168         } else if (launchCamera) {
1169             launchCamera();
1170         } else {
1171             toggleBars();
1172         }
1173     }
1174
1175     @Override
1176     public void onActionBarAllowed(boolean allowed) {
1177         mActionBarAllowed = allowed;
1178         mHandler.sendEmptyMessage(MSG_UPDATE_ACTION_BAR);
1179     }
1180
1181     @Override
1182     public void onActionBarWanted() {
1183         mHandler.sendEmptyMessage(MSG_WANT_BARS);
1184     }
1185
1186     @Override
1187     public void onFullScreenChanged(boolean full) {
1188         Message m = mHandler.obtainMessage(
1189                 MSG_ON_FULL_SCREEN_CHANGED, full ? 1 : 0, 0);
1190         m.sendToTarget();
1191     }
1192
1193     // How we do delete/undo:
1194     //
1195     // When the user choose to delete a media item, we just tell the
1196     // FilterDeleteSet to hide that item. If the user choose to undo it, we
1197     // again tell FilterDeleteSet not to hide it. If the user choose to commit
1198     // the deletion, we then actually delete the media item.
1199     @Override
1200     public void onDeleteImage(Path path, int offset) {
1201         onCommitDeleteImage();  // commit the previous deletion
1202         mDeletePath = path;
1203         mDeleteIsFocus = (offset == 0);
1204         mMediaSet.addDeletion(path, mCurrentIndex + offset);
1205     }
1206
1207     @Override
1208     public void onUndoDeleteImage() {
1209         if (mDeletePath == null) return;
1210         // If the deletion was done on the focused item, we want the model to
1211         // focus on it when it is undeleted.
1212         if (mDeleteIsFocus) mModel.setFocusHintPath(mDeletePath);
1213         mMediaSet.removeDeletion(mDeletePath);
1214         mDeletePath = null;
1215     }
1216
1217     @Override
1218     public void onCommitDeleteImage() {
1219         if (mDeletePath == null) return;
1220         mSelectionManager.deSelectAll();
1221         mSelectionManager.toggle(mDeletePath);
1222         mMenuExecutor.onMenuClicked(R.id.action_delete, null, true, false);
1223         mDeletePath = null;
1224     }
1225
1226     public void playVideo(Activity activity, Uri uri, String title) {
1227         try {
1228             Intent intent = new Intent(Intent.ACTION_VIEW)
1229                     .setDataAndType(uri, "video/*")
1230                     .putExtra(Intent.EXTRA_TITLE, title)
1231                     .putExtra(MovieActivity.KEY_TREAT_UP_AS_BACK, true);
1232             activity.startActivityForResult(intent, REQUEST_PLAY_VIDEO);
1233         } catch (ActivityNotFoundException e) {
1234             Toast.makeText(activity, activity.getString(R.string.video_err),
1235                     Toast.LENGTH_SHORT).show();
1236         }
1237     }
1238
1239     private void setCurrentPhotoByIntent(Intent intent) {
1240         if (intent == null) return;
1241         Path path = mApplication.getDataManager()
1242                 .findPathByUri(intent.getData(), intent.getType());
1243         if (path != null) {
1244             Path albumPath = mApplication.getDataManager().getDefaultSetOf(path);
1245             if (!albumPath.equalsIgnoreCase(mOriginalSetPathString)) {
1246                 // If the edited image is stored in a different album, we need
1247                 // to start a new activity state to show the new image
1248                 Bundle data = new Bundle(getData());
1249                 data.putString(KEY_MEDIA_SET_PATH, albumPath.toString());
1250                 data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path.toString());
1251                 mActivity.getStateManager().startState(SinglePhotoPage.class, data);
1252                 return;
1253             }
1254             mModel.setCurrentPhoto(path, mCurrentIndex);
1255         }
1256     }
1257
1258     @Override
1259     protected void onStateResult(int requestCode, int resultCode, Intent data) {
1260         if (resultCode == Activity.RESULT_CANCELED) {
1261             // This is a reset, not a canceled
1262             return;
1263         }
1264         if (resultCode == ProxyLauncher.RESULT_USER_CANCELED) {
1265             // Unmap reset vs. canceled
1266             resultCode = Activity.RESULT_CANCELED;
1267         }
1268         mRecenterCameraOnResume = false;
1269         switch (requestCode) {
1270             case REQUEST_EDIT:
1271                 setCurrentPhotoByIntent(data);
1272                 break;
1273             case REQUEST_CROP:
1274                 if (resultCode == Activity.RESULT_OK) {
1275                     setCurrentPhotoByIntent(data);
1276                 }
1277                 break;
1278             case REQUEST_CROP_PICASA: {
1279                 if (resultCode == Activity.RESULT_OK) {
1280                     Context context = mActivity.getAndroidContext();
1281                     String message = context.getString(R.string.crop_saved,
1282                             context.getString(R.string.folder_edited_online_photos));
1283                     Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
1284                 }
1285                 break;
1286             }
1287             case REQUEST_SLIDESHOW: {
1288                 if (data == null) break;
1289                 String path = data.getStringExtra(SlideshowPage.KEY_ITEM_PATH);
1290                 int index = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0);
1291                 if (path != null) {
1292                     mModel.setCurrentPhoto(Path.fromString(path), index);
1293                 }
1294             }
1295         }
1296     }
1297
1298     @Override
1299     public void onPause() {
1300         super.onPause();
1301         mIsActive = false;
1302
1303         mActivity.getGLRoot().unfreeze();
1304         mHandler.removeMessages(MSG_UNFREEZE_GLROOT);
1305
1306         DetailsHelper.pause();
1307         // Hide the detail dialog on exit
1308         if (mShowDetails) hideDetails();
1309         if (mModel != null) {
1310             mModel.pause();
1311         }
1312         mPhotoView.pause();
1313         mHandler.removeMessages(MSG_HIDE_BARS);
1314         mHandler.removeMessages(MSG_REFRESH_BOTTOM_CONTROLS);
1315         refreshBottomControlsWhenReady();
1316         mActionBar.removeOnMenuVisibilityListener(mMenuVisibilityListener);
1317         if (mShowSpinner) {
1318             mActionBar.disableAlbumModeMenu(true);
1319         }
1320         onCommitDeleteImage();
1321         mMenuExecutor.pause();
1322         if (mMediaSet != null) mMediaSet.clearDeletion();
1323     }
1324
1325     @Override
1326     public void onCurrentImageUpdated() {
1327         mActivity.getGLRoot().unfreeze();
1328     }
1329
1330     @Override
1331     public void onFilmModeChanged(boolean enabled) {
1332         refreshBottomControlsWhenReady();
1333         if (mShowSpinner) {
1334             if (enabled) {
1335                 mActionBar.enableAlbumModeMenu(
1336                         GalleryActionBar.ALBUM_FILMSTRIP_MODE_SELECTED, this);
1337             } else {
1338                 mActionBar.disableAlbumModeMenu(true);
1339             }
1340         }
1341         if (enabled) {
1342             mHandler.removeMessages(MSG_HIDE_BARS);
1343         } else {
1344             refreshHidingMessage();
1345         }
1346     }
1347
1348     private void transitionFromAlbumPageIfNeeded() {
1349         TransitionStore transitions = mActivity.getTransitionStore();
1350
1351         int albumPageTransition = transitions.get(
1352                 KEY_ALBUMPAGE_TRANSITION, MSG_ALBUMPAGE_NONE);
1353
1354         if (albumPageTransition == MSG_ALBUMPAGE_NONE && mAppBridge != null
1355                 && mRecenterCameraOnResume) {
1356             // Generally, resuming the PhotoPage when in Camera should
1357             // reset to the capture mode to allow quick photo taking
1358             mCurrentIndex = 0;
1359             mPhotoView.resetToFirstPicture();
1360         } else {
1361             int resumeIndex = transitions.get(KEY_INDEX_HINT, -1);
1362             if (resumeIndex >= 0) {
1363                 if (mHasCameraScreennailOrPlaceholder) {
1364                     // Account for preview/placeholder being the first item
1365                     resumeIndex++;
1366                 }
1367                 if (resumeIndex < mMediaSet.getMediaItemCount()) {
1368                     mCurrentIndex = resumeIndex;
1369                     mModel.moveTo(mCurrentIndex);
1370                 }
1371             }
1372         }
1373
1374         if (albumPageTransition == MSG_ALBUMPAGE_RESUMED) {
1375             mPhotoView.setFilmMode(mStartInFilmstrip || mAppBridge != null);
1376         } else if (albumPageTransition == MSG_ALBUMPAGE_PICKED) {
1377             mPhotoView.setFilmMode(false);
1378         }
1379     }
1380
1381     @Override
1382     protected void onResume() {
1383         super.onResume();
1384
1385         if (mModel == null) {
1386             mActivity.getStateManager().finishState(this);
1387             return;
1388         }
1389         transitionFromAlbumPageIfNeeded();
1390
1391         mActivity.getGLRoot().freeze();
1392         mIsActive = true;
1393         setContentPane(mRootPane);
1394
1395         mModel.resume();
1396         mPhotoView.resume();
1397         mActionBar.setDisplayOptions(
1398                 ((mSecureAlbum == null) && (mSetPathString != null)), false);
1399         mActionBar.addOnMenuVisibilityListener(mMenuVisibilityListener);
1400         refreshBottomControlsWhenReady();
1401         if (mShowSpinner && mPhotoView.getFilmMode()) {
1402             mActionBar.enableAlbumModeMenu(
1403                     GalleryActionBar.ALBUM_FILMSTRIP_MODE_SELECTED, this);
1404         }
1405         if (!mShowBars) {
1406             mActionBar.hide();
1407             mActivity.getGLRoot().setLightsOutMode(true);
1408         }
1409         boolean haveImageEditor = GalleryUtils.isEditorAvailable(mActivity, "image/*");
1410         if (haveImageEditor != mHaveImageEditor) {
1411             mHaveImageEditor = haveImageEditor;
1412             updateMenuOperations();
1413         }
1414
1415         mRecenterCameraOnResume = true;
1416         mHandler.sendEmptyMessageDelayed(MSG_UNFREEZE_GLROOT, UNFREEZE_GLROOT_TIMEOUT);
1417     }
1418
1419     @Override
1420     protected void onDestroy() {
1421         if (mAppBridge != null) {
1422             mAppBridge.setServer(null);
1423             mScreenNailItem.setScreenNail(null);
1424             mAppBridge.detachScreenNail();
1425             mAppBridge = null;
1426             mScreenNailSet = null;
1427             mScreenNailItem = null;
1428         }
1429         mActivity.getGLRoot().setOrientationSource(null);
1430         if (mBottomControls != null) mBottomControls.cleanup();
1431
1432         // Remove all pending messages.
1433         mHandler.removeCallbacksAndMessages(null);
1434         super.onDestroy();
1435     }
1436
1437     private class MyDetailsSource implements DetailsSource {
1438
1439         @Override
1440         public MediaDetails getDetails() {
1441             return mModel.getMediaItem(0).getDetails();
1442         }
1443
1444         @Override
1445         public int size() {
1446             return mMediaSet != null ? mMediaSet.getMediaItemCount() : 1;
1447         }
1448
1449         @Override
1450         public int setIndex() {
1451             return mModel.getCurrentIndex();
1452         }
1453     }
1454
1455     @Override
1456     public void onAlbumModeSelected(int mode) {
1457         if (mode == GalleryActionBar.ALBUM_GRID_MODE_SELECTED) {
1458             switchToGrid();
1459         }
1460     }
1461
1462     @Override
1463     public void refreshBottomControlsWhenReady() {
1464         if (mBottomControls == null) {
1465             return;
1466         }
1467         MediaObject currentPhoto = mCurrentPhoto;
1468         if (currentPhoto == null) {
1469             mHandler.obtainMessage(MSG_REFRESH_BOTTOM_CONTROLS, 0, 0, currentPhoto).sendToTarget();
1470         } else {
1471             currentPhoto.getPanoramaSupport(mRefreshBottomControlsCallback);
1472         }
1473     }
1474
1475     private void updatePanoramaUI(boolean isPanorama360) {
1476         Menu menu = mActionBar.getMenu();
1477
1478         // it could be null if onCreateActionBar has not been called yet
1479         if (menu == null) {
1480             return;
1481         }
1482
1483         MenuExecutor.updateMenuForPanorama(menu, isPanorama360, isPanorama360);
1484
1485         if (isPanorama360) {
1486             MenuItem item = menu.findItem(R.id.action_share);
1487             if (item != null) {
1488                 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
1489                 item.setTitle(mActivity.getResources().getString(R.string.share_as_photo));
1490             }
1491         } else if ((mCurrentPhoto.getSupportedOperations() & MediaObject.SUPPORT_SHARE) != 0) {
1492             MenuItem item = menu.findItem(R.id.action_share);
1493             if (item != null) {
1494                 item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
1495                 item.setTitle(mActivity.getResources().getString(R.string.share));
1496             }
1497         }
1498     }
1499
1500     @Override
1501     public void onUndoBarVisibilityChanged(boolean visible) {
1502         refreshBottomControlsWhenReady();
1503     }
1504 }