AlbumPage.java revision 48ba94ae713dbf57898cfa84ae69517da50cf7a0
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.Activity; 20import android.content.Context; 21import android.content.Intent; 22import android.graphics.Rect; 23import android.net.Uri; 24import android.os.Bundle; 25import android.os.Vibrator; 26import android.provider.MediaStore; 27import android.view.ActionMode; 28import android.view.Menu; 29import android.view.MenuInflater; 30import android.view.MenuItem; 31import android.widget.Toast; 32 33import com.android.gallery3d.R; 34import com.android.gallery3d.common.Utils; 35import com.android.gallery3d.data.DataManager; 36import com.android.gallery3d.data.MediaDetails; 37import com.android.gallery3d.data.MediaItem; 38import com.android.gallery3d.data.MediaObject; 39import com.android.gallery3d.data.MediaSet; 40import com.android.gallery3d.data.MtpDevice; 41import com.android.gallery3d.data.Path; 42import com.android.gallery3d.ui.ActionModeHandler; 43import com.android.gallery3d.ui.ActionModeHandler.ActionModeListener; 44import com.android.gallery3d.ui.AlbumSlotRenderer; 45import com.android.gallery3d.ui.DetailsHelper; 46import com.android.gallery3d.ui.DetailsHelper.CloseListener; 47import com.android.gallery3d.ui.GLCanvas; 48import com.android.gallery3d.ui.GLRoot; 49import com.android.gallery3d.ui.GLView; 50import com.android.gallery3d.ui.RelativePosition; 51import com.android.gallery3d.ui.ScreenNailHolder; 52import com.android.gallery3d.ui.SelectionManager; 53import com.android.gallery3d.ui.SlotView; 54import com.android.gallery3d.util.Future; 55import com.android.gallery3d.util.GalleryUtils; 56 57public class AlbumPage extends ActivityState implements GalleryActionBar.ClusterRunner, 58 SelectionManager.SelectionListener, MediaSet.SyncListener { 59 @SuppressWarnings("unused") 60 private static final String TAG = "AlbumPage"; 61 62 public static final String KEY_MEDIA_PATH = "media-path"; 63 public static final String KEY_PARENT_MEDIA_PATH = "parent-media-path"; 64 public static final String KEY_SET_CENTER = "set-center"; 65 public static final String KEY_AUTO_SELECT_ALL = "auto-select-all"; 66 public static final String KEY_SHOW_CLUSTER_MENU = "cluster-menu"; 67 68 private static final int REQUEST_SLIDESHOW = 1; 69 private static final int REQUEST_PHOTO = 2; 70 private static final int REQUEST_DO_ANIMATION = 3; 71 72 private static final int BIT_LOADING_RELOAD = 1; 73 private static final int BIT_LOADING_SYNC = 2; 74 75 private static final float USER_DISTANCE_METER = 0.3f; 76 private static final boolean TEST_CAMERA_PREVIEW = false; 77 78 private boolean mIsActive = false; 79 private AlbumSlotRenderer mAlbumView; 80 private Path mMediaSetPath; 81 private String mParentMediaSetString; 82 private SlotView mSlotView; 83 84 private AlbumDataLoader mAlbumDataAdapter; 85 86 protected SelectionManager mSelectionManager; 87 private Vibrator mVibrator; 88 89 private boolean mGetContent; 90 private boolean mShowClusterMenu; 91 92 private ActionMode mActionMode; 93 private ActionModeHandler mActionModeHandler; 94 private int mFocusIndex = 0; 95 private DetailsHelper mDetailsHelper; 96 private MyDetailsSource mDetailsSource; 97 private MediaSet mMediaSet; 98 private boolean mShowDetails; 99 private float mUserDistance; // in pixel 100 101 private Future<Integer> mSyncTask = null; 102 103 private int mLoadingBits = 0; 104 private boolean mInitialSynced = false; 105 private RelativePosition mOpenCenter = new RelativePosition(); 106 107 private final GLView mRootPane = new GLView() { 108 private final float mMatrix[] = new float[16]; 109 110 @Override 111 protected void renderBackground(GLCanvas view) { 112 view.clearBuffer(); 113 } 114 115 @Override 116 protected void onLayout( 117 boolean changed, int left, int top, int right, int bottom) { 118 119 int slotViewTop = mActivity.getGalleryActionBar().getHeight(); 120 int slotViewBottom = bottom - top; 121 int slotViewRight = right - left; 122 123 if (mShowDetails) { 124 mDetailsHelper.layout(left, slotViewTop, right, bottom); 125 } else { 126 mAlbumView.setHighlightItemPath(null); 127 } 128 129 // Set the mSlotView as a reference point to the open animation 130 mOpenCenter.setReferencePosition(0, slotViewTop); 131 mSlotView.layout(0, slotViewTop, slotViewRight, slotViewBottom); 132 GalleryUtils.setViewPointMatrix(mMatrix, 133 (right - left) / 2, (bottom - top) / 2, -mUserDistance); 134 } 135 136 @Override 137 protected void render(GLCanvas canvas) { 138 canvas.save(GLCanvas.SAVE_FLAG_MATRIX); 139 canvas.multiplyMatrix(mMatrix, 0); 140 super.render(canvas); 141 canvas.restore(); 142 } 143 }; 144 145 @Override 146 protected void onBackPressed() { 147 if (mShowDetails) { 148 hideDetails(); 149 } else if (mSelectionManager.inSelectionMode()) { 150 mSelectionManager.leaveSelectionMode(); 151 } else { 152 // TODO: fix this regression 153 // mAlbumView.savePositions(PositionRepository.getInstance(mActivity)); 154 super.onBackPressed(); 155 } 156 } 157 158 private void onDown(int index) { 159 mAlbumView.setPressedIndex(index); 160 } 161 162 private void onUp() { 163 mAlbumView.setPressedIndex(-1); 164 } 165 166 private void onSingleTapUp(int slotIndex) { 167 if (!mIsActive) return; 168 MediaItem item = mAlbumDataAdapter.get(slotIndex); 169 if (item == null) { 170 Log.w(TAG, "item not ready yet, ignore the click"); 171 return; 172 } 173 if (mShowDetails) { 174 mAlbumView.setHighlightItemPath(item.getPath()); 175 mDetailsHelper.reloadDetails(slotIndex); 176 } else if (!mSelectionManager.inSelectionMode()) { 177 if (mGetContent) { 178 onGetContent(item); 179 } else { 180 // Get into the PhotoPage. 181 Bundle data = new Bundle(); 182 183 // mAlbumView.savePositions(PositionRepository.getInstance(mActivity)); 184 data.putInt(PhotoPage.KEY_INDEX_HINT, slotIndex); 185 data.putParcelable(PhotoPage.KEY_OPEN_ANIMATION_RECT, 186 getSlotRect(slotIndex)); 187 data.putString(PhotoPage.KEY_MEDIA_SET_PATH, 188 mMediaSetPath.toString()); 189 data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, 190 item.getPath().toString()); 191 if (TEST_CAMERA_PREVIEW) { 192 ScreenNailHolder holder = new CameraScreenNailHolder(mActivity); 193 data.putParcelable(PhotoPage.KEY_SCREENNAIL_HOLDER, holder); 194 } 195 mActivity.getStateManager().startStateForResult( 196 PhotoPage.class, REQUEST_PHOTO, data); 197 } 198 } else { 199 mSelectionManager.toggle(item.getPath()); 200 mDetailsSource.findIndex(slotIndex); 201 mSlotView.invalidate(); 202 } 203 } 204 205 private Rect getSlotRect(int slotIndex) { 206 // Get slot rectangle relative to this root pane. 207 Rect offset = new Rect(); 208 mRootPane.getBoundsOf(mSlotView, offset); 209 Rect r = mSlotView.getSlotRect(slotIndex); 210 r.offset(offset.left - mSlotView.getScrollX(), 211 offset.top - mSlotView.getScrollY()); 212 return r; 213 } 214 215 private void onGetContent(final MediaItem item) { 216 DataManager dm = mActivity.getDataManager(); 217 Activity activity = (Activity) mActivity; 218 if (mData.getString(Gallery.EXTRA_CROP) != null) { 219 // TODO: Handle MtpImagew 220 Uri uri = dm.getContentUri(item.getPath()); 221 Intent intent = new Intent(CropImage.ACTION_CROP, uri) 222 .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT) 223 .putExtras(getData()); 224 if (mData.getParcelable(MediaStore.EXTRA_OUTPUT) == null) { 225 intent.putExtra(CropImage.KEY_RETURN_DATA, true); 226 } 227 activity.startActivity(intent); 228 activity.finish(); 229 } else { 230 activity.setResult(Activity.RESULT_OK, 231 new Intent(null, item.getContentUri())); 232 activity.finish(); 233 } 234 } 235 236 public void onLongTap(int slotIndex) { 237 if (mGetContent) return; 238 if (mShowDetails) { 239 onSingleTapUp(slotIndex); 240 } else { 241 MediaItem item = mAlbumDataAdapter.get(slotIndex); 242 if (item == null) return; 243 mSelectionManager.setAutoLeaveSelectionMode(true); 244 mSelectionManager.toggle(item.getPath()); 245 mDetailsSource.findIndex(slotIndex); 246 mSlotView.invalidate(); 247 } 248 } 249 250 @Override 251 public void doCluster(int clusterType) { 252 String basePath = mMediaSet.getPath().toString(); 253 String newPath = FilterUtils.newClusterPath(basePath, clusterType); 254 Bundle data = new Bundle(getData()); 255 data.putString(AlbumSetPage.KEY_MEDIA_PATH, newPath); 256 if (mShowClusterMenu) { 257 Context context = mActivity.getAndroidContext(); 258 data.putString(AlbumSetPage.KEY_SET_TITLE, mMediaSet.getName()); 259 data.putString(AlbumSetPage.KEY_SET_SUBTITLE, 260 GalleryActionBar.getClusterByTypeString(context, clusterType)); 261 } 262 263 // mAlbumView.savePositions(PositionRepository.getInstance(mActivity)); 264 mActivity.getStateManager().startStateForResult( 265 AlbumSetPage.class, REQUEST_DO_ANIMATION, data); 266 } 267 268 @Override 269 protected void onCreate(Bundle data, Bundle restoreState) { 270 mUserDistance = GalleryUtils.meterToPixel(USER_DISTANCE_METER); 271 initializeViews(); 272 initializeData(data); 273 mGetContent = data.getBoolean(Gallery.KEY_GET_CONTENT, false); 274 mShowClusterMenu = data.getBoolean(KEY_SHOW_CLUSTER_MENU, false); 275 mDetailsSource = new MyDetailsSource(); 276 Context context = mActivity.getAndroidContext(); 277 mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); 278 279 // Enable auto-select-all for mtp album 280 if (data.getBoolean(KEY_AUTO_SELECT_ALL)) { 281 mSelectionManager.selectAll(); 282 } 283 284 // Don't show animation if it is restored 285 if (restoreState == null && data != null) { 286 int[] center = data.getIntArray(KEY_SET_CENTER); 287 if (center != null) { 288 mOpenCenter.setAbsolutePosition(center[0], center[1]); 289 mSlotView.startScatteringAnimation(mOpenCenter); 290 } 291 } 292 } 293 294 @Override 295 protected void onResume() { 296 super.onResume(); 297 mIsActive = true; 298 setContentPane(mRootPane); 299 300 Path path = mMediaSet.getPath(); 301 boolean enableHomeButton = (mActivity.getStateManager().getStateCount() > 1) | 302 mParentMediaSetString != null; 303 mActivity.getGalleryActionBar().setDisplayOptions(enableHomeButton, true); 304 305 // Set the reload bit here to prevent it exit this page in clearLoadingBit(). 306 setLoadingBit(BIT_LOADING_RELOAD); 307 mAlbumDataAdapter.resume(); 308 309 mAlbumView.resume(); 310 mActionModeHandler.resume(); 311 if (!mInitialSynced) { 312 setLoadingBit(BIT_LOADING_SYNC); 313 mSyncTask = mMediaSet.requestSync(this); 314 } 315 } 316 317 @Override 318 protected void onPause() { 319 super.onPause(); 320 mIsActive = false; 321 mAlbumDataAdapter.pause(); 322 mAlbumView.pause(); 323 DetailsHelper.pause(); 324 325 if (mSyncTask != null) { 326 mSyncTask.cancel(); 327 mSyncTask = null; 328 clearLoadingBit(BIT_LOADING_SYNC); 329 } 330 mActionModeHandler.pause(); 331 GalleryUtils.setSpinnerVisibility((Activity) mActivity, false); 332 } 333 334 @Override 335 protected void onDestroy() { 336 super.onDestroy(); 337 if (mAlbumDataAdapter != null) { 338 mAlbumDataAdapter.setLoadingListener(null); 339 } 340 } 341 342 private void initializeViews() { 343 mSelectionManager = new SelectionManager(mActivity, false); 344 mSelectionManager.setSelectionListener(this); 345 Config.AlbumPage config = Config.AlbumPage.get((Context) mActivity); 346 mSlotView = new SlotView(mActivity, config.slotViewSpec); 347 mAlbumView = new AlbumSlotRenderer(mActivity, mSlotView, mSelectionManager); 348 mSlotView.setSlotRenderer(mAlbumView); 349 mRootPane.addComponent(mSlotView); 350 mSlotView.setListener(new SlotView.SimpleListener() { 351 @Override 352 public void onDown(int index) { 353 AlbumPage.this.onDown(index); 354 } 355 356 @Override 357 public void onUp() { 358 AlbumPage.this.onUp(); 359 } 360 361 @Override 362 public void onSingleTapUp(int slotIndex) { 363 AlbumPage.this.onSingleTapUp(slotIndex); 364 } 365 366 @Override 367 public void onLongTap(int slotIndex) { 368 AlbumPage.this.onLongTap(slotIndex); 369 } 370 }); 371 mActionModeHandler = new ActionModeHandler(mActivity, mSelectionManager); 372 mActionModeHandler.setActionModeListener(new ActionModeListener() { 373 public boolean onActionItemClicked(MenuItem item) { 374 return onItemSelected(item); 375 } 376 }); 377 } 378 379 private void initializeData(Bundle data) { 380 mMediaSetPath = Path.fromString(data.getString(KEY_MEDIA_PATH)); 381 mParentMediaSetString = data.getString(KEY_PARENT_MEDIA_PATH); 382 mMediaSet = mActivity.getDataManager().getMediaSet(mMediaSetPath); 383 if (mMediaSet == null) { 384 Utils.fail("MediaSet is null. Path = %s", mMediaSetPath); 385 } 386 mSelectionManager.setSourceMediaSet(mMediaSet); 387 mAlbumDataAdapter = new AlbumDataLoader(mActivity, mMediaSet); 388 mAlbumDataAdapter.setLoadingListener(new MyLoadingListener()); 389 mAlbumView.setModel(mAlbumDataAdapter); 390 } 391 392 private void showDetails() { 393 mShowDetails = true; 394 if (mDetailsHelper == null) { 395 mDetailsHelper = new DetailsHelper(mActivity, mRootPane, mDetailsSource); 396 mDetailsHelper.setCloseListener(new CloseListener() { 397 public void onClose() { 398 hideDetails(); 399 } 400 }); 401 } 402 mDetailsHelper.show(); 403 } 404 405 private void hideDetails() { 406 mShowDetails = false; 407 mDetailsHelper.hide(); 408 mAlbumView.setHighlightItemPath(null); 409 mSlotView.invalidate(); 410 } 411 412 @Override 413 protected boolean onCreateActionBar(Menu menu) { 414 Activity activity = (Activity) mActivity; 415 GalleryActionBar actionBar = mActivity.getGalleryActionBar(); 416 MenuInflater inflater = activity.getMenuInflater(); 417 418 if (mGetContent) { 419 inflater.inflate(R.menu.pickup, menu); 420 int typeBits = mData.getInt(Gallery.KEY_TYPE_BITS, 421 DataManager.INCLUDE_IMAGE); 422 423 actionBar.setTitle(GalleryUtils.getSelectionModePrompt(typeBits)); 424 } else { 425 inflater.inflate(R.menu.album, menu); 426 actionBar.setTitle(mMediaSet.getName()); 427 if (mMediaSet instanceof MtpDevice) { 428 menu.findItem(R.id.action_slideshow).setVisible(false); 429 } else { 430 menu.findItem(R.id.action_slideshow).setVisible(true); 431 } 432 433 MenuItem groupBy = menu.findItem(R.id.action_group_by); 434 FilterUtils.setupMenuItems(actionBar, mMediaSetPath, true); 435 436 if (groupBy != null) { 437 groupBy.setVisible(mShowClusterMenu); 438 } 439 440 actionBar.setTitle(mMediaSet.getName()); 441 } 442 actionBar.setSubtitle(null); 443 444 return true; 445 } 446 447 @Override 448 protected boolean onItemSelected(MenuItem item) { 449 switch (item.getItemId()) { 450 case android.R.id.home: { 451 if (mActivity.getStateManager().getStateCount() > 1) { 452 onBackPressed(); 453 } else if (mParentMediaSetString != null) { 454 Bundle data = new Bundle(getData()); 455 data.putString(AlbumSetPage.KEY_MEDIA_PATH, mParentMediaSetString); 456 mActivity.getStateManager().switchState(this, AlbumSetPage.class, data); 457 } 458 return true; 459 } 460 case R.id.action_cancel: 461 mActivity.getStateManager().finishState(this); 462 return true; 463 case R.id.action_select: 464 mSelectionManager.setAutoLeaveSelectionMode(false); 465 mSelectionManager.enterSelectionMode(); 466 return true; 467 case R.id.action_group_by: { 468 mActivity.getGalleryActionBar().showClusterDialog(this); 469 return true; 470 } 471 case R.id.action_slideshow: { 472 Bundle data = new Bundle(); 473 data.putString(SlideshowPage.KEY_SET_PATH, 474 mMediaSetPath.toString()); 475 data.putBoolean(SlideshowPage.KEY_REPEAT, true); 476 mActivity.getStateManager().startStateForResult( 477 SlideshowPage.class, REQUEST_SLIDESHOW, data); 478 return true; 479 } 480 case R.id.action_details: { 481 if (mShowDetails) { 482 hideDetails(); 483 } else { 484 showDetails(); 485 } 486 return true; 487 } 488 default: 489 return false; 490 } 491 } 492 493 @Override 494 protected void onStateResult(int request, int result, Intent data) { 495 switch (request) { 496 case REQUEST_SLIDESHOW: { 497 // data could be null, if there is no images in the album 498 if (data == null) return; 499 mFocusIndex = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0); 500 mSlotView.setCenterIndex(mFocusIndex); 501 break; 502 } 503 case REQUEST_PHOTO: { 504 if (data == null) return; 505 mFocusIndex = data.getIntExtra(PhotoPage.KEY_INDEX_HINT, 0); 506 mSlotView.setCenterIndex(mFocusIndex); 507 mSlotView.startRestoringAnimation(mFocusIndex); 508 break; 509 } 510 case REQUEST_DO_ANIMATION: { 511 mSlotView.startRisingAnimation(); 512 break; 513 } 514 } 515 } 516 517 public void onSelectionModeChange(int mode) { 518 switch (mode) { 519 case SelectionManager.ENTER_SELECTION_MODE: { 520 mActionMode = mActionModeHandler.startActionMode(); 521 mVibrator.vibrate(100); 522 break; 523 } 524 case SelectionManager.LEAVE_SELECTION_MODE: { 525 mActionMode.finish(); 526 mRootPane.invalidate(); 527 break; 528 } 529 case SelectionManager.SELECT_ALL_MODE: { 530 mActionModeHandler.updateSupportedOperation(); 531 mRootPane.invalidate(); 532 break; 533 } 534 } 535 } 536 537 public void onSelectionChange(Path path, boolean selected) { 538 Utils.assertTrue(mActionMode != null); 539 int count = mSelectionManager.getSelectedCount(); 540 String format = mActivity.getResources().getQuantityString( 541 R.plurals.number_of_items_selected, count); 542 mActionModeHandler.setTitle(String.format(format, count)); 543 mActionModeHandler.updateSupportedOperation(path, selected); 544 } 545 546 @Override 547 public void onSyncDone(final MediaSet mediaSet, final int resultCode) { 548 Log.d(TAG, "onSyncDone: " + Utils.maskDebugInfo(mediaSet.getName()) + " result=" 549 + resultCode); 550 ((Activity) mActivity).runOnUiThread(new Runnable() { 551 @Override 552 public void run() { 553 GLRoot root = mActivity.getGLRoot(); 554 root.lockRenderThread(); 555 try { 556 if (resultCode == MediaSet.SYNC_RESULT_SUCCESS) { 557 mInitialSynced = true; 558 } 559 clearLoadingBit(BIT_LOADING_SYNC); 560 if (resultCode == MediaSet.SYNC_RESULT_ERROR && mIsActive) { 561 Toast.makeText((Context) mActivity, R.string.sync_album_error, 562 Toast.LENGTH_LONG).show(); 563 } 564 } finally { 565 root.unlockRenderThread(); 566 } 567 } 568 }); 569 } 570 571 private void setLoadingBit(int loadTaskBit) { 572 if (mLoadingBits == 0 && mIsActive) { 573 GalleryUtils.setSpinnerVisibility((Activity) mActivity, true); 574 } 575 mLoadingBits |= loadTaskBit; 576 } 577 578 private void clearLoadingBit(int loadTaskBit) { 579 mLoadingBits &= ~loadTaskBit; 580 if (mLoadingBits == 0 && mIsActive) { 581 GalleryUtils.setSpinnerVisibility((Activity) mActivity, false); 582 583 if (mAlbumDataAdapter.size() == 0) { 584 Toast.makeText((Context) mActivity, 585 R.string.empty_album, Toast.LENGTH_LONG).show(); 586 mActivity.getStateManager().finishState(AlbumPage.this); 587 } 588 } 589 } 590 591 private class MyLoadingListener implements LoadingListener { 592 @Override 593 public void onLoadingStarted() { 594 setLoadingBit(BIT_LOADING_RELOAD); 595 } 596 597 @Override 598 public void onLoadingFinished() { 599 clearLoadingBit(BIT_LOADING_RELOAD); 600 } 601 } 602 603 private class MyDetailsSource implements DetailsHelper.DetailsSource { 604 private int mIndex; 605 606 public int size() { 607 return mAlbumDataAdapter.size(); 608 } 609 610 public int getIndex() { 611 return mIndex; 612 } 613 614 // If requested index is out of active window, suggest a valid index. 615 // If there is no valid index available, return -1. 616 public int findIndex(int indexHint) { 617 if (mAlbumDataAdapter.isActive(indexHint)) { 618 mIndex = indexHint; 619 } else { 620 mIndex = mAlbumDataAdapter.getActiveStart(); 621 if (!mAlbumDataAdapter.isActive(mIndex)) { 622 return -1; 623 } 624 } 625 return mIndex; 626 } 627 628 public MediaDetails getDetails() { 629 MediaObject item = mAlbumDataAdapter.get(mIndex); 630 if (item != null) { 631 mAlbumView.setHighlightItemPath(item.getPath()); 632 return item.getDetails(); 633 } else { 634 return null; 635 } 636 } 637 } 638} 639