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