PhotoPage.java revision 655f63f04638dfe31045eab1b6b4c39cc2db10db
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; 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.net.Uri; 27import android.os.Bundle; 28import android.os.Handler; 29import android.os.Message; 30import android.view.Menu; 31import android.view.MenuInflater; 32import android.view.MenuItem; 33import android.view.View; 34import android.view.View.MeasureSpec; 35import android.view.WindowManager; 36import android.widget.ShareActionProvider; 37import android.widget.Toast; 38 39import com.android.gallery3d.R; 40import com.android.gallery3d.data.DataManager; 41import com.android.gallery3d.data.MediaDetails; 42import com.android.gallery3d.data.MediaItem; 43import com.android.gallery3d.data.MediaObject; 44import com.android.gallery3d.data.MediaSet; 45import com.android.gallery3d.data.MtpDevice; 46import com.android.gallery3d.data.Path; 47import com.android.gallery3d.picasasource.PicasaSource; 48import com.android.gallery3d.ui.DetailsHelper; 49import com.android.gallery3d.ui.DetailsHelper.CloseListener; 50import com.android.gallery3d.ui.DetailsHelper.DetailsSource; 51import com.android.gallery3d.ui.FilmStripView; 52import com.android.gallery3d.ui.GLCanvas; 53import com.android.gallery3d.ui.GLView; 54import com.android.gallery3d.ui.ImportCompleteListener; 55import com.android.gallery3d.ui.MenuExecutor; 56import com.android.gallery3d.ui.PhotoView; 57import com.android.gallery3d.ui.PositionRepository; 58import com.android.gallery3d.ui.PositionRepository.Position; 59import com.android.gallery3d.ui.SelectionManager; 60import com.android.gallery3d.ui.SynchronizedHandler; 61import com.android.gallery3d.ui.UserInteractionListener; 62import com.android.gallery3d.util.GalleryUtils; 63 64public class PhotoPage extends ActivityState 65 implements PhotoView.PhotoTapListener, FilmStripView.Listener, 66 UserInteractionListener { 67 private static final String TAG = "PhotoPage"; 68 69 private static final int MSG_HIDE_BARS = 1; 70 71 private static final int HIDE_BARS_TIMEOUT = 3500; 72 73 private static final int REQUEST_SLIDESHOW = 1; 74 private static final int REQUEST_CROP = 2; 75 private static final int REQUEST_CROP_PICASA = 3; 76 77 public static final String KEY_MEDIA_SET_PATH = "media-set-path"; 78 public static final String KEY_MEDIA_ITEM_PATH = "media-item-path"; 79 public static final String KEY_INDEX_HINT = "index-hint"; 80 81 private GalleryApp mApplication; 82 private SelectionManager mSelectionManager; 83 84 private PhotoView mPhotoView; 85 private PhotoPage.Model mModel; 86 private FilmStripView mFilmStripView; 87 private DetailsHelper mDetailsHelper; 88 private boolean mShowDetails; 89 private Path mPendingSharePath; 90 91 // mMediaSet could be null if there is no KEY_MEDIA_SET_PATH supplied. 92 // E.g., viewing a photo in gmail attachment 93 private MediaSet mMediaSet; 94 private Menu mMenu; 95 96 private final Intent mResultIntent = new Intent(); 97 private int mCurrentIndex = 0; 98 private Handler mHandler; 99 private boolean mShowBars = true; 100 private ActionBar mActionBar; 101 private MyMenuVisibilityListener mMenuVisibilityListener; 102 private boolean mIsMenuVisible; 103 private boolean mIsInteracting; 104 private MediaItem mCurrentPhoto = null; 105 private MenuExecutor mMenuExecutor; 106 private boolean mIsActive; 107 private ShareActionProvider mShareActionProvider; 108 private String mSetPathString; 109 110 public static interface Model extends PhotoView.Model { 111 public void resume(); 112 public void pause(); 113 public boolean isEmpty(); 114 public MediaItem getCurrentMediaItem(); 115 public int getCurrentIndex(); 116 public void setCurrentPhoto(Path path, int indexHint); 117 } 118 119 private class MyMenuVisibilityListener implements OnMenuVisibilityListener { 120 public void onMenuVisibilityChanged(boolean isVisible) { 121 mIsMenuVisible = isVisible; 122 refreshHidingMessage(); 123 } 124 } 125 126 private final GLView mRootPane = new GLView() { 127 128 @Override 129 protected void renderBackground(GLCanvas view) { 130 view.clearBuffer(); 131 } 132 133 @Override 134 protected void onLayout( 135 boolean changed, int left, int top, int right, int bottom) { 136 mPhotoView.layout(0, 0, right - left, bottom - top); 137 PositionRepository.getInstance(mActivity).setOffset(0, 0); 138 int filmStripHeight = 0; 139 if (mFilmStripView != null) { 140 mFilmStripView.measure( 141 MeasureSpec.makeMeasureSpec(right - left, MeasureSpec.EXACTLY), 142 MeasureSpec.UNSPECIFIED); 143 filmStripHeight = mFilmStripView.getMeasuredHeight(); 144 mFilmStripView.layout(0, bottom - top - filmStripHeight, 145 right - left, bottom - top); 146 } 147 if (mShowDetails) { 148 mDetailsHelper.layout(left, GalleryActionBar.getHeight((Activity) mActivity), 149 right, bottom); 150 } 151 } 152 }; 153 154 private void initFilmStripView() { 155 Config.PhotoPage config = Config.PhotoPage.get((Context) mActivity); 156 mFilmStripView = new FilmStripView(mActivity, mMediaSet, 157 config.filmstripTopMargin, config.filmstripMidMargin, config.filmstripBottomMargin, 158 config.filmstripContentSize, config.filmstripThumbSize, config.filmstripBarSize, 159 config.filmstripGripSize, config.filmstripGripWidth); 160 mRootPane.addComponent(mFilmStripView); 161 mFilmStripView.setListener(this); 162 mFilmStripView.setUserInteractionListener(this); 163 mFilmStripView.setFocusIndex(mCurrentIndex); 164 mFilmStripView.setStartIndex(mCurrentIndex); 165 mRootPane.requestLayout(); 166 if (mIsActive) mFilmStripView.resume(); 167 if (!mShowBars) mFilmStripView.setVisibility(GLView.INVISIBLE); 168 } 169 170 @Override 171 public void onCreate(Bundle data, Bundle restoreState) { 172 mActionBar = ((Activity) mActivity).getActionBar(); 173 mSelectionManager = new SelectionManager(mActivity, false); 174 mMenuExecutor = new MenuExecutor(mActivity, mSelectionManager); 175 176 mPhotoView = new PhotoView(mActivity); 177 mPhotoView.setPhotoTapListener(this); 178 mRootPane.addComponent(mPhotoView); 179 mApplication = (GalleryApp)((Activity) mActivity).getApplication(); 180 181 mSetPathString = data.getString(KEY_MEDIA_SET_PATH); 182 Path itemPath = Path.fromString(data.getString(KEY_MEDIA_ITEM_PATH)); 183 184 if (mSetPathString != null) { 185 mMediaSet = mActivity.getDataManager().getMediaSet(mSetPathString); 186 mCurrentIndex = data.getInt(KEY_INDEX_HINT, 0); 187 mMediaSet = (MediaSet) 188 mActivity.getDataManager().getMediaObject(mSetPathString); 189 if (mMediaSet == null) { 190 Log.w(TAG, "failed to restore " + mSetPathString); 191 } 192 PhotoDataAdapter pda = new PhotoDataAdapter( 193 mActivity, mPhotoView, mMediaSet, itemPath, mCurrentIndex); 194 mModel = pda; 195 mPhotoView.setModel(mModel); 196 197 mResultIntent.putExtra(KEY_INDEX_HINT, mCurrentIndex); 198 setStateResult(Activity.RESULT_OK, mResultIntent); 199 200 pda.setDataListener(new PhotoDataAdapter.DataListener() { 201 202 @Override 203 public void onPhotoChanged(int index, Path item) { 204 if (mFilmStripView != null) mFilmStripView.setFocusIndex(index); 205 mCurrentIndex = index; 206 mResultIntent.putExtra(KEY_INDEX_HINT, index); 207 if (item != null) { 208 mResultIntent.putExtra(KEY_MEDIA_ITEM_PATH, item.toString()); 209 MediaItem photo = mModel.getCurrentMediaItem(); 210 if (photo != null) updateCurrentPhoto(photo); 211 } else { 212 mResultIntent.removeExtra(KEY_MEDIA_ITEM_PATH); 213 } 214 setStateResult(Activity.RESULT_OK, mResultIntent); 215 } 216 217 @Override 218 public void onLoadingFinished() { 219 GalleryUtils.setSpinnerVisibility((Activity) mActivity, false); 220 if (!mModel.isEmpty()) { 221 MediaItem photo = mModel.getCurrentMediaItem(); 222 if (photo != null) updateCurrentPhoto(photo); 223 } else if (mIsActive) { 224 mActivity.getStateManager().finishState(PhotoPage.this); 225 } 226 } 227 228 @Override 229 public void onLoadingStarted() { 230 GalleryUtils.setSpinnerVisibility((Activity) mActivity, true); 231 } 232 233 @Override 234 public void onPhotoAvailable(long version, boolean fullImage) { 235 if (mFilmStripView == null) initFilmStripView(); 236 } 237 }); 238 } else { 239 // Get default media set by the URI 240 MediaItem mediaItem = (MediaItem) 241 mActivity.getDataManager().getMediaObject(itemPath); 242 mModel = new SinglePhotoDataAdapter(mActivity, mPhotoView, mediaItem); 243 mPhotoView.setModel(mModel); 244 updateCurrentPhoto(mediaItem); 245 } 246 247 mHandler = new SynchronizedHandler(mActivity.getGLRoot()) { 248 @Override 249 public void handleMessage(Message message) { 250 switch (message.what) { 251 case MSG_HIDE_BARS: { 252 hideBars(); 253 break; 254 } 255 default: throw new AssertionError(message.what); 256 } 257 } 258 }; 259 260 // start the opening animation 261 mPhotoView.setOpenedItem(itemPath); 262 } 263 264 private void updateShareURI(Path path) { 265 if (mShareActionProvider != null) { 266 DataManager manager = mActivity.getDataManager(); 267 int type = manager.getMediaType(path); 268 Intent intent = new Intent(Intent.ACTION_SEND); 269 intent.setType(MenuExecutor.getMimeType(type)); 270 intent.putExtra(Intent.EXTRA_STREAM, manager.getContentUri(path)); 271 mShareActionProvider.setShareIntent(intent); 272 mPendingSharePath = null; 273 } else { 274 // This happens when ActionBar is not created yet. 275 mPendingSharePath = path; 276 } 277 } 278 279 private void setTitle(String title) { 280 if (title == null) return; 281 boolean showTitle = mActivity.getAndroidContext().getResources().getBoolean( 282 R.bool.show_action_bar_title); 283 if (showTitle) 284 mActionBar.setTitle(title); 285 else 286 mActionBar.setTitle(""); 287 } 288 289 private void updateCurrentPhoto(MediaItem photo) { 290 if (mCurrentPhoto == photo) return; 291 mCurrentPhoto = photo; 292 if (mCurrentPhoto == null) return; 293 updateMenuOperations(); 294 if (mShowDetails) { 295 mDetailsHelper.reloadDetails(mModel.getCurrentIndex()); 296 } 297 setTitle(photo.getName()); 298 mPhotoView.showVideoPlayIcon( 299 photo.getMediaType() == MediaObject.MEDIA_TYPE_VIDEO); 300 301 updateShareURI(photo.getPath()); 302 } 303 304 private void updateMenuOperations() { 305 if (mMenu == null) return; 306 MenuItem item = mMenu.findItem(R.id.action_slideshow); 307 if (item != null) { 308 item.setVisible(canDoSlideShow()); 309 } 310 if (mCurrentPhoto == null) return; 311 int supportedOperations = mCurrentPhoto.getSupportedOperations(); 312 if (!GalleryUtils.isEditorAvailable((Context) mActivity, "image/*")) { 313 supportedOperations &= ~MediaObject.SUPPORT_EDIT; 314 } 315 316 MenuExecutor.updateMenuOperation(mMenu, supportedOperations); 317 } 318 319 private boolean canDoSlideShow() { 320 if (mMediaSet == null || mCurrentPhoto == null) { 321 return false; 322 } 323 if (mCurrentPhoto.getMediaType() != MediaObject.MEDIA_TYPE_IMAGE) { 324 return false; 325 } 326 if (mMediaSet instanceof MtpDevice) { 327 return false; 328 } 329 return true; 330 } 331 332 private void showBars() { 333 if (mShowBars) return; 334 mShowBars = true; 335 mActionBar.show(); 336 WindowManager.LayoutParams params = ((Activity) mActivity).getWindow().getAttributes(); 337 params.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE; 338 ((Activity) mActivity).getWindow().setAttributes(params); 339 if (mFilmStripView != null) { 340 mFilmStripView.show(); 341 } 342 } 343 344 private void hideBars() { 345 if (!mShowBars) return; 346 mShowBars = false; 347 mActionBar.hide(); 348 WindowManager.LayoutParams params = ((Activity) mActivity).getWindow().getAttributes(); 349 params.systemUiVisibility = View. SYSTEM_UI_FLAG_LOW_PROFILE; 350 ((Activity) mActivity).getWindow().setAttributes(params); 351 if (mFilmStripView != null) { 352 mFilmStripView.hide(); 353 } 354 } 355 356 private void refreshHidingMessage() { 357 mHandler.removeMessages(MSG_HIDE_BARS); 358 if (!mIsMenuVisible && !mIsInteracting) { 359 mHandler.sendEmptyMessageDelayed(MSG_HIDE_BARS, HIDE_BARS_TIMEOUT); 360 } 361 } 362 363 @Override 364 public void onUserInteraction() { 365 showBars(); 366 refreshHidingMessage(); 367 } 368 369 public void onUserInteractionTap() { 370 if (mShowBars) { 371 hideBars(); 372 mHandler.removeMessages(MSG_HIDE_BARS); 373 } else { 374 showBars(); 375 refreshHidingMessage(); 376 } 377 } 378 379 @Override 380 public void onUserInteractionBegin() { 381 showBars(); 382 mIsInteracting = true; 383 refreshHidingMessage(); 384 } 385 386 @Override 387 public void onUserInteractionEnd() { 388 mIsInteracting = false; 389 390 // This function could be called from GL thread (in SlotView.render) 391 // and post to the main thread. So, it could be executed while the 392 // activity is paused. 393 if (mIsActive) refreshHidingMessage(); 394 } 395 396 @Override 397 protected void onBackPressed() { 398 if (mShowDetails) { 399 hideDetails(); 400 } else { 401 PositionRepository repository = PositionRepository.getInstance(mActivity); 402 repository.clear(); 403 if (mCurrentPhoto != null) { 404 Position position = new Position(); 405 position.x = mRootPane.getWidth() / 2; 406 position.y = mRootPane.getHeight() / 2; 407 position.z = -1000; 408 repository.putPosition( 409 System.identityHashCode(mCurrentPhoto.getPath()), 410 position); 411 } 412 super.onBackPressed(); 413 } 414 } 415 416 @Override 417 protected boolean onCreateActionBar(Menu menu) { 418 MenuInflater inflater = ((Activity) mActivity).getMenuInflater(); 419 inflater.inflate(R.menu.photo, menu); 420 mShareActionProvider = GalleryActionBar.initializeShareActionProvider(menu); 421 if (mPendingSharePath != null) updateShareURI(mPendingSharePath); 422 mMenu = menu; 423 mShowBars = true; 424 updateMenuOperations(); 425 return true; 426 } 427 428 @Override 429 protected boolean onItemSelected(MenuItem item) { 430 MediaItem current = mModel.getCurrentMediaItem(); 431 432 if (current == null) { 433 // item is not ready, ignore 434 return true; 435 } 436 437 int currentIndex = mModel.getCurrentIndex(); 438 Path path = current.getPath(); 439 440 DataManager manager = mActivity.getDataManager(); 441 int action = item.getItemId(); 442 switch (action) { 443 case android.R.id.home: { 444 if (mSetPathString != null) { 445 if (mActivity.getStateManager().getStateCount() > 1) { 446 onBackPressed(); 447 } else { 448 Activity a = (Activity) mActivity; 449 Uri uri = mActivity.getDataManager().getContentUri( 450 Path.fromString(mSetPathString)); 451 Intent intent = new Intent(Intent.ACTION_VIEW) 452 .setClass(a, Gallery.class) 453 .setDataAndType(uri, ContentResolver.CURSOR_DIR_BASE_TYPE) 454 .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | 455 Intent.FLAG_ACTIVITY_NEW_TASK); 456 a.startActivity(intent); 457 } 458 } 459 return true; 460 } 461 case R.id.action_slideshow: { 462 Bundle data = new Bundle(); 463 data.putString(SlideshowPage.KEY_SET_PATH, mMediaSet.getPath().toString()); 464 data.putString(SlideshowPage.KEY_ITEM_PATH, path.toString()); 465 data.putInt(SlideshowPage.KEY_PHOTO_INDEX, currentIndex); 466 data.putBoolean(SlideshowPage.KEY_REPEAT, true); 467 mActivity.getStateManager().startStateForResult( 468 SlideshowPage.class, REQUEST_SLIDESHOW, data); 469 return true; 470 } 471 case R.id.action_crop: { 472 Activity activity = (Activity) mActivity; 473 Intent intent = new Intent(CropImage.CROP_ACTION); 474 intent.setClass(activity, CropImage.class); 475 intent.setData(manager.getContentUri(path)); 476 activity.startActivityForResult(intent, PicasaSource.isPicasaImage(current) 477 ? REQUEST_CROP_PICASA 478 : REQUEST_CROP); 479 return true; 480 } 481 case R.id.action_details: { 482 if (mShowDetails) { 483 hideDetails(); 484 } else { 485 showDetails(currentIndex); 486 } 487 return true; 488 } 489 case R.id.action_setas: 490 case R.id.action_confirm_delete: 491 case R.id.action_rotate_ccw: 492 case R.id.action_rotate_cw: 493 case R.id.action_show_on_map: 494 case R.id.action_edit: 495 mSelectionManager.deSelectAll(); 496 mSelectionManager.toggle(path); 497 mMenuExecutor.onMenuClicked(item, null); 498 return true; 499 case R.id.action_import: 500 mSelectionManager.deSelectAll(); 501 mSelectionManager.toggle(path); 502 mMenuExecutor.onMenuClicked(item, 503 new ImportCompleteListener(mActivity)); 504 return true; 505 default : 506 return false; 507 } 508 } 509 510 private void hideDetails() { 511 mShowDetails = false; 512 mDetailsHelper.hide(); 513 } 514 515 private void showDetails(int index) { 516 mShowDetails = true; 517 if (mDetailsHelper == null) { 518 mDetailsHelper = new DetailsHelper(mActivity, mRootPane, new MyDetailsSource()); 519 mDetailsHelper.setCloseListener(new CloseListener() { 520 public void onClose() { 521 hideDetails(); 522 } 523 }); 524 } 525 mDetailsHelper.reloadDetails(index); 526 mDetailsHelper.show(); 527 } 528 529 public void onSingleTapUp(int x, int y) { 530 MediaItem item = mModel.getCurrentMediaItem(); 531 if (item == null) { 532 // item is not ready, ignore 533 return; 534 } 535 536 boolean playVideo = 537 (item.getSupportedOperations() & MediaItem.SUPPORT_PLAY) != 0; 538 539 if (playVideo) { 540 // determine if the point is at center (1/6) of the photo view. 541 // (The position of the "play" icon is at center (1/6) of the photo) 542 int w = mPhotoView.getWidth(); 543 int h = mPhotoView.getHeight(); 544 playVideo = (Math.abs(x - w / 2) * 12 <= w) 545 && (Math.abs(y - h / 2) * 12 <= h); 546 } 547 548 if (playVideo) { 549 playVideo((Activity) mActivity, item.getPlayUri(), item.getName()); 550 } else { 551 onUserInteractionTap(); 552 } 553 } 554 555 public static void playVideo(Activity activity, Uri uri, String title) { 556 try { 557 Intent intent = new Intent(Intent.ACTION_VIEW) 558 .setDataAndType(uri, "video/*"); 559 intent.putExtra(Intent.EXTRA_TITLE, title); 560 activity.startActivity(intent); 561 } catch (ActivityNotFoundException e) { 562 Toast.makeText(activity, activity.getString(R.string.video_err), 563 Toast.LENGTH_SHORT).show(); 564 } 565 } 566 567 // Called by FileStripView. 568 // Returns false if it cannot jump to the specified index at this time. 569 public boolean onSlotSelected(int slotIndex) { 570 return mPhotoView.jumpTo(slotIndex); 571 } 572 573 @Override 574 protected void onStateResult(int requestCode, int resultCode, Intent data) { 575 switch (requestCode) { 576 case REQUEST_CROP: 577 if (resultCode == Activity.RESULT_OK) { 578 if (data == null) break; 579 Path path = mApplication 580 .getDataManager().findPathByUri(data.getData()); 581 if (path != null) { 582 mModel.setCurrentPhoto(path, mCurrentIndex); 583 } 584 } 585 break; 586 case REQUEST_CROP_PICASA: { 587 int message = resultCode == Activity.RESULT_OK 588 ? R.string.crop_saved 589 : R.string.crop_not_saved; 590 Toast.makeText(mActivity.getAndroidContext(), 591 message, Toast.LENGTH_SHORT).show(); 592 break; 593 } 594 case REQUEST_SLIDESHOW: { 595 if (data == null) break; 596 String path = data.getStringExtra(SlideshowPage.KEY_ITEM_PATH); 597 int index = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0); 598 if (path != null) { 599 mModel.setCurrentPhoto(Path.fromString(path), index); 600 } 601 } 602 } 603 } 604 605 @Override 606 public void onPause() { 607 super.onPause(); 608 mIsActive = false; 609 if (mFilmStripView != null) { 610 mFilmStripView.pause(); 611 } 612 DetailsHelper.pause(); 613 mPhotoView.pause(); 614 mModel.pause(); 615 mHandler.removeMessages(MSG_HIDE_BARS); 616 mActionBar.removeOnMenuVisibilityListener(mMenuVisibilityListener); 617 mMenuExecutor.pause(); 618 } 619 620 @Override 621 protected void onResume() { 622 super.onResume(); 623 mIsActive = true; 624 setContentPane(mRootPane); 625 mModel.resume(); 626 mPhotoView.resume(); 627 if (mFilmStripView != null) { 628 mFilmStripView.resume(); 629 } 630 if (mMenuVisibilityListener == null) { 631 mMenuVisibilityListener = new MyMenuVisibilityListener(); 632 } 633 mActivity.getGalleryActionBar().setDisplayOptions(mSetPathString != null, true); 634 635 mActionBar.addOnMenuVisibilityListener(mMenuVisibilityListener); 636 onUserInteraction(); 637 } 638 639 private class MyDetailsSource implements DetailsSource { 640 private int mIndex; 641 642 @Override 643 public MediaDetails getDetails() { 644 return mModel.getCurrentMediaItem().getDetails(); 645 } 646 647 @Override 648 public int size() { 649 return mMediaSet != null ? mMediaSet.getMediaItemCount() : 1; 650 } 651 652 @Override 653 public int findIndex(int indexHint) { 654 mIndex = indexHint; 655 return indexHint; 656 } 657 658 @Override 659 public int getIndex() { 660 return mIndex; 661 } 662 } 663} 664