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.app.ProgressDialog;
21 import android.content.Context;
22 import android.content.Intent;
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.PositionProvider;
52 import com.android.gallery3d.ui.PositionRepository;
53 import com.android.gallery3d.ui.PositionRepository.Position;
54 import com.android.gallery3d.ui.SelectionManager;
55 import com.android.gallery3d.ui.SlotView;
56 import com.android.gallery3d.ui.StaticBackground;
57 import com.android.gallery3d.util.Future;
58 import com.android.gallery3d.util.GalleryUtils;
60 import java.util.Random;
62 public class AlbumPage extends ActivityState implements GalleryActionBar.ClusterRunner,
63 SelectionManager.SelectionListener, MediaSet.SyncListener {
64 @SuppressWarnings("unused")
65 private static final String TAG = "AlbumPage";
67 public static final String KEY_MEDIA_PATH = "media-path";
68 public static final String KEY_SET_CENTER = "set-center";
69 public static final String KEY_AUTO_SELECT_ALL = "auto-select-all";
70 public static final String KEY_SHOW_CLUSTER_MENU = "cluster-menu";
72 private static final int REQUEST_SLIDESHOW = 1;
73 private static final int REQUEST_PHOTO = 2;
74 private static final int REQUEST_DO_ANIMATION = 3;
76 private static final float USER_DISTANCE_METER = 0.3f;
78 private boolean mIsActive = false;
79 private StaticBackground mStaticBackground;
80 private AlbumView mAlbumView;
81 private Path mMediaSetPath;
83 private AlbumDataAdapter mAlbumDataAdapter;
85 protected SelectionManager mSelectionManager;
86 private Vibrator mVibrator;
87 private GridDrawer mGridDrawer;
88 private HighlightDrawer mHighlightDrawer;
90 private boolean mGetContent;
91 private boolean mShowClusterMenu;
93 private ActionMode mActionMode;
94 private ActionModeHandler mActionModeHandler;
95 private int mFocusIndex = 0;
96 private DetailsHelper mDetailsHelper;
97 private MyDetailsSource mDetailsSource;
98 private MediaSet mMediaSet;
99 private boolean mShowDetails;
100 private float mUserDistance; // in pixel
102 private ProgressDialog mProgressDialog;
103 private Future<?> mPendingTask;
105 private Future<Integer> mSyncTask = null;
107 private final GLView mRootPane = new GLView() {
108 private final float mMatrix[] = new float[16];
111 protected void onLayout(
112 boolean changed, int left, int top, int right, int bottom) {
113 mStaticBackground.layout(0, 0, right - left, bottom - top);
115 int slotViewTop = GalleryActionBar.getHeight((Activity) mActivity);
116 int slotViewBottom = bottom - top;
117 int slotViewRight = right - left;
120 mDetailsHelper.layout(left, slotViewTop, right, bottom);
122 mAlbumView.setSelectionDrawer(mGridDrawer);
125 mAlbumView.layout(0, slotViewTop, slotViewRight, slotViewBottom);
126 GalleryUtils.setViewPointMatrix(mMatrix,
127 (right - left) / 2, (bottom - top) / 2, -mUserDistance);
128 PositionRepository.getInstance(mActivity).setOffset(
133 protected void render(GLCanvas canvas) {
134 canvas.save(GLCanvas.SAVE_FLAG_MATRIX);
135 canvas.multiplyMatrix(mMatrix, 0);
136 super.render(canvas);
142 protected void onBackPressed() {
145 } else if (mSelectionManager.inSelectionMode()) {
146 mSelectionManager.leaveSelectionMode();
148 mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
149 super.onBackPressed();
153 private void onDown(int index) {
154 MediaItem item = mAlbumDataAdapter.get(index);
155 Path path = (item == null) ? null : item.getPath();
156 mSelectionManager.setPressedPath(path);
157 mAlbumView.invalidate();
160 private void onUp() {
161 mSelectionManager.setPressedPath(null);
162 mAlbumView.invalidate();
165 public void onSingleTapUp(int slotIndex) {
166 MediaItem item = mAlbumDataAdapter.get(slotIndex);
168 Log.w(TAG, "item not ready yet, ignore the click");
172 mHighlightDrawer.setHighlightItem(item.getPath());
173 mDetailsHelper.reloadDetails(slotIndex);
174 } else if (!mSelectionManager.inSelectionMode()) {
179 (item.getSupportedOperations() & MediaItem.SUPPORT_PLAY) != 0;
182 PhotoPage.playVideo((Activity) mActivity, item.getPlayUri(), item.getName());
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);
197 mSelectionManager.toggle(item.getPath());
198 mDetailsSource.findIndex(slotIndex);
199 mAlbumView.invalidate();
203 private void onGetContent(final MediaItem item) {
204 DataManager dm = mActivity.getDataManager();
205 Activity activity = (Activity) mActivity;
206 if (mData.getString(Gallery.EXTRA_CROP) != null) {
207 // TODO: Handle MtpImagew
208 Uri uri = dm.getContentUri(item.getPath());
209 Intent intent = new Intent(CropImage.ACTION_CROP, uri)
210 .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
211 .putExtras(getData());
212 if (mData.getParcelable(MediaStore.EXTRA_OUTPUT) == null) {
213 intent.putExtra(CropImage.KEY_RETURN_DATA, true);
215 activity.startActivity(intent);
218 activity.setResult(Activity.RESULT_OK,
219 new Intent(null, item.getContentUri()));
224 public void onLongTap(int slotIndex) {
225 if (mGetContent) return;
227 onSingleTapUp(slotIndex);
229 MediaItem item = mAlbumDataAdapter.get(slotIndex);
230 if (item == null) return;
231 mSelectionManager.setAutoLeaveSelectionMode(true);
232 mSelectionManager.toggle(item.getPath());
233 mDetailsSource.findIndex(slotIndex);
234 mAlbumView.invalidate();
238 public void doCluster(int clusterType) {
239 String basePath = mMediaSet.getPath().toString();
240 String newPath = FilterUtils.newClusterPath(basePath, clusterType);
241 Bundle data = new Bundle(getData());
242 data.putString(AlbumSetPage.KEY_MEDIA_PATH, newPath);
243 if (mShowClusterMenu) {
244 Context context = mActivity.getAndroidContext();
245 data.putString(AlbumSetPage.KEY_SET_TITLE, mMediaSet.getName());
246 data.putString(AlbumSetPage.KEY_SET_SUBTITLE,
247 GalleryActionBar.getClusterByTypeString(context, clusterType));
250 mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
251 mActivity.getStateManager().startStateForResult(
252 AlbumSetPage.class, REQUEST_DO_ANIMATION, data);
255 public void doFilter(int filterType) {
256 String basePath = mMediaSet.getPath().toString();
257 String newPath = FilterUtils.switchFilterPath(basePath, filterType);
258 Bundle data = new Bundle(getData());
259 data.putString(AlbumPage.KEY_MEDIA_PATH, newPath);
260 mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
261 mActivity.getStateManager().switchState(this, AlbumPage.class, data);
264 public void onOperationComplete() {
265 mAlbumView.invalidate();
266 // TODO: enable animation
270 protected void onCreate(Bundle data, Bundle restoreState) {
271 mUserDistance = GalleryUtils.meterToPixel(USER_DISTANCE_METER);
273 initializeData(data);
274 mGetContent = data.getBoolean(Gallery.KEY_GET_CONTENT, false);
275 mShowClusterMenu = data.getBoolean(KEY_SHOW_CLUSTER_MENU, false);
276 mDetailsSource = new MyDetailsSource();
277 Context context = mActivity.getAndroidContext();
278 mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
280 startTransition(data);
282 // Enable auto-select-all for mtp album
283 if (data.getBoolean(KEY_AUTO_SELECT_ALL)) {
284 mSelectionManager.selectAll();
288 private void startTransition() {
289 final PositionRepository repository =
290 PositionRepository.getInstance(mActivity);
291 mAlbumView.startTransition(new PositionProvider() {
292 private final Position mTempPosition = new Position();
293 public Position getPosition(long identity, Position target) {
294 Position p = repository.get(identity);
295 if (p != null) return p;
296 mTempPosition.set(target);
297 mTempPosition.z = 128;
298 return mTempPosition;
303 private void startTransition(Bundle data) {
304 final PositionRepository repository =
305 PositionRepository.getInstance(mActivity);
306 final int[] center = data == null
308 : data.getIntArray(KEY_SET_CENTER);
309 final Random random = new Random();
310 mAlbumView.startTransition(new PositionProvider() {
311 private final Position mTempPosition = new Position();
312 public Position getPosition(long identity, Position target) {
313 Position p = repository.get(identity);
314 if (p != null) return p;
315 if (center != null) {
316 random.setSeed(identity);
317 mTempPosition.set(center[0], center[1],
318 0, random.nextInt(60) - 30, 0);
320 mTempPosition.set(target);
321 mTempPosition.z = 128;
323 return mTempPosition;
329 protected void onResume() {
332 setContentPane(mRootPane);
333 mAlbumDataAdapter.resume();
335 mActionModeHandler.resume();
339 protected void onPause() {
342 mAlbumDataAdapter.pause();
344 DetailsHelper.pause();
345 Future<?> task = mPendingTask;
347 // cancel on going task
350 if (mProgressDialog != null) {
351 mProgressDialog.dismiss();
352 mProgressDialog = null;
355 if (mSyncTask != null) {
359 mActionModeHandler.pause();
363 protected void onDestroy() {
365 if (mAlbumDataAdapter != null) {
366 mAlbumDataAdapter.setLoadingListener(null);
370 private void initializeViews() {
371 mStaticBackground = new StaticBackground((Context) mActivity);
372 mRootPane.addComponent(mStaticBackground);
374 mSelectionManager = new SelectionManager(mActivity, false);
375 mSelectionManager.setSelectionListener(this);
376 mGridDrawer = new GridDrawer((Context) mActivity, mSelectionManager);
377 Config.AlbumPage config = Config.AlbumPage.get((Context) mActivity);
378 mAlbumView = new AlbumView(mActivity, config.slotViewSpec,
379 0 /* don't cache thumbnail */);
380 mAlbumView.setSelectionDrawer(mGridDrawer);
381 mRootPane.addComponent(mAlbumView);
382 mAlbumView.setListener(new SlotView.SimpleListener() {
384 public void onDown(int index) {
385 AlbumPage.this.onDown(index);
390 AlbumPage.this.onUp();
394 public void onSingleTapUp(int slotIndex) {
395 AlbumPage.this.onSingleTapUp(slotIndex);
399 public void onLongTap(int slotIndex) {
400 AlbumPage.this.onLongTap(slotIndex);
403 mActionModeHandler = new ActionModeHandler(mActivity, mSelectionManager);
404 mActionModeHandler.setActionModeListener(new ActionModeListener() {
405 public boolean onActionItemClicked(MenuItem item) {
406 return onItemSelected(item);
409 mStaticBackground.setImage(R.drawable.background,
410 R.drawable.background_portrait);
413 private void initializeData(Bundle data) {
414 mMediaSetPath = Path.fromString(data.getString(KEY_MEDIA_PATH));
415 mMediaSet = mActivity.getDataManager().getMediaSet(mMediaSetPath);
416 Utils.assertTrue(mMediaSet != null,
417 "MediaSet is null. Path = %s", mMediaSetPath);
418 mSelectionManager.setSourceMediaSet(mMediaSet);
419 mAlbumDataAdapter = new AlbumDataAdapter(mActivity, mMediaSet);
420 mAlbumDataAdapter.setLoadingListener(new MyLoadingListener());
421 mAlbumView.setModel(mAlbumDataAdapter);
424 private void showDetails() {
426 if (mDetailsHelper == null) {
427 mHighlightDrawer = new HighlightDrawer(mActivity.getAndroidContext(),
429 mDetailsHelper = new DetailsHelper(mActivity, mRootPane, mDetailsSource);
430 mDetailsHelper.setCloseListener(new CloseListener() {
431 public void onClose() {
436 mAlbumView.setSelectionDrawer(mHighlightDrawer);
437 mDetailsHelper.show();
440 private void hideDetails() {
441 mShowDetails = false;
442 mDetailsHelper.hide();
443 mAlbumView.setSelectionDrawer(mGridDrawer);
444 mAlbumView.invalidate();
448 protected boolean onCreateActionBar(Menu menu) {
449 Activity activity = (Activity) mActivity;
450 GalleryActionBar actionBar = mActivity.getGalleryActionBar();
451 MenuInflater inflater = activity.getMenuInflater();
454 inflater.inflate(R.menu.pickup, menu);
455 int typeBits = mData.getInt(Gallery.KEY_TYPE_BITS,
456 DataManager.INCLUDE_IMAGE);
458 actionBar.setTitle(GalleryUtils.getSelectionModePrompt(typeBits));
460 inflater.inflate(R.menu.album, menu);
461 actionBar.setTitle(mMediaSet.getName());
462 if (mMediaSet instanceof MtpDevice) {
463 menu.findItem(R.id.action_slideshow).setVisible(false);
465 menu.findItem(R.id.action_slideshow).setVisible(true);
468 MenuItem groupBy = menu.findItem(R.id.action_group_by);
469 FilterUtils.setupMenuItems(actionBar, mMediaSetPath, true);
471 if (groupBy != null) {
472 groupBy.setVisible(mShowClusterMenu);
475 actionBar.setTitle(mMediaSet.getName());
477 actionBar.setSubtitle(null);
483 protected boolean onItemSelected(MenuItem item) {
484 switch (item.getItemId()) {
485 case R.id.action_cancel:
486 mActivity.getStateManager().finishState(this);
488 case R.id.action_select:
489 mSelectionManager.setAutoLeaveSelectionMode(false);
490 mSelectionManager.enterSelectionMode();
492 case R.id.action_group_by: {
493 mActivity.getGalleryActionBar().showClusterDialog(this);
496 case R.id.action_slideshow: {
497 Bundle data = new Bundle();
498 data.putString(SlideshowPage.KEY_SET_PATH,
499 mMediaSetPath.toString());
500 data.putBoolean(SlideshowPage.KEY_REPEAT, true);
501 mActivity.getStateManager().startStateForResult(
502 SlideshowPage.class, REQUEST_SLIDESHOW, data);
505 case R.id.action_details: {
519 protected void onStateResult(int request, int result, Intent data) {
521 case REQUEST_SLIDESHOW: {
522 // data could be null, if there is no images in the album
523 if (data == null) return;
524 mFocusIndex = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0);
525 mAlbumView.setCenterIndex(mFocusIndex);
528 case REQUEST_PHOTO: {
529 if (data == null) return;
530 mFocusIndex = data.getIntExtra(PhotoPage.KEY_INDEX_HINT, 0);
531 mAlbumView.setCenterIndex(mFocusIndex);
535 case REQUEST_DO_ANIMATION: {
536 startTransition(null);
542 public void onSelectionModeChange(int mode) {
544 case SelectionManager.ENTER_SELECTION_MODE: {
545 mActionMode = mActionModeHandler.startActionMode();
546 mVibrator.vibrate(100);
549 case SelectionManager.LEAVE_SELECTION_MODE: {
550 mActionMode.finish();
551 mRootPane.invalidate();
554 case SelectionManager.SELECT_ALL_MODE: {
555 mActionModeHandler.updateSupportedOperation();
556 mRootPane.invalidate();
562 public void onSelectionChange(Path path, boolean selected) {
563 Utils.assertTrue(mActionMode != null);
564 int count = mSelectionManager.getSelectedCount();
565 String format = mActivity.getResources().getQuantityString(
566 R.plurals.number_of_items_selected, count);
567 mActionModeHandler.setTitle(String.format(format, count));
568 mActionModeHandler.updateSupportedOperation(path, selected);
572 public void onSyncDone(final MediaSet mediaSet, final int resultCode) {
573 Log.d(TAG, "onSyncDone: " + Utils.maskDebugInfo(mediaSet.getName()) + " result="
575 ((Activity) mActivity).runOnUiThread(new Runnable() {
578 if (!mIsActive) return;
579 mediaSet.notifyContentChanged(); // force reload to handle spinner
581 if (resultCode == MediaSet.SYNC_RESULT_ERROR) {
582 Toast.makeText((Context) mActivity, R.string.sync_album_error,
583 Toast.LENGTH_LONG).show();
589 private class MyLoadingListener implements LoadingListener {
591 public void onLoadingStarted() {
592 GalleryUtils.setSpinnerVisibility((Activity) mActivity, true);
596 public void onLoadingFinished() {
597 if (!mIsActive) return;
598 if (mAlbumDataAdapter.size() == 0) {
599 if (mSyncTask == null) {
600 mSyncTask = mMediaSet.requestSync(AlbumPage.this);
602 if (mSyncTask.isDone()){
603 Toast.makeText((Context) mActivity,
604 R.string.empty_album, Toast.LENGTH_LONG).show();
605 mActivity.getStateManager().finishState(AlbumPage.this);
608 if (mSyncTask == null || mSyncTask.isDone()) {
609 GalleryUtils.setSpinnerVisibility((Activity) mActivity, false);
614 private class MyDetailsSource implements DetailsHelper.DetailsSource {
617 return mAlbumDataAdapter.size();
620 public int getIndex() {
624 // If requested index is out of active window, suggest a valid index.
625 // If there is no valid index available, return -1.
626 public int findIndex(int indexHint) {
627 if (mAlbumDataAdapter.isActive(indexHint)) {
630 mIndex = mAlbumDataAdapter.getActiveStart();
631 if (!mAlbumDataAdapter.isActive(mIndex)) {
638 public MediaDetails getDetails() {
639 MediaObject item = mAlbumDataAdapter.get(mIndex);
641 mHighlightDrawer.setHighlightItem(item.getPath());
642 return item.getDetails();