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