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