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