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.net.Uri;
23 import android.os.Bundle;
24 import android.os.Vibrator;
25 import android.provider.MediaStore;
26 import android.view.ActionMode;
27 import android.view.Menu;
28 import android.view.MenuInflater;
29 import android.view.MenuItem;
30 import android.widget.Toast;
32 import com.android.gallery3d.R;
33 import com.android.gallery3d.common.Utils;
34 import com.android.gallery3d.data.DataManager;
35 import com.android.gallery3d.data.MediaDetails;
36 import com.android.gallery3d.data.MediaItem;
37 import com.android.gallery3d.data.MediaObject;
38 import com.android.gallery3d.data.MediaSet;
39 import com.android.gallery3d.data.MtpDevice;
40 import com.android.gallery3d.data.Path;
41 import com.android.gallery3d.ui.ActionModeHandler;
42 import com.android.gallery3d.ui.ActionModeHandler.ActionModeListener;
43 import com.android.gallery3d.ui.AlbumView;
44 import com.android.gallery3d.ui.DetailsHelper;
45 import com.android.gallery3d.ui.DetailsHelper.CloseListener;
46 import com.android.gallery3d.ui.GLCanvas;
47 import com.android.gallery3d.ui.GLView;
48 import com.android.gallery3d.ui.GridDrawer;
49 import com.android.gallery3d.ui.HighlightDrawer;
50 import com.android.gallery3d.ui.PositionProvider;
51 import com.android.gallery3d.ui.PositionRepository;
52 import com.android.gallery3d.ui.PositionRepository.Position;
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 import java.util.Random;
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 public static final String KEY_MEDIA_PATH = "media-path";
66 public static final String KEY_PARENT_MEDIA_PATH = "parent-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";
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;
75 private static final int BIT_LOADING_RELOAD = 1;
76 private static final int BIT_LOADING_SYNC = 2;
78 private static final float USER_DISTANCE_METER = 0.3f;
80 private boolean mIsActive = false;
81 private AlbumView mAlbumView;
82 private Path mMediaSetPath;
83 private String mParentMediaSetString;
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;
109 private final GLView mRootPane = new GLView() {
110 private final float mMatrix[] = new float[16];
113 protected void renderBackground(GLCanvas view) {
118 protected void onLayout(
119 boolean changed, int left, int top, int right, int bottom) {
121 int slotViewTop = GalleryActionBar.getHeight((Activity) mActivity);
122 int slotViewBottom = bottom - top;
123 int slotViewRight = right - left;
126 mDetailsHelper.layout(left, slotViewTop, right, bottom);
128 mAlbumView.setSelectionDrawer(mGridDrawer);
131 mAlbumView.layout(0, slotViewTop, slotViewRight, slotViewBottom);
132 GalleryUtils.setViewPointMatrix(mMatrix,
133 (right - left) / 2, (bottom - top) / 2, -mUserDistance);
134 PositionRepository.getInstance(mActivity).setOffset(
139 protected void render(GLCanvas canvas) {
140 canvas.save(GLCanvas.SAVE_FLAG_MATRIX);
141 canvas.multiplyMatrix(mMatrix, 0);
142 super.render(canvas);
148 protected void onBackPressed() {
151 } else if (mSelectionManager.inSelectionMode()) {
152 mSelectionManager.leaveSelectionMode();
154 mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
155 super.onBackPressed();
159 private void onDown(int index) {
160 MediaItem item = mAlbumDataAdapter.get(index);
161 Path path = (item == null) ? null : item.getPath();
162 mSelectionManager.setPressedPath(path);
163 mAlbumView.invalidate();
166 private void onUp() {
167 mSelectionManager.setPressedPath(null);
168 mAlbumView.invalidate();
171 public void onSingleTapUp(int slotIndex) {
172 MediaItem item = mAlbumDataAdapter.get(slotIndex);
174 Log.w(TAG, "item not ready yet, ignore the click");
178 mHighlightDrawer.setHighlightItem(item.getPath());
179 mDetailsHelper.reloadDetails(slotIndex);
180 } else if (!mSelectionManager.inSelectionMode()) {
184 // Get into the PhotoPage.
185 Bundle data = new Bundle();
186 mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
187 data.putInt(PhotoPage.KEY_INDEX_HINT, slotIndex);
188 data.putString(PhotoPage.KEY_MEDIA_SET_PATH,
189 mMediaSetPath.toString());
190 data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH,
191 item.getPath().toString());
192 mActivity.getStateManager().startStateForResult(
193 PhotoPage.class, REQUEST_PHOTO, data);
196 mSelectionManager.toggle(item.getPath());
197 mDetailsSource.findIndex(slotIndex);
198 mAlbumView.invalidate();
202 private void onGetContent(final MediaItem item) {
203 DataManager dm = mActivity.getDataManager();
204 Activity activity = (Activity) mActivity;
205 if (mData.getString(Gallery.EXTRA_CROP) != null) {
206 // TODO: Handle MtpImagew
207 Uri uri = dm.getContentUri(item.getPath());
208 Intent intent = new Intent(CropImage.ACTION_CROP, uri)
209 .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
210 .putExtras(getData());
211 if (mData.getParcelable(MediaStore.EXTRA_OUTPUT) == null) {
212 intent.putExtra(CropImage.KEY_RETURN_DATA, true);
214 activity.startActivity(intent);
217 activity.setResult(Activity.RESULT_OK,
218 new Intent(null, item.getContentUri()));
223 public void onLongTap(int slotIndex) {
224 if (mGetContent) return;
226 onSingleTapUp(slotIndex);
228 MediaItem item = mAlbumDataAdapter.get(slotIndex);
229 if (item == null) return;
230 mSelectionManager.setAutoLeaveSelectionMode(true);
231 mSelectionManager.toggle(item.getPath());
232 mDetailsSource.findIndex(slotIndex);
233 mAlbumView.invalidate();
237 public void doCluster(int clusterType) {
238 String basePath = mMediaSet.getPath().toString();
239 String newPath = FilterUtils.newClusterPath(basePath, clusterType);
240 Bundle data = new Bundle(getData());
241 data.putString(AlbumSetPage.KEY_MEDIA_PATH, newPath);
242 if (mShowClusterMenu) {
243 Context context = mActivity.getAndroidContext();
244 data.putString(AlbumSetPage.KEY_SET_TITLE, mMediaSet.getName());
245 data.putString(AlbumSetPage.KEY_SET_SUBTITLE,
246 GalleryActionBar.getClusterByTypeString(context, clusterType));
249 mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
250 mActivity.getStateManager().startStateForResult(
251 AlbumSetPage.class, REQUEST_DO_ANIMATION, data);
255 protected void onCreate(Bundle data, Bundle restoreState) {
256 mUserDistance = GalleryUtils.meterToPixel(USER_DISTANCE_METER);
258 initializeData(data);
259 mGetContent = data.getBoolean(Gallery.KEY_GET_CONTENT, false);
260 mShowClusterMenu = data.getBoolean(KEY_SHOW_CLUSTER_MENU, false);
261 mDetailsSource = new MyDetailsSource();
262 Context context = mActivity.getAndroidContext();
263 mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
265 startTransition(data);
267 // Enable auto-select-all for mtp album
268 if (data.getBoolean(KEY_AUTO_SELECT_ALL)) {
269 mSelectionManager.selectAll();
273 private void startTransition() {
274 final PositionRepository repository =
275 PositionRepository.getInstance(mActivity);
276 mAlbumView.startTransition(new PositionProvider() {
277 private final Position mTempPosition = new Position();
278 public Position getPosition(int identity, Position target) {
279 Position p = repository.get(identity);
280 if (p != null) return p;
281 mTempPosition.set(target);
282 mTempPosition.z = 128;
283 return mTempPosition;
288 private void startTransition(Bundle data) {
289 final PositionRepository repository =
290 PositionRepository.getInstance(mActivity);
291 final int[] center = data == null
293 : data.getIntArray(KEY_SET_CENTER);
294 final Random random = new Random();
295 mAlbumView.startTransition(new PositionProvider() {
296 private final Position mTempPosition = new Position();
297 public Position getPosition(int identity, Position target) {
298 Position p = repository.get(identity);
299 if (p != null) return p;
300 if (center != null) {
301 random.setSeed(identity);
302 mTempPosition.set(center[0], center[1],
303 0, random.nextInt(60) - 30, 0);
305 mTempPosition.set(target);
306 mTempPosition.z = 128;
308 return mTempPosition;
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 mGridDrawer = new GridDrawer((Context) mActivity, mSelectionManager);
365 Config.AlbumPage config = Config.AlbumPage.get((Context) mActivity);
366 mAlbumView = new AlbumView(mActivity, config.slotViewSpec,
367 0 /* don't cache thumbnail */);
368 mAlbumView.setSelectionDrawer(mGridDrawer);
369 mRootPane.addComponent(mAlbumView);
370 mAlbumView.setListener(new SlotView.SimpleListener() {
372 public void onDown(int index) {
373 AlbumPage.this.onDown(index);
378 AlbumPage.this.onUp();
382 public void onSingleTapUp(int slotIndex) {
383 AlbumPage.this.onSingleTapUp(slotIndex);
387 public void onLongTap(int slotIndex) {
388 AlbumPage.this.onLongTap(slotIndex);
391 mActionModeHandler = new ActionModeHandler(mActivity, mSelectionManager);
392 mActionModeHandler.setActionModeListener(new ActionModeListener() {
393 public boolean onActionItemClicked(MenuItem item) {
394 return onItemSelected(item);
399 private void initializeData(Bundle data) {
400 mMediaSetPath = Path.fromString(data.getString(KEY_MEDIA_PATH));
401 mParentMediaSetString = data.getString(KEY_PARENT_MEDIA_PATH);
402 mMediaSet = mActivity.getDataManager().getMediaSet(mMediaSetPath);
403 if (mMediaSet == null) {
404 Utils.fail("MediaSet is null. Path = %s", mMediaSetPath);
406 mSelectionManager.setSourceMediaSet(mMediaSet);
407 mAlbumDataAdapter = new AlbumDataAdapter(mActivity, mMediaSet);
408 mAlbumDataAdapter.setLoadingListener(new MyLoadingListener());
409 mAlbumView.setModel(mAlbumDataAdapter);
412 private void showDetails() {
414 if (mDetailsHelper == null) {
415 mHighlightDrawer = new HighlightDrawer(mActivity.getAndroidContext(),
417 mDetailsHelper = new DetailsHelper(mActivity, mRootPane, mDetailsSource);
418 mDetailsHelper.setCloseListener(new CloseListener() {
419 public void onClose() {
424 mAlbumView.setSelectionDrawer(mHighlightDrawer);
425 mDetailsHelper.show();
428 private void hideDetails() {
429 mShowDetails = false;
430 mDetailsHelper.hide();
431 mAlbumView.setSelectionDrawer(mGridDrawer);
432 mAlbumView.invalidate();
436 protected boolean onCreateActionBar(Menu menu) {
437 Activity activity = (Activity) mActivity;
438 GalleryActionBar actionBar = mActivity.getGalleryActionBar();
439 MenuInflater inflater = activity.getMenuInflater();
442 inflater.inflate(R.menu.pickup, menu);
443 int typeBits = mData.getInt(Gallery.KEY_TYPE_BITS,
444 DataManager.INCLUDE_IMAGE);
446 actionBar.setTitle(GalleryUtils.getSelectionModePrompt(typeBits));
448 inflater.inflate(R.menu.album, menu);
449 actionBar.setTitle(mMediaSet.getName());
450 if (mMediaSet instanceof MtpDevice) {
451 menu.findItem(R.id.action_slideshow).setVisible(false);
453 menu.findItem(R.id.action_slideshow).setVisible(true);
456 MenuItem groupBy = menu.findItem(R.id.action_group_by);
457 FilterUtils.setupMenuItems(actionBar, mMediaSetPath, true);
459 if (groupBy != null) {
460 groupBy.setVisible(mShowClusterMenu);
463 actionBar.setTitle(mMediaSet.getName());
465 actionBar.setSubtitle(null);
471 protected boolean onItemSelected(MenuItem item) {
472 switch (item.getItemId()) {
473 case android.R.id.home: {
474 if (mActivity.getStateManager().getStateCount() > 1) {
476 } else if (mParentMediaSetString != null) {
477 Activity a = (Activity) mActivity;
478 int flags = Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK;
479 Intent intent = new Intent()
480 .setClass(a, Gallery.class)
482 a.startActivity(intent);
486 case R.id.action_cancel:
487 mActivity.getStateManager().finishState(this);
489 case R.id.action_select:
490 mSelectionManager.setAutoLeaveSelectionMode(false);
491 mSelectionManager.enterSelectionMode();
493 case R.id.action_group_by: {
494 mActivity.getGalleryActionBar().showClusterDialog(this);
497 case R.id.action_slideshow: {
498 Bundle data = new Bundle();
499 data.putString(SlideshowPage.KEY_SET_PATH,
500 mMediaSetPath.toString());
501 data.putBoolean(SlideshowPage.KEY_REPEAT, true);
502 mActivity.getStateManager().startStateForResult(
503 SlideshowPage.class, REQUEST_SLIDESHOW, data);
506 case R.id.action_details: {
520 protected void onStateResult(int request, int result, Intent data) {
522 case REQUEST_SLIDESHOW: {
523 // data could be null, if there is no images in the album
524 if (data == null) return;
525 mFocusIndex = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0);
526 mAlbumView.setCenterIndex(mFocusIndex);
529 case REQUEST_PHOTO: {
530 if (data == null) return;
531 mFocusIndex = data.getIntExtra(PhotoPage.KEY_INDEX_HINT, 0);
532 mAlbumView.setCenterIndex(mFocusIndex);
536 case REQUEST_DO_ANIMATION: {
537 startTransition(null);
543 public void onSelectionModeChange(int mode) {
545 case SelectionManager.ENTER_SELECTION_MODE: {
546 mActionMode = mActionModeHandler.startActionMode();
547 mVibrator.vibrate(100);
550 case SelectionManager.LEAVE_SELECTION_MODE: {
551 mActionMode.finish();
552 mRootPane.invalidate();
555 case SelectionManager.SELECT_ALL_MODE: {
556 mActionModeHandler.updateSupportedOperation();
557 mRootPane.invalidate();
563 public void onSelectionChange(Path path, boolean selected) {
564 Utils.assertTrue(mActionMode != null);
565 int count = mSelectionManager.getSelectedCount();
566 String format = mActivity.getResources().getQuantityString(
567 R.plurals.number_of_items_selected, count);
568 mActionModeHandler.setTitle(String.format(format, count));
569 mActionModeHandler.updateSupportedOperation(path, selected);
573 public void onSyncDone(final MediaSet mediaSet, final int resultCode) {
574 Log.d(TAG, "onSyncDone: " + Utils.maskDebugInfo(mediaSet.getName()) + " result="
576 ((Activity) mActivity).runOnUiThread(new Runnable() {
579 if (resultCode == MediaSet.SYNC_RESULT_SUCCESS) {
580 mInitialSynced = true;
582 clearLoadingBit(BIT_LOADING_SYNC);
583 if (resultCode == MediaSet.SYNC_RESULT_ERROR && mIsActive) {
584 Toast.makeText((Context) mActivity, R.string.sync_album_error,
585 Toast.LENGTH_LONG).show();
591 private void setLoadingBit(int loadTaskBit) {
592 if (mLoadingBits == 0 && mIsActive) {
593 GalleryUtils.setSpinnerVisibility((Activity) mActivity, true);
595 mLoadingBits |= loadTaskBit;
598 private void clearLoadingBit(int loadTaskBit) {
599 mLoadingBits &= ~loadTaskBit;
600 if (mLoadingBits == 0 && mIsActive) {
601 GalleryUtils.setSpinnerVisibility((Activity) mActivity, false);
603 if (mAlbumDataAdapter.size() == 0) {
604 Toast.makeText((Context) mActivity,
605 R.string.empty_album, Toast.LENGTH_LONG).show();
606 mActivity.getStateManager().finishState(AlbumPage.this);
611 private class MyLoadingListener implements LoadingListener {
613 public void onLoadingStarted() {
614 setLoadingBit(BIT_LOADING_RELOAD);
618 public void onLoadingFinished() {
619 clearLoadingBit(BIT_LOADING_RELOAD);
623 private class MyDetailsSource implements DetailsHelper.DetailsSource {
627 return mAlbumDataAdapter.size();
630 public int getIndex() {
634 // If requested index is out of active window, suggest a valid index.
635 // If there is no valid index available, return -1.
636 public int findIndex(int indexHint) {
637 if (mAlbumDataAdapter.isActive(indexHint)) {
640 mIndex = mAlbumDataAdapter.getActiveStart();
641 if (!mAlbumDataAdapter.isActive(mIndex)) {
648 public MediaDetails getDetails() {
649 MediaObject item = mAlbumDataAdapter.get(mIndex);
651 mHighlightDrawer.setHighlightItem(item.getPath());
652 return item.getDetails();