OSDN Git Service

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