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