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