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