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