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