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