AlbumSetPage.java revision 2bb717f1ea38e2ce33dd102a23afe6bfacb5675c
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.os.Bundle; 24import android.os.Handler; 25import android.os.Message; 26import android.os.Vibrator; 27import android.view.Menu; 28import android.view.MenuItem; 29import android.widget.Toast; 30 31import com.android.gallery3d.R; 32import com.android.gallery3d.common.Utils; 33import com.android.gallery3d.data.DataManager; 34import com.android.gallery3d.data.MediaDetails; 35import com.android.gallery3d.data.MediaItem; 36import com.android.gallery3d.data.MediaObject; 37import com.android.gallery3d.data.MediaSet; 38import com.android.gallery3d.data.Path; 39import com.android.gallery3d.picasasource.PicasaSource; 40import com.android.gallery3d.settings.GallerySettings; 41import com.android.gallery3d.ui.ActionModeHandler; 42import com.android.gallery3d.ui.ActionModeHandler.ActionModeListener; 43import com.android.gallery3d.ui.AlbumSetSlotRenderer; 44import com.android.gallery3d.ui.DetailsHelper; 45import com.android.gallery3d.ui.DetailsHelper.CloseListener; 46import com.android.gallery3d.ui.FadeTexture; 47import com.android.gallery3d.ui.GLCanvas; 48import com.android.gallery3d.ui.GLRoot; 49import com.android.gallery3d.ui.GLView; 50import com.android.gallery3d.ui.SelectionManager; 51import com.android.gallery3d.ui.SlotView; 52import com.android.gallery3d.ui.SynchronizedHandler; 53import com.android.gallery3d.util.Future; 54import com.android.gallery3d.util.GalleryUtils; 55import com.android.gallery3d.util.HelpUtils; 56 57public class AlbumSetPage extends ActivityState implements 58 SelectionManager.SelectionListener, GalleryActionBar.ClusterRunner, 59 EyePosition.EyePositionListener, MediaSet.SyncListener { 60 @SuppressWarnings("unused") 61 private static final String TAG = "AlbumSetPage"; 62 63 private static final int MSG_PICK_ALBUM = 1; 64 65 public static final String KEY_MEDIA_PATH = "media-path"; 66 public static final String KEY_SET_TITLE = "set-title"; 67 public static final String KEY_SET_SUBTITLE = "set-subtitle"; 68 public static final String KEY_SELECTED_CLUSTER_TYPE = "selected-cluster"; 69 70 private static final int DATA_CACHE_SIZE = 256; 71 private static final int REQUEST_DO_ANIMATION = 1; 72 73 private static final int BIT_LOADING_RELOAD = 1; 74 private static final int BIT_LOADING_SYNC = 2; 75 76 private boolean mIsActive = false; 77 private SlotView mSlotView; 78 private AlbumSetSlotRenderer mAlbumSetView; 79 private Config.AlbumSetPage mConfig; 80 81 private MediaSet mMediaSet; 82 private String mTitle; 83 private String mSubtitle; 84 private boolean mShowClusterMenu; 85 private GalleryActionBar mActionBar; 86 private int mSelectedAction; 87 private Vibrator mVibrator; 88 89 protected SelectionManager mSelectionManager; 90 private AlbumSetDataLoader mAlbumSetDataAdapter; 91 92 private boolean mGetContent; 93 private boolean mGetAlbum; 94 private ActionModeHandler mActionModeHandler; 95 private DetailsHelper mDetailsHelper; 96 private MyDetailsSource mDetailsSource; 97 private boolean mShowDetails; 98 private EyePosition mEyePosition; 99 private Handler mHandler; 100 101 // The eyes' position of the user, the origin is at the center of the 102 // device and the unit is in pixels. 103 private float mX; 104 private float mY; 105 private float mZ; 106 107 private Future<Integer> mSyncTask = null; 108 109 private int mLoadingBits = 0; 110 private boolean mInitialSynced = false; 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 mEyePosition.resetPosition(); 124 125 int slotViewTop = mActionBar.getHeight() + mConfig.paddingTop; 126 int slotViewBottom = bottom - top - mConfig.paddingBottom; 127 int slotViewRight = right - left; 128 129 if (mShowDetails) { 130 mDetailsHelper.layout(left, slotViewTop, right, bottom); 131 } else { 132 mAlbumSetView.setHighlightItemPath(null); 133 } 134 135 mSlotView.layout(0, slotViewTop, slotViewRight, slotViewBottom); 136 } 137 138 @Override 139 protected void render(GLCanvas canvas) { 140 canvas.save(GLCanvas.SAVE_FLAG_MATRIX); 141 GalleryUtils.setViewPointMatrix(mMatrix, 142 getWidth() / 2 + mX, getHeight() / 2 + mY, mZ); 143 canvas.multiplyMatrix(mMatrix, 0); 144 super.render(canvas); 145 canvas.restore(); 146 } 147 }; 148 149 @Override 150 public void onEyePositionChanged(float x, float y, float z) { 151 mRootPane.lockRendering(); 152 mX = x; 153 mY = y; 154 mZ = z; 155 mRootPane.unlockRendering(); 156 mRootPane.invalidate(); 157 } 158 159 @Override 160 public void onBackPressed() { 161 if (mShowDetails) { 162 hideDetails(); 163 } else if (mSelectionManager.inSelectionMode()) { 164 mSelectionManager.leaveSelectionMode(); 165 } else { 166 super.onBackPressed(); 167 } 168 } 169 170 private void getSlotCenter(int slotIndex, int center[]) { 171 Rect offset = new Rect(); 172 mRootPane.getBoundsOf(mSlotView, offset); 173 Rect r = mSlotView.getSlotRect(slotIndex); 174 int scrollX = mSlotView.getScrollX(); 175 int scrollY = mSlotView.getScrollY(); 176 center[0] = offset.left + (r.left + r.right) / 2 - scrollX; 177 center[1] = offset.top + (r.top + r.bottom) / 2 - scrollY; 178 } 179 180 public void onSingleTapUp(int slotIndex) { 181 if (!mIsActive) return; 182 183 if (mSelectionManager.inSelectionMode()) { 184 MediaSet targetSet = mAlbumSetDataAdapter.getMediaSet(slotIndex); 185 if (targetSet == null) return; // Content is dirty, we shall reload soon 186 mSelectionManager.toggle(targetSet.getPath()); 187 mSlotView.invalidate(); 188 } else { 189 // Show pressed-up animation for the single-tap. 190 mAlbumSetView.setPressedIndex(slotIndex); 191 mAlbumSetView.setPressedUp(); 192 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_PICK_ALBUM, slotIndex, 0), 193 FadeTexture.DURATION); 194 } 195 } 196 197 private void pickAlbum(int slotIndex) { 198 if (!mIsActive) return; 199 200 MediaSet targetSet = mAlbumSetDataAdapter.getMediaSet(slotIndex); 201 if (targetSet == null) return; // Content is dirty, we shall reload soon 202 String mediaPath = targetSet.getPath().toString(); 203 204 Bundle data = new Bundle(getData()); 205 int[] center = new int[2]; 206 getSlotCenter(slotIndex, center); 207 data.putIntArray(AlbumPage.KEY_SET_CENTER, center); 208 if (mGetAlbum && targetSet.isLeafAlbum()) { 209 Activity activity = (Activity) mActivity; 210 Intent result = new Intent() 211 .putExtra(AlbumPicker.KEY_ALBUM_PATH, targetSet.getPath().toString()); 212 activity.setResult(Activity.RESULT_OK, result); 213 activity.finish(); 214 } else if (targetSet.getSubMediaSetCount() > 0) { 215 data.putString(AlbumSetPage.KEY_MEDIA_PATH, mediaPath); 216 mActivity.getStateManager().startStateForResult( 217 AlbumSetPage.class, REQUEST_DO_ANIMATION, data); 218 } else { 219 if (!mGetContent && (targetSet.getSupportedOperations() 220 & MediaObject.SUPPORT_IMPORT) != 0) { 221 data.putBoolean(AlbumPage.KEY_AUTO_SELECT_ALL, true); 222 } 223 data.putString(AlbumPage.KEY_MEDIA_PATH, mediaPath); 224 boolean inAlbum = mActivity.getStateManager().hasStateClass(AlbumPage.class); 225 // We only show cluster menu in the first AlbumPage in stack 226 data.putBoolean(AlbumPage.KEY_SHOW_CLUSTER_MENU, !inAlbum); 227 mActivity.getStateManager().startStateForResult( 228 AlbumPage.class, REQUEST_DO_ANIMATION, data); 229 } 230 } 231 232 private void onDown(int index) { 233 mAlbumSetView.setPressedIndex(index); 234 } 235 236 private void onUp(boolean followedByLongPress) { 237 if (followedByLongPress) { 238 // Avoid showing press-up animations for long-press. 239 mAlbumSetView.setPressedIndex(-1); 240 } else { 241 mAlbumSetView.setPressedUp(); 242 } 243 } 244 245 public void onLongTap(int slotIndex) { 246 if (mGetContent || mGetAlbum) return; 247 MediaSet set = mAlbumSetDataAdapter.getMediaSet(slotIndex); 248 if (set == null) return; 249 mSelectionManager.setAutoLeaveSelectionMode(true); 250 mSelectionManager.toggle(set.getPath()); 251 mSlotView.invalidate(); 252 } 253 254 @Override 255 public void doCluster(int clusterType) { 256 String basePath = mMediaSet.getPath().toString(); 257 String newPath = FilterUtils.switchClusterPath(basePath, clusterType); 258 Bundle data = new Bundle(getData()); 259 data.putString(AlbumSetPage.KEY_MEDIA_PATH, newPath); 260 data.putInt(KEY_SELECTED_CLUSTER_TYPE, clusterType); 261 mActivity.getStateManager().switchState(this, AlbumSetPage.class, data); 262 } 263 264 @Override 265 public void onCreate(Bundle data, Bundle restoreState) { 266 initializeViews(); 267 initializeData(data); 268 Context context = mActivity.getAndroidContext(); 269 mGetContent = data.getBoolean(Gallery.KEY_GET_CONTENT, false); 270 mGetAlbum = data.getBoolean(Gallery.KEY_GET_ALBUM, false); 271 mTitle = data.getString(AlbumSetPage.KEY_SET_TITLE); 272 mSubtitle = data.getString(AlbumSetPage.KEY_SET_SUBTITLE); 273 mEyePosition = new EyePosition(context, this); 274 mDetailsSource = new MyDetailsSource(); 275 mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); 276 mActionBar = mActivity.getGalleryActionBar(); 277 mSelectedAction = data.getInt(AlbumSetPage.KEY_SELECTED_CLUSTER_TYPE, 278 FilterUtils.CLUSTER_BY_ALBUM); 279 280 mHandler = new SynchronizedHandler(mActivity.getGLRoot()) { 281 @Override 282 public void handleMessage(Message message) { 283 switch (message.what) { 284 case MSG_PICK_ALBUM: { 285 pickAlbum(message.arg1); 286 break; 287 } 288 default: throw new AssertionError(message.what); 289 } 290 } 291 }; 292 } 293 294 private void clearLoadingBit(int loadingBit) { 295 mLoadingBits &= ~loadingBit; 296 if (mLoadingBits == 0 && mIsActive) { 297 // Only show toast when there's no album and we are going to finish 298 // the page. Toast is redundant if we are going to stay on this page. 299 if ((mAlbumSetDataAdapter.size() == 0)) { 300 if (mActivity.getStateManager().getStateCount() > 1) { 301 Toast.makeText((Context) mActivity, 302 R.string.empty_album, Toast.LENGTH_LONG).show(); 303 mActivity.getStateManager().finishState(this); 304 } 305 } 306 } 307 } 308 309 private void setLoadingBit(int loadingBit) { 310 mLoadingBits |= loadingBit; 311 } 312 313 @Override 314 public void onPause() { 315 super.onPause(); 316 mIsActive = false; 317 mActionModeHandler.pause(); 318 mAlbumSetDataAdapter.pause(); 319 mAlbumSetView.pause(); 320 mEyePosition.pause(); 321 DetailsHelper.pause(); 322 // Call disableClusterMenu to avoid receiving callback after paused. 323 // Don't hide menu here otherwise the list menu will disappear earlier than 324 // the action bar, which is janky and unwanted behavior. 325 mActionBar.disableClusterMenu(false); 326 if (mSyncTask != null) { 327 mSyncTask.cancel(); 328 mSyncTask = null; 329 clearLoadingBit(BIT_LOADING_SYNC); 330 } 331 } 332 333 @Override 334 public void onResume() { 335 super.onResume(); 336 mIsActive = true; 337 setContentPane(mRootPane); 338 339 // Set the reload bit here to prevent it exit this page in clearLoadingBit(). 340 setLoadingBit(BIT_LOADING_RELOAD); 341 mAlbumSetDataAdapter.resume(); 342 343 mAlbumSetView.resume(); 344 mEyePosition.resume(); 345 mActionModeHandler.resume(); 346 if (mShowClusterMenu) { 347 mActionBar.enableClusterMenu(mSelectedAction, this); 348 } 349 if (!mInitialSynced) { 350 setLoadingBit(BIT_LOADING_SYNC); 351 mSyncTask = mMediaSet.requestSync(AlbumSetPage.this); 352 } 353 } 354 355 private void initializeData(Bundle data) { 356 String mediaPath = data.getString(AlbumSetPage.KEY_MEDIA_PATH); 357 mMediaSet = mActivity.getDataManager().getMediaSet(mediaPath); 358 mSelectionManager.setSourceMediaSet(mMediaSet); 359 mAlbumSetDataAdapter = new AlbumSetDataLoader( 360 mActivity, mMediaSet, DATA_CACHE_SIZE); 361 mAlbumSetDataAdapter.setLoadingListener(new MyLoadingListener()); 362 mAlbumSetView.setModel(mAlbumSetDataAdapter); 363 } 364 365 private void initializeViews() { 366 mSelectionManager = new SelectionManager(mActivity, true); 367 mSelectionManager.setSelectionListener(this); 368 369 mConfig = Config.AlbumSetPage.get((Context) mActivity); 370 mSlotView = new SlotView(mActivity, mConfig.slotViewSpec); 371 mAlbumSetView = new AlbumSetSlotRenderer( 372 mActivity, mSelectionManager, mSlotView, mConfig.labelSpec); 373 mSlotView.setSlotRenderer(mAlbumSetView); 374 mSlotView.setListener(new SlotView.SimpleListener() { 375 @Override 376 public void onDown(int index) { 377 AlbumSetPage.this.onDown(index); 378 } 379 380 @Override 381 public void onUp(boolean followedByLongPress) { 382 AlbumSetPage.this.onUp(followedByLongPress); 383 } 384 385 @Override 386 public void onSingleTapUp(int slotIndex) { 387 AlbumSetPage.this.onSingleTapUp(slotIndex); 388 } 389 390 @Override 391 public void onLongTap(int slotIndex) { 392 AlbumSetPage.this.onLongTap(slotIndex); 393 } 394 }); 395 396 mActionModeHandler = new ActionModeHandler(mActivity, mSelectionManager); 397 mActionModeHandler.setActionModeListener(new ActionModeListener() { 398 @Override 399 public boolean onActionItemClicked(MenuItem item) { 400 return onItemSelected(item); 401 } 402 }); 403 mRootPane.addComponent(mSlotView); 404 } 405 406 @Override 407 protected boolean onCreateActionBar(Menu menu) { 408 Activity activity = (Activity) mActivity; 409 final boolean inAlbum = mActivity.getStateManager().hasStateClass(AlbumPage.class); 410 boolean result; 411 if (mGetContent) { 412 result = mActionBar.createActionMenu(menu, R.menu.pickup); 413 int typeBits = mData.getInt( 414 Gallery.KEY_TYPE_BITS, DataManager.INCLUDE_IMAGE); 415 int id = R.string.select_image; 416 if ((typeBits & DataManager.INCLUDE_VIDEO) != 0) { 417 id = (typeBits & DataManager.INCLUDE_IMAGE) == 0 418 ? R.string.select_video 419 : R.string.select_item; 420 } 421 mActionBar.setTitle(id); 422 } else if (mGetAlbum) { 423 result = mActionBar.createActionMenu(menu, R.menu.pickup); 424 mActionBar.setTitle(R.string.select_album); 425 } else { 426 result = mActionBar.createActionMenu(menu, R.menu.albumset); 427 mShowClusterMenu = !inAlbum; 428 boolean selectAlbums = !inAlbum && 429 mActionBar.getClusterTypeAction() == FilterUtils.CLUSTER_BY_ALBUM; 430 mActionBar.setMenuItemTitle(R.id.action_select, activity.getString( 431 selectAlbums ? R.string.select_album : R.string.select_group)); 432 433 FilterUtils.setupMenuItems(mActionBar, mMediaSet.getPath(), false); 434 435 mActionBar.setMenuItemVisible( 436 R.id.action_camera, GalleryUtils.isCameraAvailable(activity)); 437 438 Intent helpIntent = HelpUtils.getHelpIntent(activity, R.string.help_url_gallery_main); 439 if (helpIntent == null) { 440 mActionBar.setMenuItemVisible(R.id.action_general_help, false); 441 } else { 442 mActionBar.setMenuItemVisible(R.id.action_general_help, true); 443 mActionBar.setMenuItemIntent(R.id.action_general_help, helpIntent); 444 } 445 mActionBar.setTitle(mTitle); 446 mActionBar.setSubtitle(mSubtitle); 447 } 448 return result; 449 } 450 451 @Override 452 protected boolean onItemSelected(MenuItem item) { 453 Activity activity = (Activity) mActivity; 454 switch (item.getItemId()) { 455 case R.id.action_cancel: 456 activity.setResult(Activity.RESULT_CANCELED); 457 activity.finish(); 458 return true; 459 case R.id.action_select: 460 mSelectionManager.setAutoLeaveSelectionMode(false); 461 mSelectionManager.enterSelectionMode(); 462 return true; 463 case R.id.action_details: 464 if (mAlbumSetDataAdapter.size() != 0) { 465 if (mShowDetails) { 466 hideDetails(); 467 } else { 468 showDetails(); 469 } 470 } else { 471 Toast.makeText(activity, 472 activity.getText(R.string.no_albums_alert), 473 Toast.LENGTH_SHORT).show(); 474 } 475 return true; 476 case R.id.action_camera: { 477 GalleryUtils.startCameraActivity(activity); 478 return true; 479 } 480 case R.id.action_manage_offline: { 481 Bundle data = new Bundle(); 482 String mediaPath = mActivity.getDataManager().getTopSetPath( 483 DataManager.INCLUDE_ALL); 484 data.putString(AlbumSetPage.KEY_MEDIA_PATH, mediaPath); 485 mActivity.getStateManager().startState(ManageCachePage.class, data); 486 return true; 487 } 488 case R.id.action_sync_picasa_albums: { 489 PicasaSource.requestSync(activity); 490 return true; 491 } 492 case R.id.action_settings: { 493 activity.startActivity(new Intent(activity, GallerySettings.class)); 494 return true; 495 } 496 default: 497 return false; 498 } 499 } 500 501 @Override 502 protected void onStateResult(int requestCode, int resultCode, Intent data) { 503 switch (requestCode) { 504 case REQUEST_DO_ANIMATION: { 505 mSlotView.startRisingAnimation(); 506 } 507 } 508 } 509 510 private String getSelectedString() { 511 int count = mSelectionManager.getSelectedCount(); 512 int action = mActionBar.getClusterTypeAction(); 513 int string = action == FilterUtils.CLUSTER_BY_ALBUM 514 ? R.plurals.number_of_albums_selected 515 : R.plurals.number_of_groups_selected; 516 String format = mActivity.getResources().getQuantityString(string, count); 517 return String.format(format, count); 518 } 519 520 @Override 521 public void onSelectionModeChange(int mode) { 522 switch (mode) { 523 case SelectionManager.ENTER_SELECTION_MODE: { 524 mActionBar.disableClusterMenu(true); 525 mActionModeHandler.startActionMode(); 526 if (mHapticsEnabled) mVibrator.vibrate(100); 527 break; 528 } 529 case SelectionManager.LEAVE_SELECTION_MODE: { 530 mActionModeHandler.finishActionMode(); 531 if (mShowClusterMenu) { 532 mActionBar.enableClusterMenu(mSelectedAction, this); 533 } 534 mRootPane.invalidate(); 535 break; 536 } 537 case SelectionManager.SELECT_ALL_MODE: { 538 mActionModeHandler.updateSupportedOperation(); 539 mRootPane.invalidate(); 540 break; 541 } 542 } 543 } 544 545 @Override 546 public void onSelectionChange(Path path, boolean selected) { 547 mActionModeHandler.setTitle(getSelectedString()); 548 mActionModeHandler.updateSupportedOperation(path, selected); 549 } 550 551 private void hideDetails() { 552 mShowDetails = false; 553 mDetailsHelper.hide(); 554 mAlbumSetView.setHighlightItemPath(null); 555 mSlotView.invalidate(); 556 } 557 558 private void showDetails() { 559 mShowDetails = true; 560 if (mDetailsHelper == null) { 561 mDetailsHelper = new DetailsHelper(mActivity, mRootPane, mDetailsSource); 562 mDetailsHelper.setCloseListener(new CloseListener() { 563 @Override 564 public void onClose() { 565 hideDetails(); 566 } 567 }); 568 } 569 mDetailsHelper.show(); 570 } 571 572 @Override 573 public void onSyncDone(final MediaSet mediaSet, final int resultCode) { 574 if (resultCode == MediaSet.SYNC_RESULT_ERROR) { 575 Log.d(TAG, "onSyncDone: " + Utils.maskDebugInfo(mediaSet.getName()) + " result=" 576 + resultCode); 577 } 578 ((Activity) mActivity).runOnUiThread(new Runnable() { 579 @Override 580 public void run() { 581 GLRoot root = mActivity.getGLRoot(); 582 root.lockRenderThread(); 583 try { 584 if (resultCode == MediaSet.SYNC_RESULT_SUCCESS) { 585 mInitialSynced = true; 586 } 587 clearLoadingBit(BIT_LOADING_SYNC); 588 if (resultCode == MediaSet.SYNC_RESULT_ERROR && mIsActive) { 589 Log.w(TAG, "failed to load album set"); 590 } 591 } finally { 592 root.unlockRenderThread(); 593 } 594 } 595 }); 596 } 597 598 private class MyLoadingListener implements LoadingListener { 599 @Override 600 public void onLoadingStarted() { 601 setLoadingBit(BIT_LOADING_RELOAD); 602 } 603 604 @Override 605 public void onLoadingFinished() { 606 clearLoadingBit(BIT_LOADING_RELOAD); 607 } 608 } 609 610 private class MyDetailsSource implements DetailsHelper.DetailsSource { 611 private int mIndex; 612 613 @Override 614 public int size() { 615 return mAlbumSetDataAdapter.size(); 616 } 617 618 @Override 619 public int setIndex() { 620 Path id = mSelectionManager.getSelected(false).get(0); 621 mIndex = mAlbumSetDataAdapter.findSet(id); 622 return mIndex; 623 } 624 625 @Override 626 public MediaDetails getDetails() { 627 MediaObject item = mAlbumSetDataAdapter.getMediaSet(mIndex); 628 if (item != null) { 629 mAlbumSetView.setHighlightItemPath(item.getPath()); 630 return item.getDetails(); 631 } else { 632 return null; 633 } 634 } 635 } 636} 637