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