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