PhotoPage.java revision a0ce682e40c39ce046c48d8a7f989fff01bb9f2c
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.Activity; 21import android.content.ActivityNotFoundException; 22import android.content.ContentResolver; 23import android.content.Context; 24import android.content.Intent; 25import android.graphics.Rect; 26import android.net.Uri; 27import android.nfc.NfcAdapter; 28import android.os.Bundle; 29import android.os.Handler; 30import android.os.Message; 31import android.view.animation.AccelerateInterpolator; 32import android.widget.Toast; 33 34import com.actionbarsherlock.app.ActionBar.OnMenuVisibilityListener; 35import com.actionbarsherlock.view.Menu; 36import com.actionbarsherlock.view.MenuItem; 37import com.android.gallery3d.R; 38import com.android.gallery3d.anim.FloatAnimation; 39import com.android.gallery3d.common.ApiHelper; 40import com.android.gallery3d.common.Utils; 41import com.android.gallery3d.data.DataManager; 42import com.android.gallery3d.data.FilterDeleteSet; 43import com.android.gallery3d.data.MediaDetails; 44import com.android.gallery3d.data.MediaItem; 45import com.android.gallery3d.data.MediaObject; 46import com.android.gallery3d.data.MediaSet; 47import com.android.gallery3d.data.MtpSource; 48import com.android.gallery3d.data.Path; 49import com.android.gallery3d.data.SecureAlbum; 50import com.android.gallery3d.data.SecureSource; 51import com.android.gallery3d.data.SnailAlbum; 52import com.android.gallery3d.data.SnailItem; 53import com.android.gallery3d.data.SnailSource; 54import com.android.gallery3d.picasasource.PicasaSource; 55import com.android.gallery3d.ui.AnimationTime; 56import com.android.gallery3d.ui.DetailsHelper; 57import com.android.gallery3d.ui.DetailsHelper.CloseListener; 58import com.android.gallery3d.ui.DetailsHelper.DetailsSource; 59import com.android.gallery3d.ui.GLCanvas; 60import com.android.gallery3d.ui.GLRoot; 61import com.android.gallery3d.ui.GLRoot.OnGLIdleListener; 62import com.android.gallery3d.ui.BitmapScreenNail; 63import com.android.gallery3d.ui.GLView; 64import com.android.gallery3d.ui.ImportCompleteListener; 65import com.android.gallery3d.ui.MenuExecutor; 66import com.android.gallery3d.ui.PhotoFallbackEffect; 67import com.android.gallery3d.ui.PhotoView; 68import com.android.gallery3d.ui.RawTexture; 69import com.android.gallery3d.ui.SelectionManager; 70import com.android.gallery3d.ui.SynchronizedHandler; 71import com.android.gallery3d.util.GalleryUtils; 72import com.android.gallery3d.util.MediaSetUtils; 73 74public class PhotoPage extends ActivityState implements 75 PhotoView.Listener, OrientationManager.Listener, AppBridge.Server { 76 private static final String TAG = "PhotoPage"; 77 78 private static final int MSG_HIDE_BARS = 1; 79 private static final int MSG_LOCK_ORIENTATION = 2; 80 private static final int MSG_UNLOCK_ORIENTATION = 3; 81 private static final int MSG_ON_FULL_SCREEN_CHANGED = 4; 82 private static final int MSG_UPDATE_ACTION_BAR = 5; 83 private static final int MSG_UNFREEZE_GLROOT = 6; 84 private static final int MSG_WANT_BARS = 7; 85 86 private static final int HIDE_BARS_TIMEOUT = 3500; 87 private static final int UNFREEZE_GLROOT_TIMEOUT = 250; 88 89 private static final int REQUEST_SLIDESHOW = 1; 90 private static final int REQUEST_CROP = 2; 91 private static final int REQUEST_CROP_PICASA = 3; 92 private static final int REQUEST_EDIT = 4; 93 private static final int REQUEST_PLAY_VIDEO = 5; 94 95 public static final String KEY_MEDIA_SET_PATH = "media-set-path"; 96 public static final String KEY_MEDIA_ITEM_PATH = "media-item-path"; 97 public static final String KEY_INDEX_HINT = "index-hint"; 98 public static final String KEY_OPEN_ANIMATION_RECT = "open-animation-rect"; 99 public static final String KEY_APP_BRIDGE = "app-bridge"; 100 public static final String KEY_TREAT_BACK_AS_UP = "treat-back-as-up"; 101 102 public static final String KEY_RETURN_INDEX_HINT = "return-index-hint"; 103 104 private GalleryApp mApplication; 105 private SelectionManager mSelectionManager; 106 107 private PhotoView mPhotoView; 108 private PhotoPage.Model mModel; 109 private DetailsHelper mDetailsHelper; 110 private boolean mShowDetails; 111 private Path mPendingSharePath; 112 113 // mMediaSet could be null if there is no KEY_MEDIA_SET_PATH supplied. 114 // E.g., viewing a photo in gmail attachment 115 private FilterDeleteSet mMediaSet; 116 117 // The mediaset used by camera launched from secure lock screen. 118 private SecureAlbum mSecureAlbum; 119 120 private int mCurrentIndex = 0; 121 private Handler mHandler; 122 private boolean mShowBars = true; 123 private volatile boolean mActionBarAllowed = true; 124 private GalleryActionBar mActionBar; 125 private boolean mIsMenuVisible; 126 private MediaItem mCurrentPhoto = null; 127 private MenuExecutor mMenuExecutor; 128 private boolean mIsActive; 129 private String mSetPathString; 130 // This is the original mSetPathString before adding the camera preview item. 131 private String mOriginalSetPathString; 132 private AppBridge mAppBridge; 133 private SnailItem mScreenNailItem; 134 private SnailAlbum mScreenNailSet; 135 private OrientationManager mOrientationManager; 136 private boolean mHasActivityResult; 137 private boolean mTreatBackAsUp; 138 139 private RawTexture mFadeOutTexture; 140 private Rect mOpenAnimationRect; 141 public static final int ANIM_TIME_OPENING = 400; 142 143 // The item that is deleted (but it can still be undeleted before commiting) 144 private Path mDeletePath; 145 private boolean mDeleteIsFocus; // whether the deleted item was in focus 146 147 private NfcAdapter mNfcAdapter; 148 149 private Menu mActionBarMenu; 150 151 private final MyMenuVisibilityListener mMenuVisibilityListener = 152 new MyMenuVisibilityListener(); 153 154 public static interface Model extends PhotoView.Model { 155 public void resume(); 156 public void pause(); 157 public boolean isEmpty(); 158 public void setCurrentPhoto(Path path, int indexHint); 159 } 160 161 private class MyMenuVisibilityListener implements OnMenuVisibilityListener { 162 @Override 163 public void onMenuVisibilityChanged(boolean isVisible) { 164 mIsMenuVisible = isVisible; 165 refreshHidingMessage(); 166 } 167 } 168 169 private static class BackgroundFadeOut extends FloatAnimation { 170 public BackgroundFadeOut() { 171 super(1f, 0f, ANIM_TIME_OPENING); 172 setInterpolator(new AccelerateInterpolator(2f)); 173 } 174 } 175 176 private final FloatAnimation mBackgroundFade = new BackgroundFadeOut(); 177 178 @Override 179 protected int getBackgroundColorId() { 180 return R.color.photo_background; 181 } 182 183 private final GLView mRootPane = new GLView() { 184 @Override 185 protected void renderBackground(GLCanvas view) { 186 if(mFadeOutTexture != null) { 187 if(mBackgroundFade.calculate(AnimationTime.get())) invalidate(); 188 if(!mBackgroundFade.isActive()) { 189 mFadeOutTexture = null; 190 mOpenAnimationRect = null; 191 BitmapScreenNail.enableDrawPlaceholder(); 192 } else { 193 float fadeAlpha = mBackgroundFade.get(); 194 if(fadeAlpha < 1f) { 195 view.clearBuffer(getBackgroundColor()); 196 view.setAlpha(fadeAlpha); 197 } 198 mFadeOutTexture.draw(view, 0, 0); 199 view.setAlpha(1f - fadeAlpha); 200 return; 201 } 202 } 203 view.clearBuffer(getBackgroundColor()); 204 } 205 206 @Override 207 protected void onLayout( 208 boolean changed, int left, int top, int right, int bottom) { 209 mPhotoView.layout(0, 0, right - left, bottom - top); 210 if (mShowDetails) { 211 mDetailsHelper.layout(left, mActionBar.getHeight(), right, bottom); 212 } 213 } 214 }; 215 216 @Override 217 public void onCreate(Bundle data, Bundle restoreState) { 218 mActionBar = mActivity.getGalleryActionBar(); 219 mSelectionManager = new SelectionManager(mActivity, false); 220 mMenuExecutor = new MenuExecutor(mActivity, mSelectionManager); 221 222 mPhotoView = new PhotoView(mActivity); 223 mPhotoView.setListener(this); 224 mRootPane.addComponent(mPhotoView); 225 mApplication = (GalleryApp)((Activity) mActivity).getApplication(); 226 mOrientationManager = mActivity.getOrientationManager(); 227 mOrientationManager.addListener(this); 228 mActivity.getGLRoot().setOrientationSource(mOrientationManager); 229 230 mSetPathString = data.getString(KEY_MEDIA_SET_PATH); 231 mOriginalSetPathString = mSetPathString; 232 mNfcAdapter = NfcAdapter.getDefaultAdapter(mActivity.getAndroidContext()); 233 Path itemPath = Path.fromString(data.getString(KEY_MEDIA_ITEM_PATH)); 234 mTreatBackAsUp = data.getBoolean(KEY_TREAT_BACK_AS_UP, false); 235 236 if (mSetPathString != null) { 237 mAppBridge = (AppBridge) data.getParcelable(KEY_APP_BRIDGE); 238 if (mAppBridge != null) { 239 mAppBridge.setServer(this); 240 mOrientationManager.lockOrientation(); 241 242 // Get the ScreenNail from AppBridge and register it. 243 int id = SnailSource.newId(); 244 Path screenNailSetPath = SnailSource.getSetPath(id); 245 Path screenNailItemPath = SnailSource.getItemPath(id); 246 mScreenNailSet = (SnailAlbum) mActivity.getDataManager() 247 .getMediaObject(screenNailSetPath); 248 mScreenNailItem = (SnailItem) mActivity.getDataManager() 249 .getMediaObject(screenNailItemPath); 250 mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail()); 251 252 // Check if the path is a secure album. 253 if (SecureSource.isSecurePath(mSetPathString)) { 254 mSecureAlbum = (SecureAlbum) mActivity.getDataManager() 255 .getMediaSet(mSetPathString); 256 } 257 258 // Combine the original MediaSet with the one for ScreenNail 259 // from AppBridge. 260 mSetPathString = "/combo/item/{" + screenNailSetPath + 261 "," + mSetPathString + "}"; 262 263 // Start from the screen nail. 264 itemPath = screenNailItemPath; 265 266 // Action bar should not be displayed when camera starts. 267 mFlags |= FLAG_HIDE_ACTION_BAR | FLAG_HIDE_STATUS_BAR; 268 mShowBars = false; 269 } 270 271 MediaSet originalSet = mActivity.getDataManager() 272 .getMediaSet(mSetPathString); 273 mSelectionManager.setSourceMediaSet(originalSet); 274 mSetPathString = "/filter/delete/{" + mSetPathString + "}"; 275 mMediaSet = (FilterDeleteSet) mActivity.getDataManager() 276 .getMediaSet(mSetPathString); 277 mCurrentIndex = data.getInt(KEY_INDEX_HINT, 0); 278 if (mMediaSet == null) { 279 Log.w(TAG, "failed to restore " + mSetPathString); 280 } 281 PhotoDataAdapter pda = new PhotoDataAdapter( 282 mActivity, mPhotoView, mMediaSet, itemPath, mCurrentIndex, 283 mAppBridge == null ? -1 : 0, 284 mAppBridge == null ? false : mAppBridge.isPanorama(), 285 mAppBridge == null ? false : mAppBridge.isStaticCamera()); 286 mModel = pda; 287 mPhotoView.setModel(mModel); 288 289 pda.setDataListener(new PhotoDataAdapter.DataListener() { 290 291 @Override 292 public void onPhotoChanged(int index, Path item) { 293 mCurrentIndex = index; 294 if (item != null) { 295 MediaItem photo = mModel.getMediaItem(0); 296 if (photo != null) updateCurrentPhoto(photo); 297 } 298 updateBars(); 299 } 300 301 @Override 302 public void onLoadingFinished() { 303 if (!mModel.isEmpty()) { 304 MediaItem photo = mModel.getMediaItem(0); 305 if (photo != null) updateCurrentPhoto(photo); 306 } else if (mIsActive) { 307 // We only want to finish the PhotoPage if there is no 308 // deletion that the user can undo. 309 if (mMediaSet.getNumberOfDeletions() == 0) { 310 mActivity.getStateManager().finishState( 311 PhotoPage.this); 312 } 313 } 314 } 315 316 @Override 317 public void onLoadingStarted() { 318 } 319 }); 320 } else { 321 // Get default media set by the URI 322 MediaItem mediaItem = (MediaItem) 323 mActivity.getDataManager().getMediaObject(itemPath); 324 mModel = new SinglePhotoDataAdapter(mActivity, mPhotoView, mediaItem); 325 mPhotoView.setModel(mModel); 326 updateCurrentPhoto(mediaItem); 327 } 328 329 mHandler = new SynchronizedHandler(mActivity.getGLRoot()) { 330 @Override 331 public void handleMessage(Message message) { 332 switch (message.what) { 333 case MSG_HIDE_BARS: { 334 hideBars(); 335 break; 336 } 337 case MSG_LOCK_ORIENTATION: { 338 mOrientationManager.lockOrientation(); 339 break; 340 } 341 case MSG_UNLOCK_ORIENTATION: { 342 mOrientationManager.unlockOrientation(); 343 break; 344 } 345 case MSG_ON_FULL_SCREEN_CHANGED: { 346 mAppBridge.onFullScreenChanged(message.arg1 == 1); 347 break; 348 } 349 case MSG_UPDATE_ACTION_BAR: { 350 updateBars(); 351 break; 352 } 353 case MSG_WANT_BARS: { 354 wantBars(); 355 break; 356 } 357 case MSG_UNFREEZE_GLROOT: { 358 mActivity.getGLRoot().unfreeze(); 359 break; 360 } 361 default: throw new AssertionError(message.what); 362 } 363 } 364 }; 365 366 // start the opening animation only if it's not restored. 367 if (restoreState == null) { 368 mFadeOutTexture = mActivity.getTransitionStore().get(AlbumPage.KEY_FADE_TEXTURE); 369 if(mFadeOutTexture != null) { 370 mBackgroundFade.start(); 371 BitmapScreenNail.disableDrawPlaceholder(); 372 mOpenAnimationRect = (Rect) data.getParcelable(KEY_OPEN_ANIMATION_RECT); 373 mPhotoView.setOpenAnimationRect(mOpenAnimationRect); 374 } 375 } 376 } 377 378 @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN) 379 private void setNfcBeamPushUris(Uri[] uris) { 380 if (mNfcAdapter != null && ApiHelper.HAS_SET_BEAM_PUSH_URIS) { 381 mNfcAdapter.setBeamPushUris(uris, (Activity)mActivity); 382 } 383 } 384 385 private Intent createShareIntent(Path path) { 386 DataManager manager = mActivity.getDataManager(); 387 int type = manager.getMediaType(path); 388 Intent intent = new Intent(Intent.ACTION_SEND); 389 intent.setType(MenuExecutor.getMimeType(type)); 390 Uri uri = manager.getContentUri(path); 391 intent.putExtra(Intent.EXTRA_STREAM, uri); 392 return intent; 393 394 } 395 396 private void updateShareURI(Path path) { 397 if (mActionBar.hasShareMenuItem()) { 398 DataManager manager = mActivity.getDataManager(); 399 Uri uri = manager.getContentUri(path); 400 mActionBar.setShareIntent(createShareIntent(path)); 401 setNfcBeamPushUris(new Uri[]{uri}); 402 mPendingSharePath = null; 403 } else { 404 // This happens when ActionBar is not created yet. 405 mPendingSharePath = path; 406 } 407 } 408 409 private void updateCurrentPhoto(MediaItem photo) { 410 if (mCurrentPhoto == photo) return; 411 mCurrentPhoto = photo; 412 if (mCurrentPhoto == null) return; 413 updateMenuOperations(); 414 updateTitle(); 415 if (mShowDetails) { 416 mDetailsHelper.reloadDetails(); 417 } 418 if ((photo.getSupportedOperations() & MediaItem.SUPPORT_SHARE) != 0) { 419 updateShareURI(photo.getPath()); 420 } 421 } 422 423 private void updateTitle() { 424 if (mCurrentPhoto == null) return; 425 boolean showTitle = mActivity.getAndroidContext().getResources().getBoolean( 426 R.bool.show_action_bar_title); 427 if (showTitle && mCurrentPhoto.getName() != null) { 428 mActionBar.setTitle(mCurrentPhoto.getName()); 429 } else { 430 mActionBar.setTitle(""); 431 } 432 } 433 434 private void updateMenuOperations() { 435 MenuItem item = mActionBar.findMenuItem(R.id.action_slideshow); 436 item.setVisible(canDoSlideShow()); 437 if (mCurrentPhoto == null) return; 438 int supportedOperations = mCurrentPhoto.getSupportedOperations(); 439 if (!GalleryUtils.isEditorAvailable((Context) mActivity, "image/*")) { 440 supportedOperations &= ~MediaObject.SUPPORT_EDIT; 441 } 442 MenuExecutor.updateMenuOperation(mActionBar.getMenu(), supportedOperations); 443 } 444 445 private boolean canDoSlideShow() { 446 if (mMediaSet == null || mCurrentPhoto == null) { 447 return false; 448 } 449 if (mCurrentPhoto.getMediaType() != MediaObject.MEDIA_TYPE_IMAGE) { 450 return false; 451 } 452 if (MtpSource.isMtpPath(mOriginalSetPathString)) { 453 return false; 454 } 455 return true; 456 } 457 458 ////////////////////////////////////////////////////////////////////////// 459 // Action Bar show/hide management 460 ////////////////////////////////////////////////////////////////////////// 461 462 private void showBars() { 463 if (mShowBars) return; 464 mShowBars = true; 465 mOrientationManager.unlockOrientation(); 466 mActionBar.show(); 467 mActivity.getGLRoot().setLightsOutMode(false); 468 refreshHidingMessage(); 469 } 470 471 private void hideBars() { 472 if (!mShowBars) return; 473 mShowBars = false; 474 mActionBar.hide(); 475 mActivity.getGLRoot().setLightsOutMode(true); 476 mHandler.removeMessages(MSG_HIDE_BARS); 477 } 478 479 private void refreshHidingMessage() { 480 mHandler.removeMessages(MSG_HIDE_BARS); 481 if (!mIsMenuVisible) { 482 mHandler.sendEmptyMessageDelayed(MSG_HIDE_BARS, HIDE_BARS_TIMEOUT); 483 } 484 } 485 486 private boolean canShowBars() { 487 // No bars if we are showing camera preview. 488 if (mAppBridge != null && mCurrentIndex == 0) return false; 489 // No bars if it's not allowed. 490 if (!mActionBarAllowed) return false; 491 492 return true; 493 } 494 495 private void wantBars() { 496 if (canShowBars()) showBars(); 497 } 498 499 private void toggleBars() { 500 if (mShowBars) { 501 hideBars(); 502 } else { 503 if (canShowBars()) showBars(); 504 } 505 } 506 507 private void updateBars() { 508 if (!canShowBars()) { 509 hideBars(); 510 } 511 } 512 513 @Override 514 public void onOrientationCompensationChanged() { 515 mActivity.getGLRoot().requestLayoutContentPane(); 516 } 517 518 @Override 519 protected void onBackPressed() { 520 if (mShowDetails) { 521 hideDetails(); 522 } else if (mAppBridge == null || !switchWithCaptureAnimation(-1)) { 523 // We are leaving this page. Set the result now. 524 setResult(); 525 if (mTreatBackAsUp) { 526 onUpPressed(); 527 } else { 528 super.onBackPressed(); 529 } 530 } 531 } 532 533 private void onUpPressed() { 534 if (mActivity.getStateManager().getStateCount() > 1) { 535 super.onBackPressed(); 536 return; 537 } 538 539 if (mOriginalSetPathString == null) return; 540 541 if (mAppBridge == null) { 542 // We're in view mode so set up the stacks on our own. 543 Bundle data = new Bundle(getData()); 544 data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString); 545 data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH, 546 mActivity.getDataManager().getTopSetPath( 547 DataManager.INCLUDE_ALL)); 548 mActivity.getStateManager().switchState(this, AlbumPage.class, data); 549 } else { 550 // Start the real gallery activity to view the camera roll. 551 Uri uri = Uri.parse("content://media/external/file?bucketId=" 552 + MediaSetUtils.CAMERA_BUCKET_ID); 553 Intent intent = new Intent(Intent.ACTION_VIEW); 554 intent.setDataAndType(uri, ContentResolver.CURSOR_DIR_BASE_TYPE + "/image"); 555 ((Activity) mActivity).startActivity(intent); 556 } 557 } 558 559 private void setResult() { 560 Intent result = null; 561 if (!mPhotoView.getFilmMode()) { 562 result = new Intent(); 563 result.putExtra(KEY_RETURN_INDEX_HINT, mCurrentIndex); 564 } 565 setStateResult(Activity.RESULT_OK, result); 566 } 567 568 ////////////////////////////////////////////////////////////////////////// 569 // AppBridge.Server interface 570 ////////////////////////////////////////////////////////////////////////// 571 572 @Override 573 public void setCameraRelativeFrame(Rect frame) { 574 mPhotoView.setCameraRelativeFrame(frame); 575 } 576 577 @Override 578 public boolean switchWithCaptureAnimation(int offset) { 579 return mPhotoView.switchWithCaptureAnimation(offset); 580 } 581 582 @Override 583 public void setSwipingEnabled(boolean enabled) { 584 mPhotoView.setSwipingEnabled(enabled); 585 } 586 587 @Override 588 public void notifyScreenNailChanged() { 589 mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail()); 590 mScreenNailSet.notifyChange(); 591 } 592 593 @Override 594 public void addSecureAlbumItem(boolean isVideo, int id) { 595 mSecureAlbum.addMediaItem(isVideo, id); 596 } 597 598 @Override 599 protected boolean onCreateActionBar(Menu menu) { 600 mActionBar.createActionBarMenu(R.menu.photo, menu); 601 if (mPendingSharePath != null) updateShareURI(mPendingSharePath); 602 updateMenuOperations(); 603 updateTitle(); 604 return true; 605 } 606 607 private MenuExecutor.ProgressListener mConfirmDialogListener = 608 new MenuExecutor.ProgressListener() { 609 @Override 610 public void onProgressUpdate(int index) {} 611 612 @Override 613 public void onProgressComplete(int result) {} 614 615 @Override 616 public void onConfirmDialogShown() { 617 mHandler.removeMessages(MSG_HIDE_BARS); 618 } 619 620 @Override 621 public void onConfirmDialogDismissed(boolean confirmed) { 622 refreshHidingMessage(); 623 } 624 625 @Override 626 public void onProgressStart() {} 627 }; 628 629 @Override 630 protected boolean onItemSelected(MenuItem item) { 631 refreshHidingMessage(); 632 MediaItem current = mModel.getMediaItem(0); 633 634 if (current == null) { 635 // item is not ready, ignore 636 return true; 637 } 638 639 int currentIndex = mModel.getCurrentIndex(); 640 Path path = current.getPath(); 641 642 DataManager manager = mActivity.getDataManager(); 643 int action = item.getItemId(); 644 String confirmMsg = null; 645 switch (action) { 646 case android.R.id.home: { 647 onUpPressed(); 648 return true; 649 } 650 case R.id.action_slideshow: { 651 Bundle data = new Bundle(); 652 data.putString(SlideshowPage.KEY_SET_PATH, mMediaSet.getPath().toString()); 653 data.putString(SlideshowPage.KEY_ITEM_PATH, path.toString()); 654 data.putInt(SlideshowPage.KEY_PHOTO_INDEX, currentIndex); 655 data.putBoolean(SlideshowPage.KEY_REPEAT, true); 656 mActivity.getStateManager().startStateForResult( 657 SlideshowPage.class, REQUEST_SLIDESHOW, data); 658 return true; 659 } 660 case R.id.action_crop: { 661 Activity activity = (Activity) mActivity; 662 Intent intent = new Intent(CropImage.CROP_ACTION); 663 intent.setClass(activity, CropImage.class); 664 intent.setData(manager.getContentUri(path)); 665 activity.startActivityForResult(intent, PicasaSource.isPicasaImage(current) 666 ? REQUEST_CROP_PICASA 667 : REQUEST_CROP); 668 return true; 669 } 670 case R.id.action_edit: { 671 Intent intent = new Intent(Intent.ACTION_EDIT) 672 .setData(manager.getContentUri(path)) 673 .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 674 ((Activity) mActivity).startActivityForResult(Intent.createChooser(intent, null), 675 REQUEST_EDIT); 676 return true; 677 } 678 case R.id.action_details: { 679 if (mShowDetails) { 680 hideDetails(); 681 } else { 682 showDetails(); 683 } 684 return true; 685 } 686 case R.id.action_delete: 687 confirmMsg = mActivity.getResources().getQuantityString( 688 R.plurals.delete_selection, 1); 689 case R.id.action_setas: 690 case R.id.action_rotate_ccw: 691 case R.id.action_rotate_cw: 692 case R.id.action_show_on_map: 693 mSelectionManager.deSelectAll(); 694 mSelectionManager.toggle(path); 695 mMenuExecutor.onMenuClicked(item, confirmMsg, mConfirmDialogListener); 696 return true; 697 case R.id.action_import: 698 mSelectionManager.deSelectAll(); 699 mSelectionManager.toggle(path); 700 mMenuExecutor.onMenuClicked(item, confirmMsg, 701 new ImportCompleteListener(mActivity)); 702 return true; 703 case R.id.action_share: 704 Activity activity = (Activity) mActivity; 705 Intent intent = createShareIntent(mCurrentPhoto.getPath()); 706 activity.startActivity(Intent.createChooser(intent, 707 activity.getString(R.string.share))); 708 return true; 709 default : 710 return false; 711 } 712 } 713 714 private void hideDetails() { 715 mShowDetails = false; 716 mDetailsHelper.hide(); 717 } 718 719 private void showDetails() { 720 mShowDetails = true; 721 if (mDetailsHelper == null) { 722 mDetailsHelper = new DetailsHelper(mActivity, mRootPane, new MyDetailsSource()); 723 mDetailsHelper.setCloseListener(new CloseListener() { 724 @Override 725 public void onClose() { 726 hideDetails(); 727 } 728 }); 729 } 730 mDetailsHelper.show(); 731 } 732 733 //////////////////////////////////////////////////////////////////////////// 734 // Callbacks from PhotoView 735 //////////////////////////////////////////////////////////////////////////// 736 @Override 737 public void onSingleTapUp(int x, int y) { 738 if (mAppBridge != null) { 739 if (mAppBridge.onSingleTapUp(x, y)) return; 740 } 741 742 MediaItem item = mModel.getMediaItem(0); 743 if (item == null || item == mScreenNailItem) { 744 // item is not ready or it is camera preview, ignore 745 return; 746 } 747 748 boolean playVideo = 749 (item.getSupportedOperations() & MediaItem.SUPPORT_PLAY) != 0; 750 751 if (playVideo) { 752 // determine if the point is at center (1/6) of the photo view. 753 // (The position of the "play" icon is at center (1/6) of the photo) 754 int w = mPhotoView.getWidth(); 755 int h = mPhotoView.getHeight(); 756 playVideo = (Math.abs(x - w / 2) * 12 <= w) 757 && (Math.abs(y - h / 2) * 12 <= h); 758 } 759 760 if (playVideo) { 761 playVideo((Activity) mActivity, item.getPlayUri(), item.getName()); 762 } else { 763 toggleBars(); 764 } 765 } 766 767 @Override 768 public void lockOrientation() { 769 mHandler.sendEmptyMessage(MSG_LOCK_ORIENTATION); 770 } 771 772 @Override 773 public void unlockOrientation() { 774 mHandler.sendEmptyMessage(MSG_UNLOCK_ORIENTATION); 775 } 776 777 @Override 778 public void onActionBarAllowed(boolean allowed) { 779 mActionBarAllowed = allowed; 780 mHandler.sendEmptyMessage(MSG_UPDATE_ACTION_BAR); 781 } 782 783 @Override 784 public void onActionBarWanted() { 785 mHandler.sendEmptyMessage(MSG_WANT_BARS); 786 } 787 788 @Override 789 public void onFullScreenChanged(boolean full) { 790 Message m = mHandler.obtainMessage( 791 MSG_ON_FULL_SCREEN_CHANGED, full ? 1 : 0, 0); 792 m.sendToTarget(); 793 } 794 795 // How we do delete/undo: 796 // 797 // When the user choose to delete a media item, we just tell the 798 // FilterDeleteSet to hide that item. If the user choose to undo it, we 799 // again tell FilterDeleteSet not to hide it. If the user choose to commit 800 // the deletion, we then actually delete the media item. 801 @Override 802 public void onDeleteImage(Path path, int offset) { 803 onCommitDeleteImage(); // commit the previous deletion 804 mDeletePath = path; 805 mDeleteIsFocus = (offset == 0); 806 mMediaSet.addDeletion(path, mCurrentIndex + offset); 807 } 808 809 @Override 810 public void onUndoDeleteImage() { 811 if (mDeletePath == null) return; 812 // If the deletion was done on the focused item, we want the model to 813 // focus on it when it is undeleted. 814 if (mDeleteIsFocus) mModel.setFocusHintPath(mDeletePath); 815 mMediaSet.removeDeletion(mDeletePath); 816 mDeletePath = null; 817 } 818 819 @Override 820 public void onCommitDeleteImage() { 821 if (mDeletePath == null) return; 822 mSelectionManager.deSelectAll(); 823 mSelectionManager.toggle(mDeletePath); 824 mMenuExecutor.onMenuClicked(R.id.action_delete, null, true, false); 825 mDeletePath = null; 826 } 827 828 public static void playVideo(Activity activity, Uri uri, String title) { 829 try { 830 Intent intent = new Intent(Intent.ACTION_VIEW) 831 .setDataAndType(uri, "video/*") 832 .putExtra(Intent.EXTRA_TITLE, title) 833 .putExtra(MovieActivity.KEY_TREAT_UP_AS_BACK, true); 834 activity.startActivityForResult(intent, REQUEST_PLAY_VIDEO); 835 } catch (ActivityNotFoundException e) { 836 Toast.makeText(activity, activity.getString(R.string.video_err), 837 Toast.LENGTH_SHORT).show(); 838 } 839 } 840 841 private void setCurrentPhotoByIntent(Intent intent) { 842 if (intent == null) return; 843 Path path = mApplication.getDataManager() 844 .findPathByUri(intent.getData(), intent.getType()); 845 if (path != null) { 846 mModel.setCurrentPhoto(path, mCurrentIndex); 847 } 848 } 849 850 @Override 851 protected void onStateResult(int requestCode, int resultCode, Intent data) { 852 mHasActivityResult = true; 853 switch (requestCode) { 854 case REQUEST_EDIT: 855 setCurrentPhotoByIntent(data); 856 break; 857 case REQUEST_CROP: 858 if (resultCode == Activity.RESULT_OK) { 859 setCurrentPhotoByIntent(data); 860 } 861 break; 862 case REQUEST_CROP_PICASA: { 863 if (resultCode == Activity.RESULT_OK) { 864 Context context = mActivity.getAndroidContext(); 865 String message = context.getString(R.string.crop_saved, 866 context.getString(R.string.folder_download)); 867 Toast.makeText(context, message, Toast.LENGTH_SHORT).show(); 868 } 869 break; 870 } 871 case REQUEST_SLIDESHOW: { 872 if (data == null) break; 873 String path = data.getStringExtra(SlideshowPage.KEY_ITEM_PATH); 874 int index = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0); 875 if (path != null) { 876 mModel.setCurrentPhoto(Path.fromString(path), index); 877 } 878 } 879 } 880 } 881 882 @Override 883 protected void clearStateResult() { 884 mHasActivityResult = false; 885 } 886 887 private class PreparePhotoFallback implements OnGLIdleListener { 888 private PhotoFallbackEffect mPhotoFallback = new PhotoFallbackEffect(); 889 private boolean mResultReady = false; 890 891 public synchronized PhotoFallbackEffect get() { 892 while (!mResultReady) { 893 Utils.waitWithoutInterrupt(this); 894 } 895 return mPhotoFallback; 896 } 897 898 @Override 899 public boolean onGLIdle(GLCanvas canvas, boolean renderRequested) { 900 mPhotoFallback = mPhotoView.buildFallbackEffect(mRootPane, canvas); 901 synchronized (this) { 902 mResultReady = true; 903 notifyAll(); 904 } 905 return false; 906 } 907 } 908 909 private void preparePhotoFallbackView() { 910 GLRoot root = mActivity.getGLRoot(); 911 PreparePhotoFallback task = new PreparePhotoFallback(); 912 root.unlockRenderThread(); 913 PhotoFallbackEffect anim; 914 try { 915 root.addOnGLIdleListener(task); 916 anim = task.get(); 917 } finally { 918 root.lockRenderThread(); 919 } 920 mActivity.getTransitionStore().put( 921 AlbumPage.KEY_RESUME_ANIMATION, anim); 922 } 923 924 @Override 925 public void onPause() { 926 super.onPause(); 927 mIsActive = false; 928 929 mActivity.getGLRoot().unfreeze(); 930 mHandler.removeMessages(MSG_UNFREEZE_GLROOT); 931 if (isFinishing()) preparePhotoFallbackView(); 932 933 DetailsHelper.pause(); 934 mPhotoView.pause(); 935 mModel.pause(); 936 mHandler.removeMessages(MSG_HIDE_BARS); 937 mActionBar.removeOnMenuVisibilityListener(mMenuVisibilityListener); 938 939 onCommitDeleteImage(); 940 mMenuExecutor.pause(); 941 if (mMediaSet != null) mMediaSet.clearDeletion(); 942 } 943 944 @Override 945 public void onCurrentImageUpdated() { 946 mActivity.getGLRoot().unfreeze(); 947 } 948 949 @Override 950 protected void onResume() { 951 super.onResume(); 952 mActivity.getGLRoot().freeze(); 953 mIsActive = true; 954 setContentPane(mRootPane); 955 956 mModel.resume(); 957 mPhotoView.resume(); 958 mActionBar.setDisplayOptions(mSetPathString != null, true); 959 mActionBar.addOnMenuVisibilityListener(mMenuVisibilityListener); 960 961 if (mAppBridge != null && !mHasActivityResult) { 962 mPhotoView.resetToFirstPicture(); 963 } 964 mHasActivityResult = false; 965 mHandler.sendEmptyMessageDelayed(MSG_UNFREEZE_GLROOT, UNFREEZE_GLROOT_TIMEOUT); 966 } 967 968 @Override 969 protected void onDestroy() { 970 if (mAppBridge != null) { 971 mAppBridge.setServer(null); 972 mScreenNailItem.setScreenNail(null); 973 mAppBridge.detachScreenNail(); 974 mAppBridge = null; 975 mScreenNailSet = null; 976 mScreenNailItem = null; 977 } 978 mOrientationManager.removeListener(this); 979 mActivity.getGLRoot().setOrientationSource(null); 980 981 // Remove all pending messages. 982 mHandler.removeCallbacksAndMessages(null); 983 super.onDestroy(); 984 } 985 986 private class MyDetailsSource implements DetailsSource { 987 988 @Override 989 public MediaDetails getDetails() { 990 return mModel.getMediaItem(0).getDetails(); 991 } 992 993 @Override 994 public int size() { 995 return mMediaSet != null ? mMediaSet.getMediaItemCount() : 1; 996 } 997 998 @Override 999 public int setIndex() { 1000 return mModel.getCurrentIndex(); 1001 } 1002 } 1003} 1004