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