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.Vibrator;
26 import android.provider.MediaStore;
27 import android.view.ActionMode;
28 import android.view.Menu;
29 import android.view.MenuInflater;
30 import android.view.MenuItem;
31 import android.widget.Toast;
33 import com.android.gallery3d.R;
34 import com.android.gallery3d.common.Utils;
35 import com.android.gallery3d.data.DataManager;
36 import com.android.gallery3d.data.MediaDetails;
37 import com.android.gallery3d.data.MediaItem;
38 import com.android.gallery3d.data.MediaObject;
39 import com.android.gallery3d.data.MediaSet;
40 import com.android.gallery3d.data.MtpDevice;
41 import com.android.gallery3d.data.Path;
42 import com.android.gallery3d.ui.ActionModeHandler;
43 import com.android.gallery3d.ui.ActionModeHandler.ActionModeListener;
44 import com.android.gallery3d.ui.AlbumView;
45 import com.android.gallery3d.ui.DetailsHelper;
46 import com.android.gallery3d.ui.DetailsHelper.CloseListener;
47 import com.android.gallery3d.ui.GLCanvas;
48 import com.android.gallery3d.ui.GLView;
49 import com.android.gallery3d.ui.GridDrawer;
50 import com.android.gallery3d.ui.HighlightDrawer;
51 import com.android.gallery3d.ui.RelativePosition;
52 import com.android.gallery3d.ui.ScreenNailHolder;
53 import com.android.gallery3d.ui.SelectionManager;
54 import com.android.gallery3d.ui.SlotView;
55 import com.android.gallery3d.util.Future;
56 import com.android.gallery3d.util.GalleryUtils;
58 public class AlbumPage extends ActivityState implements GalleryActionBar.ClusterRunner,
59 SelectionManager.SelectionListener, MediaSet.SyncListener {
60 @SuppressWarnings("unused")
61 private static final String TAG = "AlbumPage";
63 public static final String KEY_MEDIA_PATH = "media-path";
64 public static final String KEY_PARENT_MEDIA_PATH = "parent-media-path";
65 public static final String KEY_SET_CENTER = "set-center";
66 public static final String KEY_AUTO_SELECT_ALL = "auto-select-all";
67 public static final String KEY_SHOW_CLUSTER_MENU = "cluster-menu";
69 private static final int REQUEST_SLIDESHOW = 1;
70 private static final int REQUEST_PHOTO = 2;
71 private static final int REQUEST_DO_ANIMATION = 3;
73 private static final int BIT_LOADING_RELOAD = 1;
74 private static final int BIT_LOADING_SYNC = 2;
76 private static final float USER_DISTANCE_METER = 0.3f;
77 private static final boolean TEST_CAMERA_PREVIEW = false;
79 private boolean mIsActive = false;
80 private AlbumView mAlbumView;
81 private Path mMediaSetPath;
82 private String mParentMediaSetString;
83 private SlotView mSlotView;
85 private AlbumDataAdapter mAlbumDataAdapter;
87 protected SelectionManager mSelectionManager;
88 private Vibrator mVibrator;
89 private GridDrawer mGridDrawer;
90 private HighlightDrawer mHighlightDrawer;
92 private boolean mGetContent;
93 private boolean mShowClusterMenu;
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
104 private Future<Integer> mSyncTask = null;
106 private int mLoadingBits = 0;
107 private boolean mInitialSynced = false;
108 private RelativePosition mOpenCenter = new RelativePosition();
110 private final GLView mRootPane = new GLView() {
111 private final float mMatrix[] = new float[16];
114 protected void renderBackground(GLCanvas view) {
119 protected void onLayout(
120 boolean changed, int left, int top, int right, int bottom) {
122 int slotViewTop = mActivity.getGalleryActionBar().getHeight();
123 int slotViewBottom = bottom - top;
124 int slotViewRight = right - left;
127 mDetailsHelper.layout(left, slotViewTop, right, bottom);
129 mAlbumView.setSelectionDrawer(mGridDrawer);
132 // Set the mSlotView as a reference point to the open animation
133 mOpenCenter.setReferencePosition(0, slotViewTop);
134 mSlotView.layout(0, slotViewTop, slotViewRight, slotViewBottom);
135 GalleryUtils.setViewPointMatrix(mMatrix,
136 (right - left) / 2, (bottom - top) / 2, -mUserDistance);
140 protected void render(GLCanvas canvas) {
141 canvas.save(GLCanvas.SAVE_FLAG_MATRIX);
142 canvas.multiplyMatrix(mMatrix, 0);
143 super.render(canvas);
149 protected void onBackPressed() {
152 } else if (mSelectionManager.inSelectionMode()) {
153 mSelectionManager.leaveSelectionMode();
155 // TODO: fix this regression
156 // mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
157 super.onBackPressed();
161 private void onDown(int index) {
162 MediaItem item = mAlbumDataAdapter.get(index);
163 Path path = (item == null) ? null : item.getPath();
164 mSelectionManager.setPressedPath(path);
165 mSlotView.invalidate();
168 private void onUp() {
169 mSelectionManager.setPressedPath(null);
170 mSlotView.invalidate();
173 private void onSingleTapUp(int slotIndex) {
174 MediaItem item = mAlbumDataAdapter.get(slotIndex);
176 Log.w(TAG, "item not ready yet, ignore the click");
180 mHighlightDrawer.setHighlightItem(item.getPath());
181 mDetailsHelper.reloadDetails(slotIndex);
182 } else if (!mSelectionManager.inSelectionMode()) {
186 // Get into the PhotoPage.
187 Bundle data = new Bundle();
189 // mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
190 data.putInt(PhotoPage.KEY_INDEX_HINT, slotIndex);
191 data.putParcelable(PhotoPage.KEY_OPEN_ANIMATION_RECT,
192 getSlotRect(slotIndex));
193 data.putString(PhotoPage.KEY_MEDIA_SET_PATH,
194 mMediaSetPath.toString());
195 data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH,
196 item.getPath().toString());
197 if (TEST_CAMERA_PREVIEW) {
198 ScreenNailHolder holder = new CameraScreenNailHolder(mActivity);
199 data.putParcelable(PhotoPage.KEY_SCREENNAIL_HOLDER, holder);
201 mActivity.getStateManager().startStateForResult(
202 PhotoPage.class, REQUEST_PHOTO, data);
205 mSelectionManager.toggle(item.getPath());
206 mDetailsSource.findIndex(slotIndex);
207 mSlotView.invalidate();
211 private Rect getSlotRect(int slotIndex) {
212 // Get slot rectangle relative to this root pane.
213 Rect offset = new Rect();
214 mRootPane.getBoundsOf(mSlotView, offset);
215 Rect r = mSlotView.getSlotRect(slotIndex);
216 r.offset(offset.left - mSlotView.getScrollX(),
217 offset.top - mSlotView.getScrollY());
221 private void onGetContent(final MediaItem item) {
222 DataManager dm = mActivity.getDataManager();
223 Activity activity = (Activity) mActivity;
224 if (mData.getString(Gallery.EXTRA_CROP) != null) {
225 // TODO: Handle MtpImagew
226 Uri uri = dm.getContentUri(item.getPath());
227 Intent intent = new Intent(CropImage.ACTION_CROP, uri)
228 .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
229 .putExtras(getData());
230 if (mData.getParcelable(MediaStore.EXTRA_OUTPUT) == null) {
231 intent.putExtra(CropImage.KEY_RETURN_DATA, true);
233 activity.startActivity(intent);
236 activity.setResult(Activity.RESULT_OK,
237 new Intent(null, item.getContentUri()));
242 public void onLongTap(int slotIndex) {
243 if (mGetContent) return;
245 onSingleTapUp(slotIndex);
247 MediaItem item = mAlbumDataAdapter.get(slotIndex);
248 if (item == null) return;
249 mSelectionManager.setAutoLeaveSelectionMode(true);
250 mSelectionManager.toggle(item.getPath());
251 mDetailsSource.findIndex(slotIndex);
252 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);
301 protected void onResume() {
304 setContentPane(mRootPane);
306 Path path = mMediaSet.getPath();
307 boolean enableHomeButton = (mActivity.getStateManager().getStateCount() > 1) |
308 mParentMediaSetString != null;
309 mActivity.getGalleryActionBar().setDisplayOptions(enableHomeButton, true);
311 // Set the reload bit here to prevent it exit this page in clearLoadingBit().
312 setLoadingBit(BIT_LOADING_RELOAD);
313 mAlbumDataAdapter.resume();
316 mActionModeHandler.resume();
317 if (!mInitialSynced) {
318 setLoadingBit(BIT_LOADING_SYNC);
319 mSyncTask = mMediaSet.requestSync(this);
324 protected void onPause() {
327 mAlbumDataAdapter.pause();
329 DetailsHelper.pause();
331 if (mSyncTask != null) {
334 clearLoadingBit(BIT_LOADING_SYNC);
336 mActionModeHandler.pause();
337 GalleryUtils.setSpinnerVisibility((Activity) mActivity, false);
341 protected void onDestroy() {
343 if (mAlbumDataAdapter != null) {
344 mAlbumDataAdapter.setLoadingListener(null);
348 private void initializeViews() {
349 mSelectionManager = new SelectionManager(mActivity, false);
350 mSelectionManager.setSelectionListener(this);
351 mGridDrawer = new GridDrawer((Context) mActivity, mSelectionManager);
352 Config.AlbumPage config = Config.AlbumPage.get((Context) mActivity);
353 mSlotView = new SlotView((Context) mActivity, config.slotViewSpec);
354 mAlbumView = new AlbumView(mActivity, mSlotView);
355 mSlotView.setSlotRenderer(mAlbumView);
356 mAlbumView.setSelectionDrawer(mGridDrawer);
357 mRootPane.addComponent(mSlotView);
358 mSlotView.setListener(new SlotView.SimpleListener() {
360 public void onDown(int index) {
361 AlbumPage.this.onDown(index);
366 AlbumPage.this.onUp();
370 public void onSingleTapUp(int slotIndex) {
371 AlbumPage.this.onSingleTapUp(slotIndex);
375 public void onLongTap(int slotIndex) {
376 AlbumPage.this.onLongTap(slotIndex);
379 mActionModeHandler = new ActionModeHandler(mActivity, mSelectionManager);
380 mActionModeHandler.setActionModeListener(new ActionModeListener() {
381 public boolean onActionItemClicked(MenuItem item) {
382 return onItemSelected(item);
387 private void initializeData(Bundle data) {
388 mMediaSetPath = Path.fromString(data.getString(KEY_MEDIA_PATH));
389 mParentMediaSetString = data.getString(KEY_PARENT_MEDIA_PATH);
390 mMediaSet = mActivity.getDataManager().getMediaSet(mMediaSetPath);
391 if (mMediaSet == null) {
392 Utils.fail("MediaSet is null. Path = %s", mMediaSetPath);
394 mSelectionManager.setSourceMediaSet(mMediaSet);
395 mAlbumDataAdapter = new AlbumDataAdapter(mActivity, mMediaSet);
396 mAlbumDataAdapter.setLoadingListener(new MyLoadingListener());
397 mAlbumView.setModel(mAlbumDataAdapter);
400 private void showDetails() {
402 if (mDetailsHelper == null) {
403 mHighlightDrawer = new HighlightDrawer(mActivity.getAndroidContext(),
405 mDetailsHelper = new DetailsHelper(mActivity, mRootPane, mDetailsSource);
406 mDetailsHelper.setCloseListener(new CloseListener() {
407 public void onClose() {
412 mAlbumView.setSelectionDrawer(mHighlightDrawer);
413 mDetailsHelper.show();
416 private void hideDetails() {
417 mShowDetails = false;
418 mDetailsHelper.hide();
419 mAlbumView.setSelectionDrawer(mGridDrawer);
420 mSlotView.invalidate();
424 protected boolean onCreateActionBar(Menu menu) {
425 Activity activity = (Activity) mActivity;
426 GalleryActionBar actionBar = mActivity.getGalleryActionBar();
427 MenuInflater inflater = activity.getMenuInflater();
430 inflater.inflate(R.menu.pickup, menu);
431 int typeBits = mData.getInt(Gallery.KEY_TYPE_BITS,
432 DataManager.INCLUDE_IMAGE);
434 actionBar.setTitle(GalleryUtils.getSelectionModePrompt(typeBits));
436 inflater.inflate(R.menu.album, menu);
437 actionBar.setTitle(mMediaSet.getName());
438 if (mMediaSet instanceof MtpDevice) {
439 menu.findItem(R.id.action_slideshow).setVisible(false);
441 menu.findItem(R.id.action_slideshow).setVisible(true);
444 MenuItem groupBy = menu.findItem(R.id.action_group_by);
445 FilterUtils.setupMenuItems(actionBar, mMediaSetPath, true);
447 if (groupBy != null) {
448 groupBy.setVisible(mShowClusterMenu);
451 actionBar.setTitle(mMediaSet.getName());
453 actionBar.setSubtitle(null);
459 protected boolean onItemSelected(MenuItem item) {
460 switch (item.getItemId()) {
461 case android.R.id.home: {
462 if (mActivity.getStateManager().getStateCount() > 1) {
464 } else if (mParentMediaSetString != null) {
465 Activity a = (Activity) mActivity;
466 int flags = Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK;
467 Intent intent = new Intent()
468 .setClass(a, Gallery.class)
470 a.startActivity(intent);
474 case R.id.action_cancel:
475 mActivity.getStateManager().finishState(this);
477 case R.id.action_select:
478 mSelectionManager.setAutoLeaveSelectionMode(false);
479 mSelectionManager.enterSelectionMode();
481 case R.id.action_group_by: {
482 mActivity.getGalleryActionBar().showClusterDialog(this);
485 case R.id.action_slideshow: {
486 Bundle data = new Bundle();
487 data.putString(SlideshowPage.KEY_SET_PATH,
488 mMediaSetPath.toString());
489 data.putBoolean(SlideshowPage.KEY_REPEAT, true);
490 mActivity.getStateManager().startStateForResult(
491 SlideshowPage.class, REQUEST_SLIDESHOW, data);
494 case R.id.action_details: {
508 protected void onStateResult(int request, int result, Intent data) {
510 case REQUEST_SLIDESHOW: {
511 // data could be null, if there is no images in the album
512 if (data == null) return;
513 mFocusIndex = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0);
514 mSlotView.setCenterIndex(mFocusIndex);
517 case REQUEST_PHOTO: {
518 if (data == null) return;
519 mFocusIndex = data.getIntExtra(PhotoPage.KEY_INDEX_HINT, 0);
520 mSlotView.setCenterIndex(mFocusIndex);
521 mSlotView.startRestoringAnimation(mFocusIndex);
524 case REQUEST_DO_ANIMATION: {
525 mSlotView.startRisingAnimation();
531 public void onSelectionModeChange(int mode) {
533 case SelectionManager.ENTER_SELECTION_MODE: {
534 mActionMode = mActionModeHandler.startActionMode();
535 mVibrator.vibrate(100);
538 case SelectionManager.LEAVE_SELECTION_MODE: {
539 mActionMode.finish();
540 mRootPane.invalidate();
543 case SelectionManager.SELECT_ALL_MODE: {
544 mActionModeHandler.updateSupportedOperation();
545 mRootPane.invalidate();
551 public void onSelectionChange(Path path, boolean selected) {
552 Utils.assertTrue(mActionMode != null);
553 int count = mSelectionManager.getSelectedCount();
554 String format = mActivity.getResources().getQuantityString(
555 R.plurals.number_of_items_selected, count);
556 mActionModeHandler.setTitle(String.format(format, count));
557 mActionModeHandler.updateSupportedOperation(path, selected);
561 public void onSyncDone(final MediaSet mediaSet, final int resultCode) {
562 Log.d(TAG, "onSyncDone: " + Utils.maskDebugInfo(mediaSet.getName()) + " result="
564 ((Activity) mActivity).runOnUiThread(new Runnable() {
567 if (resultCode == MediaSet.SYNC_RESULT_SUCCESS) {
568 mInitialSynced = true;
570 clearLoadingBit(BIT_LOADING_SYNC);
571 if (resultCode == MediaSet.SYNC_RESULT_ERROR && mIsActive) {
572 Toast.makeText((Context) mActivity, R.string.sync_album_error,
573 Toast.LENGTH_LONG).show();
579 private void setLoadingBit(int loadTaskBit) {
580 if (mLoadingBits == 0 && mIsActive) {
581 GalleryUtils.setSpinnerVisibility((Activity) mActivity, true);
583 mLoadingBits |= loadTaskBit;
586 private void clearLoadingBit(int loadTaskBit) {
587 mLoadingBits &= ~loadTaskBit;
588 if (mLoadingBits == 0 && mIsActive) {
589 GalleryUtils.setSpinnerVisibility((Activity) mActivity, false);
591 if (mAlbumDataAdapter.size() == 0) {
592 Toast.makeText((Context) mActivity,
593 R.string.empty_album, Toast.LENGTH_LONG).show();
594 mActivity.getStateManager().finishState(AlbumPage.this);
599 private class MyLoadingListener implements LoadingListener {
601 public void onLoadingStarted() {
602 setLoadingBit(BIT_LOADING_RELOAD);
606 public void onLoadingFinished() {
607 clearLoadingBit(BIT_LOADING_RELOAD);
611 private class MyDetailsSource implements DetailsHelper.DetailsSource {
615 return mAlbumDataAdapter.size();
618 public int getIndex() {
622 // If requested index is out of active window, suggest a valid index.
623 // If there is no valid index available, return -1.
624 public int findIndex(int indexHint) {
625 if (mAlbumDataAdapter.isActive(indexHint)) {
628 mIndex = mAlbumDataAdapter.getActiveStart();
629 if (!mAlbumDataAdapter.isActive(mIndex)) {
636 public MediaDetails getDetails() {
637 MediaObject item = mAlbumDataAdapter.get(mIndex);
639 mHighlightDrawer.setHighlightItem(item.getPath());
640 return item.getDetails();