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