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