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