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