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