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