2 * Copyright (C) 2010 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.gallery3d.app;
19 import android.app.Activity;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.graphics.Rect;
23 import android.net.Uri;
24 import android.os.Bundle;
25 import android.os.Handler;
26 import android.os.Message;
27 import android.os.Vibrator;
28 import android.provider.MediaStore;
29 import android.view.ActionMode;
30 import android.view.Menu;
31 import android.view.MenuInflater;
32 import android.view.MenuItem;
33 import android.widget.Toast;
35 import com.android.gallery3d.R;
36 import com.android.gallery3d.common.Utils;
37 import com.android.gallery3d.data.DataManager;
38 import com.android.gallery3d.data.MediaDetails;
39 import com.android.gallery3d.data.MediaItem;
40 import com.android.gallery3d.data.MediaObject;
41 import com.android.gallery3d.data.MediaSet;
42 import com.android.gallery3d.data.MtpDevice;
43 import com.android.gallery3d.data.Path;
44 import com.android.gallery3d.ui.ActionModeHandler;
45 import com.android.gallery3d.ui.ActionModeHandler.ActionModeListener;
46 import com.android.gallery3d.ui.AlbumSlotRenderer;
47 import com.android.gallery3d.ui.DetailsHelper;
48 import com.android.gallery3d.ui.DetailsHelper.CloseListener;
49 import com.android.gallery3d.ui.FadeTexture;
50 import com.android.gallery3d.ui.GLCanvas;
51 import com.android.gallery3d.ui.GLRoot;
52 import com.android.gallery3d.ui.GLView;
53 import com.android.gallery3d.ui.RelativePosition;
54 import com.android.gallery3d.ui.SelectionManager;
55 import com.android.gallery3d.ui.SlotView;
56 import com.android.gallery3d.ui.SynchronizedHandler;
57 import com.android.gallery3d.util.Future;
58 import com.android.gallery3d.util.GalleryUtils;
60 public class AlbumPage extends ActivityState implements GalleryActionBar.ClusterRunner,
61 SelectionManager.SelectionListener, MediaSet.SyncListener {
62 @SuppressWarnings("unused")
63 private static final String TAG = "AlbumPage";
65 private static final int MSG_PICK_PHOTO = 1;
67 public static final String KEY_MEDIA_PATH = "media-path";
68 public static final String KEY_PARENT_MEDIA_PATH = "parent-media-path";
69 public static final String KEY_SET_CENTER = "set-center";
70 public static final String KEY_AUTO_SELECT_ALL = "auto-select-all";
71 public static final String KEY_SHOW_CLUSTER_MENU = "cluster-menu";
73 private static final int REQUEST_SLIDESHOW = 1;
74 private static final int REQUEST_PHOTO = 2;
75 private static final int REQUEST_DO_ANIMATION = 3;
77 private static final int BIT_LOADING_RELOAD = 1;
78 private static final int BIT_LOADING_SYNC = 2;
80 private static final float USER_DISTANCE_METER = 0.3f;
82 private boolean mIsActive = false;
83 private AlbumSlotRenderer mAlbumView;
84 private Path mMediaSetPath;
85 private String mParentMediaSetString;
86 private SlotView mSlotView;
88 private AlbumDataLoader mAlbumDataAdapter;
90 protected SelectionManager mSelectionManager;
91 private Vibrator mVibrator;
93 private boolean mGetContent;
94 private boolean mShowClusterMenu;
96 private ActionMode mActionMode;
97 private ActionModeHandler mActionModeHandler;
98 private int mFocusIndex = 0;
99 private DetailsHelper mDetailsHelper;
100 private MyDetailsSource mDetailsSource;
101 private MediaSet mMediaSet;
102 private boolean mShowDetails;
103 private float mUserDistance; // in pixel
104 private Handler mHandler;
106 private Future<Integer> mSyncTask = null;
108 private int mLoadingBits = 0;
109 private boolean mInitialSynced = false;
110 private RelativePosition mOpenCenter = new RelativePosition();
112 private final GLView mRootPane = new GLView() {
113 private final float mMatrix[] = new float[16];
116 protected void renderBackground(GLCanvas view) {
121 protected void onLayout(
122 boolean changed, int left, int top, int right, int bottom) {
124 int slotViewTop = mActivity.getGalleryActionBar().getHeight();
125 int slotViewBottom = bottom - top;
126 int slotViewRight = right - left;
129 mDetailsHelper.layout(left, slotViewTop, right, bottom);
131 mAlbumView.setHighlightItemPath(null);
134 // Set the mSlotView as a reference point to the open animation
135 mOpenCenter.setReferencePosition(0, slotViewTop);
136 mSlotView.layout(0, slotViewTop, slotViewRight, slotViewBottom);
137 GalleryUtils.setViewPointMatrix(mMatrix,
138 (right - left) / 2, (bottom - top) / 2, -mUserDistance);
142 protected void render(GLCanvas canvas) {
143 canvas.save(GLCanvas.SAVE_FLAG_MATRIX);
144 canvas.multiplyMatrix(mMatrix, 0);
145 super.render(canvas);
151 protected void onBackPressed() {
154 } else if (mSelectionManager.inSelectionMode()) {
155 mSelectionManager.leaveSelectionMode();
157 // TODO: fix this regression
158 // mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
159 super.onBackPressed();
163 private void onDown(int index) {
164 mAlbumView.setPressedIndex(index);
167 private void onUp(boolean followedByLongPress) {
168 if (followedByLongPress) {
169 // Avoid showing press-up animations for long-press.
170 mAlbumView.setPressedIndex(-1);
172 mAlbumView.setPressedUp();
176 private void onSingleTapUp(int slotIndex) {
177 if (!mIsActive) return;
179 if (mSelectionManager.inSelectionMode()) {
180 MediaItem item = mAlbumDataAdapter.get(slotIndex);
181 if (item == null) return; // Item not ready yet, ignore the click
182 mSelectionManager.toggle(item.getPath());
183 mSlotView.invalidate();
185 // Show pressed-up animation for the single-tap.
186 mAlbumView.setPressedIndex(slotIndex);
187 mAlbumView.setPressedUp();
188 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_PICK_PHOTO, slotIndex, 0),
189 FadeTexture.DURATION);
193 private void pickPhoto(int slotIndex) {
194 if (!mIsActive) return;
196 MediaItem item = mAlbumDataAdapter.get(slotIndex);
197 if (item == null) return; // Item not ready yet, ignore the click
201 // Get into the PhotoPage.
202 // mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
203 Bundle data = new Bundle();
204 data.putInt(PhotoPage.KEY_INDEX_HINT, slotIndex);
205 data.putParcelable(PhotoPage.KEY_OPEN_ANIMATION_RECT,
206 getSlotRect(slotIndex));
207 data.putString(PhotoPage.KEY_MEDIA_SET_PATH,
208 mMediaSetPath.toString());
209 data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH,
210 item.getPath().toString());
211 mActivity.getStateManager().startStateForResult(
212 PhotoPage.class, REQUEST_PHOTO, data);
216 private Rect getSlotRect(int slotIndex) {
217 // Get slot rectangle relative to this root pane.
218 Rect offset = new Rect();
219 mRootPane.getBoundsOf(mSlotView, offset);
220 Rect r = mSlotView.getSlotRect(slotIndex);
221 r.offset(offset.left - mSlotView.getScrollX(),
222 offset.top - mSlotView.getScrollY());
226 private void onGetContent(final MediaItem item) {
227 DataManager dm = mActivity.getDataManager();
228 Activity activity = (Activity) mActivity;
229 if (mData.getString(Gallery.EXTRA_CROP) != null) {
230 // TODO: Handle MtpImagew
231 Uri uri = dm.getContentUri(item.getPath());
232 Intent intent = new Intent(CropImage.ACTION_CROP, uri)
233 .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
234 .putExtras(getData());
235 if (mData.getParcelable(MediaStore.EXTRA_OUTPUT) == null) {
236 intent.putExtra(CropImage.KEY_RETURN_DATA, true);
238 activity.startActivity(intent);
241 activity.setResult(Activity.RESULT_OK,
242 new Intent(null, item.getContentUri()));
247 public void onLongTap(int slotIndex) {
248 if (mGetContent) return;
249 MediaItem item = mAlbumDataAdapter.get(slotIndex);
250 if (item == null) return;
251 mSelectionManager.setAutoLeaveSelectionMode(true);
252 mSelectionManager.toggle(item.getPath());
253 mSlotView.invalidate();
257 public void doCluster(int clusterType) {
258 String basePath = mMediaSet.getPath().toString();
259 String newPath = FilterUtils.newClusterPath(basePath, clusterType);
260 Bundle data = new Bundle(getData());
261 data.putString(AlbumSetPage.KEY_MEDIA_PATH, newPath);
262 if (mShowClusterMenu) {
263 Context context = mActivity.getAndroidContext();
264 data.putString(AlbumSetPage.KEY_SET_TITLE, mMediaSet.getName());
265 data.putString(AlbumSetPage.KEY_SET_SUBTITLE,
266 GalleryActionBar.getClusterByTypeString(context, clusterType));
269 // mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
270 mActivity.getStateManager().startStateForResult(
271 AlbumSetPage.class, REQUEST_DO_ANIMATION, data);
275 protected void onCreate(Bundle data, Bundle restoreState) {
276 mUserDistance = GalleryUtils.meterToPixel(USER_DISTANCE_METER);
278 initializeData(data);
279 mGetContent = data.getBoolean(Gallery.KEY_GET_CONTENT, false);
280 mShowClusterMenu = data.getBoolean(KEY_SHOW_CLUSTER_MENU, false);
281 mDetailsSource = new MyDetailsSource();
282 Context context = mActivity.getAndroidContext();
283 mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
285 // Enable auto-select-all for mtp album
286 if (data.getBoolean(KEY_AUTO_SELECT_ALL)) {
287 mSelectionManager.selectAll();
290 // Don't show animation if it is restored
291 if (restoreState == null && data != null) {
292 int[] center = data.getIntArray(KEY_SET_CENTER);
293 if (center != null) {
294 mOpenCenter.setAbsolutePosition(center[0], center[1]);
295 mSlotView.startScatteringAnimation(mOpenCenter);
299 mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
301 public void handleMessage(Message message) {
302 switch (message.what) {
303 case MSG_PICK_PHOTO: {
304 pickPhoto(message.arg1);
307 default: throw new AssertionError(message.what);
314 protected void onResume() {
317 setContentPane(mRootPane);
319 Path path = mMediaSet.getPath();
320 boolean enableHomeButton = (mActivity.getStateManager().getStateCount() > 1) |
321 mParentMediaSetString != null;
322 mActivity.getGalleryActionBar().setDisplayOptions(enableHomeButton, true);
324 // Set the reload bit here to prevent it exit this page in clearLoadingBit().
325 setLoadingBit(BIT_LOADING_RELOAD);
326 mAlbumDataAdapter.resume();
329 mActionModeHandler.resume();
330 if (!mInitialSynced) {
331 setLoadingBit(BIT_LOADING_SYNC);
332 mSyncTask = mMediaSet.requestSync(this);
337 protected void onPause() {
340 mAlbumDataAdapter.pause();
342 DetailsHelper.pause();
344 if (mSyncTask != null) {
347 clearLoadingBit(BIT_LOADING_SYNC);
349 mActionModeHandler.pause();
350 GalleryUtils.setSpinnerVisibility((Activity) mActivity, false);
354 protected void onDestroy() {
356 if (mAlbumDataAdapter != null) {
357 mAlbumDataAdapter.setLoadingListener(null);
361 private void initializeViews() {
362 mSelectionManager = new SelectionManager(mActivity, false);
363 mSelectionManager.setSelectionListener(this);
364 Config.AlbumPage config = Config.AlbumPage.get((Context) mActivity);
365 mSlotView = new SlotView(mActivity, config.slotViewSpec);
366 mAlbumView = new AlbumSlotRenderer(mActivity, mSlotView, mSelectionManager);
367 mSlotView.setSlotRenderer(mAlbumView);
368 mRootPane.addComponent(mSlotView);
369 mSlotView.setListener(new SlotView.SimpleListener() {
371 public void onDown(int index) {
372 AlbumPage.this.onDown(index);
376 public void onUp(boolean followedByLongPress) {
377 AlbumPage.this.onUp(followedByLongPress);
381 public void onSingleTapUp(int slotIndex) {
382 AlbumPage.this.onSingleTapUp(slotIndex);
386 public void onLongTap(int slotIndex) {
387 AlbumPage.this.onLongTap(slotIndex);
390 mActionModeHandler = new ActionModeHandler(mActivity, mSelectionManager);
391 mActionModeHandler.setActionModeListener(new ActionModeListener() {
392 public boolean onActionItemClicked(MenuItem item) {
393 return onItemSelected(item);
398 private void initializeData(Bundle data) {
399 mMediaSetPath = Path.fromString(data.getString(KEY_MEDIA_PATH));
400 mParentMediaSetString = data.getString(KEY_PARENT_MEDIA_PATH);
401 mMediaSet = mActivity.getDataManager().getMediaSet(mMediaSetPath);
402 if (mMediaSet == null) {
403 Utils.fail("MediaSet is null. Path = %s", mMediaSetPath);
405 mSelectionManager.setSourceMediaSet(mMediaSet);
406 mAlbumDataAdapter = new AlbumDataLoader(mActivity, mMediaSet);
407 mAlbumDataAdapter.setLoadingListener(new MyLoadingListener());
408 mAlbumView.setModel(mAlbumDataAdapter);
411 private void showDetails() {
413 if (mDetailsHelper == null) {
414 mDetailsHelper = new DetailsHelper(mActivity, mRootPane, mDetailsSource);
415 mDetailsHelper.setCloseListener(new CloseListener() {
416 public void onClose() {
421 mDetailsHelper.show();
424 private void hideDetails() {
425 mShowDetails = false;
426 mDetailsHelper.hide();
427 mAlbumView.setHighlightItemPath(null);
428 mSlotView.invalidate();
432 protected boolean onCreateActionBar(Menu menu) {
433 Activity activity = (Activity) mActivity;
434 GalleryActionBar actionBar = mActivity.getGalleryActionBar();
435 MenuInflater inflater = activity.getMenuInflater();
438 inflater.inflate(R.menu.pickup, menu);
439 int typeBits = mData.getInt(Gallery.KEY_TYPE_BITS,
440 DataManager.INCLUDE_IMAGE);
442 actionBar.setTitle(GalleryUtils.getSelectionModePrompt(typeBits));
444 inflater.inflate(R.menu.album, menu);
445 actionBar.setTitle(mMediaSet.getName());
446 if (mMediaSet instanceof MtpDevice) {
447 menu.findItem(R.id.action_slideshow).setVisible(false);
449 menu.findItem(R.id.action_slideshow).setVisible(true);
452 MenuItem groupBy = menu.findItem(R.id.action_group_by);
453 FilterUtils.setupMenuItems(actionBar, mMediaSetPath, true);
455 if (groupBy != null) {
456 groupBy.setVisible(mShowClusterMenu);
459 actionBar.setTitle(mMediaSet.getName());
461 actionBar.setSubtitle(null);
467 protected boolean onItemSelected(MenuItem item) {
468 switch (item.getItemId()) {
469 case android.R.id.home: {
470 if (mActivity.getStateManager().getStateCount() > 1) {
472 } else if (mParentMediaSetString != null) {
473 Bundle data = new Bundle(getData());
474 data.putString(AlbumSetPage.KEY_MEDIA_PATH, mParentMediaSetString);
475 mActivity.getStateManager().switchState(this, AlbumSetPage.class, data);
479 case R.id.action_cancel:
480 mActivity.getStateManager().finishState(this);
482 case R.id.action_select:
483 mSelectionManager.setAutoLeaveSelectionMode(false);
484 mSelectionManager.enterSelectionMode();
486 case R.id.action_group_by: {
487 mActivity.getGalleryActionBar().showClusterDialog(this);
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);
499 case R.id.action_details: {
513 protected void onStateResult(int request, int result, Intent data) {
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 mSlotView.setCenterIndex(mFocusIndex);
522 case REQUEST_PHOTO: {
523 if (data == null) return;
524 mFocusIndex = data.getIntExtra(PhotoPage.KEY_RETURN_INDEX_HINT, 0);
525 mSlotView.setCenterIndex(mFocusIndex);
526 mSlotView.startRestoringAnimation(mFocusIndex);
529 case REQUEST_DO_ANIMATION: {
530 mSlotView.startRisingAnimation();
536 public void onSelectionModeChange(int mode) {
538 case SelectionManager.ENTER_SELECTION_MODE: {
539 mActionMode = mActionModeHandler.startActionMode();
540 mVibrator.vibrate(100);
543 case SelectionManager.LEAVE_SELECTION_MODE: {
544 mActionMode.finish();
545 mRootPane.invalidate();
548 case SelectionManager.SELECT_ALL_MODE: {
549 mActionModeHandler.updateSupportedOperation();
550 mRootPane.invalidate();
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);
566 public void onSyncDone(final MediaSet mediaSet, final int resultCode) {
567 Log.d(TAG, "onSyncDone: " + Utils.maskDebugInfo(mediaSet.getName()) + " result="
569 ((Activity) mActivity).runOnUiThread(new Runnable() {
572 GLRoot root = mActivity.getGLRoot();
573 root.lockRenderThread();
575 if (resultCode == MediaSet.SYNC_RESULT_SUCCESS) {
576 mInitialSynced = true;
578 clearLoadingBit(BIT_LOADING_SYNC);
579 if (resultCode == MediaSet.SYNC_RESULT_ERROR && mIsActive) {
580 Toast.makeText((Context) mActivity, R.string.sync_album_error,
581 Toast.LENGTH_LONG).show();
584 root.unlockRenderThread();
590 private void setLoadingBit(int loadTaskBit) {
591 if (mLoadingBits == 0 && mIsActive) {
592 GalleryUtils.setSpinnerVisibility((Activity) mActivity, true);
594 mLoadingBits |= loadTaskBit;
597 private void clearLoadingBit(int loadTaskBit) {
598 mLoadingBits &= ~loadTaskBit;
599 if (mLoadingBits == 0 && mIsActive) {
600 GalleryUtils.setSpinnerVisibility((Activity) mActivity, false);
602 if (mAlbumDataAdapter.size() == 0) {
603 Toast.makeText((Context) mActivity,
604 R.string.empty_album, Toast.LENGTH_LONG).show();
605 mActivity.getStateManager().finishState(AlbumPage.this);
610 private class MyLoadingListener implements LoadingListener {
612 public void onLoadingStarted() {
613 setLoadingBit(BIT_LOADING_RELOAD);
617 public void onLoadingFinished() {
618 clearLoadingBit(BIT_LOADING_RELOAD);
622 private class MyDetailsSource implements DetailsHelper.DetailsSource {
626 return mAlbumDataAdapter.size();
629 public int getIndex() {
633 // If requested index is out of active window, suggest a valid index.
634 // If there is no valid index available, return -1.
635 public int findIndex(int indexHint) {
636 if (mAlbumDataAdapter.isActive(indexHint)) {
639 mIndex = mAlbumDataAdapter.getActiveStart();
640 if (!mAlbumDataAdapter.isActive(mIndex)) {
647 public MediaDetails getDetails() {
648 MediaObject item = mAlbumDataAdapter.get(mIndex);
650 mAlbumView.setHighlightItemPath(item.getPath());
651 return item.getDetails();