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