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