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