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