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