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