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