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