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