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