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.ui.StaticBackground; 56import com.android.gallery3d.util.Future; 57import com.android.gallery3d.util.GalleryUtils; 58 59import java.util.Random; 60 61public class AlbumPage extends ActivityState implements GalleryActionBar.ClusterRunner, 62 SelectionManager.SelectionListener, MediaSet.SyncListener { 63 @SuppressWarnings("unused") 64 private static final String TAG = "AlbumPage"; 65 66 public static final String KEY_MEDIA_PATH = "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 StaticBackground mStaticBackground; 82 private AlbumView mAlbumView; 83 private Path mMediaSetPath; 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 onLayout( 114 boolean changed, int left, int top, int right, int bottom) { 115 mStaticBackground.layout(0, 0, right - left, bottom - top); 116 117 int slotViewTop = GalleryActionBar.getHeight((Activity) mActivity); 118 int slotViewBottom = bottom - top; 119 int slotViewRight = right - left; 120 121 if (mShowDetails) { 122 mDetailsHelper.layout(left, slotViewTop, right, bottom); 123 } else { 124 mAlbumView.setSelectionDrawer(mGridDrawer); 125 } 126 127 mAlbumView.layout(0, slotViewTop, slotViewRight, slotViewBottom); 128 GalleryUtils.setViewPointMatrix(mMatrix, 129 (right - left) / 2, (bottom - top) / 2, -mUserDistance); 130 PositionRepository.getInstance(mActivity).setOffset( 131 0, slotViewTop); 132 } 133 134 @Override 135 protected void render(GLCanvas canvas) { 136 canvas.save(GLCanvas.SAVE_FLAG_MATRIX); 137 canvas.multiplyMatrix(mMatrix, 0); 138 super.render(canvas); 139 canvas.restore(); 140 } 141 }; 142 143 @Override 144 protected void onBackPressed() { 145 if (mShowDetails) { 146 hideDetails(); 147 } else if (mSelectionManager.inSelectionMode()) { 148 mSelectionManager.leaveSelectionMode(); 149 } else { 150 mAlbumView.savePositions(PositionRepository.getInstance(mActivity)); 151 super.onBackPressed(); 152 } 153 } 154 155 private void onDown(int index) { 156 MediaItem item = mAlbumDataAdapter.get(index); 157 Path path = (item == null) ? null : item.getPath(); 158 mSelectionManager.setPressedPath(path); 159 mAlbumView.invalidate(); 160 } 161 162 private void onUp() { 163 mSelectionManager.setPressedPath(null); 164 mAlbumView.invalidate(); 165 } 166 167 public void onSingleTapUp(int slotIndex) { 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 mHighlightDrawer.setHighlightItem(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 mAlbumView.savePositions(PositionRepository.getInstance(mActivity)); 183 data.putInt(PhotoPage.KEY_INDEX_HINT, slotIndex); 184 data.putString(PhotoPage.KEY_MEDIA_SET_PATH, 185 mMediaSetPath.toString()); 186 data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, 187 item.getPath().toString()); 188 mActivity.getStateManager().startStateForResult( 189 PhotoPage.class, REQUEST_PHOTO, data); 190 } 191 } else { 192 mSelectionManager.toggle(item.getPath()); 193 mDetailsSource.findIndex(slotIndex); 194 mAlbumView.invalidate(); 195 } 196 } 197 198 private void onGetContent(final MediaItem item) { 199 DataManager dm = mActivity.getDataManager(); 200 Activity activity = (Activity) mActivity; 201 if (mData.getString(Gallery.EXTRA_CROP) != null) { 202 // TODO: Handle MtpImagew 203 Uri uri = dm.getContentUri(item.getPath()); 204 Intent intent = new Intent(CropImage.ACTION_CROP, uri) 205 .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT) 206 .putExtras(getData()); 207 if (mData.getParcelable(MediaStore.EXTRA_OUTPUT) == null) { 208 intent.putExtra(CropImage.KEY_RETURN_DATA, true); 209 } 210 activity.startActivity(intent); 211 activity.finish(); 212 } else { 213 activity.setResult(Activity.RESULT_OK, 214 new Intent(null, item.getContentUri())); 215 activity.finish(); 216 } 217 } 218 219 public void onLongTap(int slotIndex) { 220 if (mGetContent) return; 221 if (mShowDetails) { 222 onSingleTapUp(slotIndex); 223 } else { 224 MediaItem item = mAlbumDataAdapter.get(slotIndex); 225 if (item == null) return; 226 mSelectionManager.setAutoLeaveSelectionMode(true); 227 mSelectionManager.toggle(item.getPath()); 228 mDetailsSource.findIndex(slotIndex); 229 mAlbumView.invalidate(); 230 } 231 } 232 233 public void doCluster(int clusterType) { 234 String basePath = mMediaSet.getPath().toString(); 235 String newPath = FilterUtils.newClusterPath(basePath, clusterType); 236 Bundle data = new Bundle(getData()); 237 data.putString(AlbumSetPage.KEY_MEDIA_PATH, newPath); 238 if (mShowClusterMenu) { 239 Context context = mActivity.getAndroidContext(); 240 data.putString(AlbumSetPage.KEY_SET_TITLE, mMediaSet.getName()); 241 data.putString(AlbumSetPage.KEY_SET_SUBTITLE, 242 GalleryActionBar.getClusterByTypeString(context, clusterType)); 243 } 244 245 mAlbumView.savePositions(PositionRepository.getInstance(mActivity)); 246 mActivity.getStateManager().startStateForResult( 247 AlbumSetPage.class, REQUEST_DO_ANIMATION, data); 248 } 249 250 public void doFilter(int filterType) { 251 String basePath = mMediaSet.getPath().toString(); 252 String newPath = FilterUtils.switchFilterPath(basePath, filterType); 253 Bundle data = new Bundle(getData()); 254 data.putString(AlbumPage.KEY_MEDIA_PATH, newPath); 255 mAlbumView.savePositions(PositionRepository.getInstance(mActivity)); 256 mActivity.getStateManager().switchState(this, AlbumPage.class, data); 257 } 258 259 public void onOperationComplete() { 260 mAlbumView.invalidate(); 261 // TODO: enable animation 262 } 263 264 @Override 265 protected void onCreate(Bundle data, Bundle restoreState) { 266 mUserDistance = GalleryUtils.meterToPixel(USER_DISTANCE_METER); 267 initializeViews(); 268 initializeData(data); 269 mGetContent = data.getBoolean(Gallery.KEY_GET_CONTENT, false); 270 mShowClusterMenu = data.getBoolean(KEY_SHOW_CLUSTER_MENU, false); 271 mDetailsSource = new MyDetailsSource(); 272 Context context = mActivity.getAndroidContext(); 273 mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); 274 275 startTransition(data); 276 277 // Enable auto-select-all for mtp album 278 if (data.getBoolean(KEY_AUTO_SELECT_ALL)) { 279 mSelectionManager.selectAll(); 280 } 281 } 282 283 private void startTransition() { 284 final PositionRepository repository = 285 PositionRepository.getInstance(mActivity); 286 mAlbumView.startTransition(new PositionProvider() { 287 private final Position mTempPosition = new Position(); 288 public Position getPosition(long identity, Position target) { 289 Position p = repository.get(identity); 290 if (p != null) return p; 291 mTempPosition.set(target); 292 mTempPosition.z = 128; 293 return mTempPosition; 294 } 295 }); 296 } 297 298 private void startTransition(Bundle data) { 299 final PositionRepository repository = 300 PositionRepository.getInstance(mActivity); 301 final int[] center = data == null 302 ? null 303 : data.getIntArray(KEY_SET_CENTER); 304 final Random random = new Random(); 305 mAlbumView.startTransition(new PositionProvider() { 306 private final Position mTempPosition = new Position(); 307 public Position getPosition(long identity, Position target) { 308 Position p = repository.get(identity); 309 if (p != null) return p; 310 if (center != null) { 311 random.setSeed(identity); 312 mTempPosition.set(center[0], center[1], 313 0, random.nextInt(60) - 30, 0); 314 } else { 315 mTempPosition.set(target); 316 mTempPosition.z = 128; 317 } 318 return mTempPosition; 319 } 320 }); 321 } 322 323 @Override 324 protected void onResume() { 325 super.onResume(); 326 mIsActive = true; 327 setContentPane(mRootPane); 328 329 // Set the reload bit here to prevent it exit this page in clearLoadingBit(). 330 setLoadingBit(BIT_LOADING_RELOAD); 331 mAlbumDataAdapter.resume(); 332 333 mAlbumView.resume(); 334 mActionModeHandler.resume(); 335 if (!mInitialSynced) { 336 setLoadingBit(BIT_LOADING_SYNC); 337 mSyncTask = mMediaSet.requestSync(this); 338 } 339 } 340 341 @Override 342 protected void onPause() { 343 super.onPause(); 344 mIsActive = false; 345 mAlbumDataAdapter.pause(); 346 mAlbumView.pause(); 347 DetailsHelper.pause(); 348 349 if (mSyncTask != null) { 350 mSyncTask.cancel(); 351 mSyncTask = null; 352 } 353 mActionModeHandler.pause(); 354 } 355 356 @Override 357 protected void onDestroy() { 358 super.onDestroy(); 359 if (mAlbumDataAdapter != null) { 360 mAlbumDataAdapter.setLoadingListener(null); 361 } 362 } 363 364 private void initializeViews() { 365 mStaticBackground = new StaticBackground((Context) mActivity); 366 mRootPane.addComponent(mStaticBackground); 367 368 mSelectionManager = new SelectionManager(mActivity, false); 369 mSelectionManager.setSelectionListener(this); 370 mGridDrawer = new GridDrawer((Context) mActivity, mSelectionManager); 371 Config.AlbumPage config = Config.AlbumPage.get((Context) mActivity); 372 mAlbumView = new AlbumView(mActivity, config.slotViewSpec, 373 0 /* don't cache thumbnail */); 374 mAlbumView.setSelectionDrawer(mGridDrawer); 375 mRootPane.addComponent(mAlbumView); 376 mAlbumView.setListener(new SlotView.SimpleListener() { 377 @Override 378 public void onDown(int index) { 379 AlbumPage.this.onDown(index); 380 } 381 382 @Override 383 public void onUp() { 384 AlbumPage.this.onUp(); 385 } 386 387 @Override 388 public void onSingleTapUp(int slotIndex) { 389 AlbumPage.this.onSingleTapUp(slotIndex); 390 } 391 392 @Override 393 public void onLongTap(int slotIndex) { 394 AlbumPage.this.onLongTap(slotIndex); 395 } 396 }); 397 mActionModeHandler = new ActionModeHandler(mActivity, mSelectionManager); 398 mActionModeHandler.setActionModeListener(new ActionModeListener() { 399 public boolean onActionItemClicked(MenuItem item) { 400 return onItemSelected(item); 401 } 402 }); 403 mStaticBackground.setImage(R.drawable.background, 404 R.drawable.background_portrait); 405 } 406 407 private void initializeData(Bundle data) { 408 mMediaSetPath = Path.fromString(data.getString(KEY_MEDIA_PATH)); 409 mMediaSet = mActivity.getDataManager().getMediaSet(mMediaSetPath); 410 Utils.assertTrue(mMediaSet != null, 411 "MediaSet is null. Path = %s", mMediaSetPath); 412 mSelectionManager.setSourceMediaSet(mMediaSet); 413 mAlbumDataAdapter = new AlbumDataAdapter(mActivity, mMediaSet); 414 mAlbumDataAdapter.setLoadingListener(new MyLoadingListener()); 415 mAlbumView.setModel(mAlbumDataAdapter); 416 } 417 418 private void showDetails() { 419 mShowDetails = true; 420 if (mDetailsHelper == null) { 421 mHighlightDrawer = new HighlightDrawer(mActivity.getAndroidContext(), 422 mSelectionManager); 423 mDetailsHelper = new DetailsHelper(mActivity, mRootPane, mDetailsSource); 424 mDetailsHelper.setCloseListener(new CloseListener() { 425 public void onClose() { 426 hideDetails(); 427 } 428 }); 429 } 430 mAlbumView.setSelectionDrawer(mHighlightDrawer); 431 mDetailsHelper.show(); 432 } 433 434 private void hideDetails() { 435 mShowDetails = false; 436 mDetailsHelper.hide(); 437 mAlbumView.setSelectionDrawer(mGridDrawer); 438 mAlbumView.invalidate(); 439 } 440 441 @Override 442 protected boolean onCreateActionBar(Menu menu) { 443 Activity activity = (Activity) mActivity; 444 GalleryActionBar actionBar = mActivity.getGalleryActionBar(); 445 MenuInflater inflater = activity.getMenuInflater(); 446 447 if (mGetContent) { 448 inflater.inflate(R.menu.pickup, menu); 449 int typeBits = mData.getInt(Gallery.KEY_TYPE_BITS, 450 DataManager.INCLUDE_IMAGE); 451 452 actionBar.setTitle(GalleryUtils.getSelectionModePrompt(typeBits)); 453 } else { 454 inflater.inflate(R.menu.album, menu); 455 actionBar.setTitle(mMediaSet.getName()); 456 if (mMediaSet instanceof MtpDevice) { 457 menu.findItem(R.id.action_slideshow).setVisible(false); 458 } else { 459 menu.findItem(R.id.action_slideshow).setVisible(true); 460 } 461 462 MenuItem groupBy = menu.findItem(R.id.action_group_by); 463 FilterUtils.setupMenuItems(actionBar, mMediaSetPath, true); 464 465 if (groupBy != null) { 466 groupBy.setVisible(mShowClusterMenu); 467 } 468 469 actionBar.setTitle(mMediaSet.getName()); 470 } 471 actionBar.setSubtitle(null); 472 473 return true; 474 } 475 476 @Override 477 protected boolean onItemSelected(MenuItem item) { 478 switch (item.getItemId()) { 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 mAlbumView.setCenterIndex(mFocusIndex); 520 break; 521 } 522 case REQUEST_PHOTO: { 523 if (data == null) return; 524 mFocusIndex = data.getIntExtra(PhotoPage.KEY_INDEX_HINT, 0); 525 mAlbumView.setCenterIndex(mFocusIndex); 526 startTransition(); 527 break; 528 } 529 case REQUEST_DO_ANIMATION: { 530 startTransition(null); 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 if (resultCode == MediaSet.SYNC_RESULT_SUCCESS) { 573 mInitialSynced = true; 574 } 575 if (!mIsActive) return; 576 clearLoadingBit(BIT_LOADING_SYNC); 577 if (resultCode == MediaSet.SYNC_RESULT_ERROR) { 578 Toast.makeText((Context) mActivity, R.string.sync_album_error, 579 Toast.LENGTH_LONG).show(); 580 } 581 } 582 }); 583 } 584 585 private void setLoadingBit(int loadTaskBit) { 586 if (mLoadingBits == 0) { 587 GalleryUtils.setSpinnerVisibility((Activity) mActivity, true); 588 } 589 mLoadingBits |= loadTaskBit; 590 } 591 592 private void clearLoadingBit(int loadTaskBit) { 593 mLoadingBits &= ~loadTaskBit; 594 if (mLoadingBits == 0) { 595 GalleryUtils.setSpinnerVisibility((Activity) mActivity, false); 596 597 if (mAlbumDataAdapter.size() == 0) { 598 Toast.makeText((Context) mActivity, 599 R.string.empty_album, Toast.LENGTH_LONG).show(); 600 mActivity.getStateManager().finishState(AlbumPage.this); 601 } 602 } 603 } 604 605 private class MyLoadingListener implements LoadingListener { 606 @Override 607 public void onLoadingStarted() { 608 setLoadingBit(BIT_LOADING_RELOAD); 609 } 610 611 @Override 612 public void onLoadingFinished() { 613 if (!mIsActive) return; 614 clearLoadingBit(BIT_LOADING_RELOAD); 615 } 616 } 617 618 private class MyDetailsSource implements DetailsHelper.DetailsSource { 619 private int mIndex; 620 621 public int size() { 622 return mAlbumDataAdapter.size(); 623 } 624 625 public int getIndex() { 626 return mIndex; 627 } 628 629 // If requested index is out of active window, suggest a valid index. 630 // If there is no valid index available, return -1. 631 public int findIndex(int indexHint) { 632 if (mAlbumDataAdapter.isActive(indexHint)) { 633 mIndex = indexHint; 634 } else { 635 mIndex = mAlbumDataAdapter.getActiveStart(); 636 if (!mAlbumDataAdapter.isActive(mIndex)) { 637 return -1; 638 } 639 } 640 return mIndex; 641 } 642 643 public MediaDetails getDetails() { 644 MediaObject item = mAlbumDataAdapter.get(mIndex); 645 if (item != null) { 646 mHighlightDrawer.setHighlightItem(item.getPath()); 647 return item.getDetails(); 648 } else { 649 return null; 650 } 651 } 652 } 653} 654