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