AlbumPage.java revision 9201679ed1c485767f2e334aa618bd733024af03
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 com.android.gallery3d.R;
20import com.android.gallery3d.common.Utils;
21import com.android.gallery3d.data.DataManager;
22import com.android.gallery3d.data.MediaDetails;
23import com.android.gallery3d.data.MediaItem;
24import com.android.gallery3d.data.MediaObject;
25import com.android.gallery3d.data.MediaSet;
26import com.android.gallery3d.data.MtpDevice;
27import com.android.gallery3d.data.Path;
28import com.android.gallery3d.ui.ActionModeHandler;
29import com.android.gallery3d.ui.ActionModeHandler.ActionModeListener;
30import com.android.gallery3d.ui.AlbumView;
31import com.android.gallery3d.ui.DetailsHelper;
32import com.android.gallery3d.ui.DetailsHelper.CloseListener;
33import com.android.gallery3d.ui.GLCanvas;
34import com.android.gallery3d.ui.GLView;
35import com.android.gallery3d.ui.GridDrawer;
36import com.android.gallery3d.ui.HighlightDrawer;
37import com.android.gallery3d.ui.PositionProvider;
38import com.android.gallery3d.ui.PositionRepository;
39import com.android.gallery3d.ui.PositionRepository.Position;
40import com.android.gallery3d.ui.SelectionManager;
41import com.android.gallery3d.ui.SlotView;
42import com.android.gallery3d.ui.StaticBackground;
43import com.android.gallery3d.util.Future;
44import com.android.gallery3d.util.GalleryUtils;
45
46import android.app.Activity;
47import android.app.ProgressDialog;
48import android.content.Context;
49import android.content.Intent;
50import android.net.Uri;
51import android.os.Bundle;
52import android.provider.MediaStore;
53import android.view.ActionMode;
54import android.view.Menu;
55import android.view.MenuInflater;
56import android.view.MenuItem;
57import android.widget.Toast;
58
59import java.util.Random;
60
61public class AlbumPage extends ActivityState implements GalleryActionBar.ClusterRunner,
62        SelectionManager.SelectionListener {
63    @SuppressWarnings("unused")
64    private static final String TAG = "AlbumPage";
65
66    public static final String KEY_MEDIA_PATH = "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";
70
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;
74
75    private static final float USER_DISTANCE_METER = 0.3f;
76
77    private boolean mIsActive = false;
78    private StaticBackground mStaticBackground;
79    private AlbumView mAlbumView;
80    private Path mMediaSetPath;
81
82    private AlbumDataAdapter mAlbumDataAdapter;
83
84    protected SelectionManager mSelectionManager;
85    private GridDrawer mGridDrawer;
86    private HighlightDrawer mHighlightDrawer;
87
88    private boolean mGetContent;
89    private boolean mShowClusterMenu;
90
91    private ActionMode mActionMode;
92    private ActionModeHandler mActionModeHandler;
93    private int mFocusIndex = 0;
94    private DetailsHelper mDetailsHelper;
95    private MyDetailsSource mDetailsSource;
96    private MediaSet mMediaSet;
97    private boolean mShowDetails;
98    private float mUserDistance; // in pixel
99
100    private ProgressDialog mProgressDialog;
101    private Future<?> mPendingTask;
102
103    private Future<Void> mSyncTask = null;
104
105    private GLView mRootPane = new GLView() {
106        private float mMatrix[] = new float[16];
107
108        @Override
109        protected void onLayout(
110                boolean changed, int left, int top, int right, int bottom) {
111            mStaticBackground.layout(0, 0, right - left, bottom - top);
112
113            int slotViewTop = GalleryActionBar.getHeight((Activity) mActivity);
114            int slotViewBottom = bottom - top;
115            int slotViewRight = right - left;
116
117            if (mShowDetails) {
118                mDetailsHelper.layout(left, slotViewTop, right, bottom);
119            } else {
120                mAlbumView.setSelectionDrawer(mGridDrawer);
121            }
122
123            mAlbumView.layout(0, slotViewTop, slotViewRight, slotViewBottom);
124            GalleryUtils.setViewPointMatrix(mMatrix,
125                    (right - left) / 2, (bottom - top) / 2, -mUserDistance);
126            PositionRepository.getInstance(mActivity).setOffset(
127                    0, slotViewTop);
128        }
129
130        @Override
131        protected void render(GLCanvas canvas) {
132            canvas.save(GLCanvas.SAVE_FLAG_MATRIX);
133            canvas.multiplyMatrix(mMatrix, 0);
134            super.render(canvas);
135            canvas.restore();
136        }
137    };
138
139    @Override
140    protected void onBackPressed() {
141        if (mShowDetails) {
142            hideDetails();
143        } else if (mSelectionManager.inSelectionMode()) {
144            mSelectionManager.leaveSelectionMode();
145        } else {
146            mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
147            super.onBackPressed();
148        }
149    }
150
151    public void onSingleTapUp(int slotIndex) {
152        MediaItem item = mAlbumDataAdapter.get(slotIndex);
153        if (item == null) {
154            Log.w(TAG, "item not ready yet, ignore the click");
155            return;
156        }
157        if (mShowDetails) {
158            mHighlightDrawer.setHighlightItem(item.getPath());
159            mDetailsHelper.reloadDetails(slotIndex);
160        } else if (!mSelectionManager.inSelectionMode()) {
161            if (mGetContent) {
162                onGetContent(item);
163            } else {
164                boolean playVideo =
165                    (item.getSupportedOperations() & MediaItem.SUPPORT_PLAY) != 0;
166                if (playVideo) {
167                    // Play the video.
168                    PhotoPage.playVideo((Activity) mActivity, item.getPlayUri(), item.getName());
169                } else {
170                    // Get into the PhotoPage.
171                    Bundle data = new Bundle();
172                    mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
173                    data.putInt(PhotoPage.KEY_INDEX_HINT, slotIndex);
174                    data.putString(PhotoPage.KEY_MEDIA_SET_PATH,
175                            mMediaSetPath.toString());
176                    data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH,
177                            item.getPath().toString());
178                    mActivity.getStateManager().startStateForResult(
179                            PhotoPage.class, REQUEST_PHOTO, data);
180                }
181            }
182        } else {
183            mSelectionManager.toggle(item.getPath());
184            mAlbumView.invalidate();
185        }
186    }
187
188    private void onGetContent(final MediaItem item) {
189        DataManager dm = mActivity.getDataManager();
190        Activity activity = (Activity) mActivity;
191        if (mData.getString(Gallery.EXTRA_CROP) != null) {
192            // TODO: Handle MtpImagew
193            Uri uri = dm.getContentUri(item.getPath());
194            Intent intent = new Intent(CropImage.ACTION_CROP, uri)
195                    .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT)
196                    .putExtras(getData());
197            if (mData.getParcelable(MediaStore.EXTRA_OUTPUT) == null) {
198                intent.putExtra(CropImage.KEY_RETURN_DATA, true);
199            }
200            activity.startActivity(intent);
201            activity.finish();
202        } else {
203            activity.setResult(Activity.RESULT_OK,
204                    new Intent(null, item.getContentUri()));
205            activity.finish();
206        }
207    }
208
209    public void onLongTap(int slotIndex) {
210        if (mGetContent) return;
211        if (mShowDetails) {
212            onSingleTapUp(slotIndex);
213        } else {
214            MediaItem item = mAlbumDataAdapter.get(slotIndex);
215            if (item == null) return;
216            mSelectionManager.setAutoLeaveSelectionMode(true);
217            mSelectionManager.toggle(item.getPath());
218            mDetailsSource.findIndex(slotIndex);
219            mAlbumView.invalidate();
220        }
221    }
222
223    public void doCluster(int clusterType) {
224        String basePath = mMediaSet.getPath().toString();
225        String newPath = FilterUtils.newClusterPath(basePath, clusterType);
226        Bundle data = new Bundle(getData());
227        data.putString(AlbumSetPage.KEY_MEDIA_PATH, newPath);
228        if (mShowClusterMenu) {
229            Context context = mActivity.getAndroidContext();
230            data.putString(AlbumSetPage.KEY_SET_TITLE, mMediaSet.getName());
231            data.putString(AlbumSetPage.KEY_SET_SUBTITLE,
232                    GalleryActionBar.getClusterByTypeString(context, clusterType));
233        }
234
235        mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
236        mActivity.getStateManager().startStateForResult(
237                AlbumSetPage.class, REQUEST_DO_ANIMATION, data);
238    }
239
240    public void doFilter(int filterType) {
241        String basePath = mMediaSet.getPath().toString();
242        String newPath = FilterUtils.switchFilterPath(basePath, filterType);
243        Bundle data = new Bundle(getData());
244        data.putString(AlbumPage.KEY_MEDIA_PATH, newPath);
245        mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
246        mActivity.getStateManager().switchState(this, AlbumPage.class, data);
247    }
248
249    public void onOperationComplete() {
250        mAlbumView.invalidate();
251        // TODO: enable animation
252    }
253
254    @Override
255    protected void onCreate(Bundle data, Bundle restoreState) {
256        mUserDistance = GalleryUtils.meterToPixel(USER_DISTANCE_METER);
257        initializeViews();
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
263        startTransition(data);
264
265        // Enable auto-select-all for mtp album
266        if (data.getBoolean(KEY_AUTO_SELECT_ALL)) {
267            mSelectionManager.selectAll();
268        }
269    }
270
271    private void startTransition() {
272        final PositionRepository repository =
273                PositionRepository.getInstance(mActivity);
274        mAlbumView.startTransition(new PositionProvider() {
275            private Position mTempPosition = new Position();
276            public Position getPosition(long identity, Position target) {
277                Position p = repository.get(identity);
278                if (p != null) return p;
279                mTempPosition.set(target);
280                mTempPosition.z = 128;
281                return mTempPosition;
282            }
283        });
284    }
285
286    private void startTransition(Bundle data) {
287        final PositionRepository repository =
288                PositionRepository.getInstance(mActivity);
289        final int[] center = data == null
290                ? null
291                : data.getIntArray(KEY_SET_CENTER);
292        final Random random = new Random();
293        mAlbumView.startTransition(new PositionProvider() {
294            private Position mTempPosition = new Position();
295            public Position getPosition(long identity, Position target) {
296                Position p = repository.get(identity);
297                if (p != null) return p;
298                if (center != null) {
299                    random.setSeed(identity);
300                    mTempPosition.set(center[0], center[1],
301                            0, random.nextInt(60) - 30, 0);
302                } else {
303                    mTempPosition.set(target);
304                    mTempPosition.z = 128;
305                }
306                return mTempPosition;
307            }
308        });
309    }
310
311    @Override
312    protected void onResume() {
313        super.onResume();
314        mIsActive = true;
315        setContentPane(mRootPane);
316        mAlbumDataAdapter.resume();
317        mAlbumView.resume();
318        mActionModeHandler.resume();
319    }
320
321    @Override
322    protected void onPause() {
323        super.onPause();
324        mIsActive = false;
325        mAlbumDataAdapter.pause();
326        mAlbumView.pause();
327        DetailsHelper.pause();
328        Future<?> task = mPendingTask;
329        if (task != null) {
330            // cancel on going task
331            task.cancel();
332            task.waitDone();
333            if (mProgressDialog != null) {
334                mProgressDialog.dismiss();
335                mProgressDialog = null;
336            }
337        }
338        if (mSyncTask != null) {
339            mSyncTask.cancel();
340            mSyncTask = null;
341        }
342        mActionModeHandler.pause();
343    }
344
345    @Override
346    protected void onDestroy() {
347        if (mAlbumDataAdapter != null) {
348            mAlbumDataAdapter.setLoadingListener(null);
349        }
350    }
351
352    private void initializeViews() {
353        mStaticBackground = new StaticBackground((Context) mActivity);
354        mRootPane.addComponent(mStaticBackground);
355
356        mSelectionManager = new SelectionManager(mActivity, false);
357        mSelectionManager.setSelectionListener(this);
358        mGridDrawer = new GridDrawer((Context) mActivity, mSelectionManager);
359        Config.AlbumPage config = Config.AlbumPage.get((Context) mActivity);
360        mAlbumView = new AlbumView(mActivity, config.slotViewSpec,
361                0 /* don't cache thumbnail */);
362        mAlbumView.setSelectionDrawer(mGridDrawer);
363        mRootPane.addComponent(mAlbumView);
364        mAlbumView.setListener(new SlotView.SimpleListener() {
365            @Override
366            public void onSingleTapUp(int slotIndex) {
367                AlbumPage.this.onSingleTapUp(slotIndex);
368            }
369            @Override
370            public void onLongTap(int slotIndex) {
371                AlbumPage.this.onLongTap(slotIndex);
372            }
373        });
374        mActionModeHandler = new ActionModeHandler(mActivity, mSelectionManager);
375        mActionModeHandler.setActionModeListener(new ActionModeListener() {
376            public boolean onActionItemClicked(MenuItem item) {
377                return onItemSelected(item);
378            }
379        });
380        mStaticBackground.setImage(R.drawable.background,
381                R.drawable.background_portrait);
382    }
383
384    private void initializeData(Bundle data) {
385        mMediaSetPath = Path.fromString(data.getString(KEY_MEDIA_PATH));
386        mMediaSet = mActivity.getDataManager().getMediaSet(mMediaSetPath);
387        Utils.assertTrue(mMediaSet != null,
388                "MediaSet is null. Path = %s", mMediaSetPath);
389        mSelectionManager.setSourceMediaSet(mMediaSet);
390        mAlbumDataAdapter = new AlbumDataAdapter(mActivity, mMediaSet);
391        mAlbumDataAdapter.setLoadingListener(new MyLoadingListener());
392        mAlbumView.setModel(mAlbumDataAdapter);
393    }
394
395    private void showDetails() {
396        mShowDetails = true;
397        if (mDetailsHelper == null) {
398            mHighlightDrawer = new HighlightDrawer(mActivity.getAndroidContext());
399            mDetailsHelper = new DetailsHelper(mActivity, mRootPane, mDetailsSource);
400            mDetailsHelper.setCloseListener(new CloseListener() {
401                public void onClose() {
402                    hideDetails();
403                }
404            });
405        }
406        mAlbumView.setSelectionDrawer(mHighlightDrawer);
407        mDetailsHelper.show();
408    }
409
410    private void hideDetails() {
411        mShowDetails = false;
412        mDetailsHelper.hide();
413        mAlbumView.setSelectionDrawer(mGridDrawer);
414        mAlbumView.invalidate();
415    }
416
417    @Override
418    protected boolean onCreateActionBar(Menu menu) {
419        Activity activity = (Activity) mActivity;
420        GalleryActionBar actionBar = mActivity.getGalleryActionBar();
421        MenuInflater inflater = activity.getMenuInflater();
422
423        if (mGetContent) {
424            inflater.inflate(R.menu.pickup, menu);
425            int typeBits = mData.getInt(Gallery.KEY_TYPE_BITS,
426                    DataManager.INCLUDE_IMAGE);
427
428            actionBar.setTitle(GalleryUtils.getSelectionModePrompt(typeBits));
429        } else {
430            inflater.inflate(R.menu.album, menu);
431            actionBar.setTitle(mMediaSet.getName());
432            if (mMediaSet instanceof MtpDevice) {
433                menu.findItem(R.id.action_slideshow).setVisible(false);
434            } else {
435                menu.findItem(R.id.action_slideshow).setVisible(true);
436            }
437
438            MenuItem groupBy = menu.findItem(R.id.action_group_by);
439            FilterUtils.setupMenuItems(actionBar, mMediaSetPath, true);
440
441            if (groupBy != null) {
442                groupBy.setVisible(mShowClusterMenu);
443            }
444
445            actionBar.setTitle(mMediaSet.getName());
446        }
447        actionBar.setSubtitle(null);
448
449        return true;
450    }
451
452    @Override
453    protected boolean onItemSelected(MenuItem item) {
454        switch (item.getItemId()) {
455            case R.id.action_cancel:
456                mActivity.getStateManager().finishState(this);
457                return true;
458            case R.id.action_select:
459                mSelectionManager.setAutoLeaveSelectionMode(false);
460                mSelectionManager.enterSelectionMode();
461                return true;
462            case R.id.action_group_by: {
463                mActivity.getGalleryActionBar().showClusterDialog(this);
464                return true;
465            }
466            case R.id.action_slideshow: {
467                Bundle data = new Bundle();
468                data.putString(SlideshowPage.KEY_SET_PATH,
469                        mMediaSetPath.toString());
470                data.putBoolean(SlideshowPage.KEY_REPEAT, true);
471                mActivity.getStateManager().startStateForResult(
472                        SlideshowPage.class, REQUEST_SLIDESHOW, data);
473                return true;
474            }
475            case R.id.action_details: {
476                if (mShowDetails) {
477                    hideDetails();
478                } else {
479                    showDetails();
480                }
481                return true;
482            }
483            default:
484                return false;
485        }
486    }
487
488    @Override
489    protected void onStateResult(int request, int result, Intent data) {
490        switch (request) {
491            case REQUEST_SLIDESHOW: {
492                // data could be null, if there is no images in the album
493                if (data == null) return;
494                mFocusIndex = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0);
495                mAlbumView.setCenterIndex(mFocusIndex);
496                break;
497            }
498            case REQUEST_PHOTO: {
499                if (data == null) return;
500                mFocusIndex = data.getIntExtra(PhotoPage.KEY_INDEX_HINT, 0);
501                mAlbumView.setCenterIndex(mFocusIndex);
502                startTransition();
503                break;
504            }
505            case REQUEST_DO_ANIMATION: {
506                startTransition(null);
507                break;
508            }
509        }
510    }
511
512    public void onSelectionModeChange(int mode) {
513        switch (mode) {
514            case SelectionManager.ENTER_SELECTION_MODE: {
515                mActionMode = mActionModeHandler.startActionMode();
516                break;
517            }
518            case SelectionManager.LEAVE_SELECTION_MODE: {
519                mActionMode.finish();
520                mRootPane.invalidate();
521                break;
522            }
523            case SelectionManager.SELECT_ALL_MODE: {
524                int count = mSelectionManager.getSelectedCount();
525                String format = mActivity.getResources().getQuantityString(
526                        R.plurals.number_of_items_selected, count);
527                mActionModeHandler.setTitle(String.format(format, count));
528                mActionModeHandler.updateSupportedOperation();
529                mRootPane.invalidate();
530                break;
531            }
532        }
533    }
534
535    public void onSelectionChange(Path path, boolean selected) {
536        Utils.assertTrue(mActionMode != null);
537        int count = mSelectionManager.getSelectedCount();
538        String format = mActivity.getResources().getQuantityString(
539                R.plurals.number_of_items_selected, count);
540        mActionModeHandler.setTitle(String.format(format, count));
541        mActionModeHandler.updateSupportedOperation(path, selected);
542    }
543
544    private class MyLoadingListener implements LoadingListener {
545        @Override
546        public void onLoadingStarted() {
547            GalleryUtils.setSpinnerVisibility((Activity) mActivity, true);
548        }
549
550        @Override
551        public void onLoadingFinished() {
552            if (!mIsActive) return;
553            if (mAlbumDataAdapter.size() == 0) {
554                if (mSyncTask == null) {
555                    mSyncTask = mMediaSet.requestSync();
556                }
557                if (mSyncTask.isDone()){
558                    Toast.makeText((Context) mActivity,
559                            R.string.empty_album, Toast.LENGTH_LONG).show();
560                    mActivity.getStateManager().finishState(AlbumPage.this);
561                }
562            }
563            if (mSyncTask == null || mSyncTask.isDone()) {
564                GalleryUtils.setSpinnerVisibility((Activity) mActivity, false);
565            }
566        }
567    }
568
569    private class MyDetailsSource implements DetailsHelper.DetailsSource {
570        private int mIndex;
571        public int size() {
572            return mAlbumDataAdapter.size();
573        }
574
575        public int getIndex() {
576            return mIndex;
577        }
578
579        // If requested index is out of active window, suggest a valid index.
580        // If there is no valid index available, return -1.
581        public int findIndex(int indexHint) {
582            if (mAlbumDataAdapter.isActive(indexHint)) {
583                mIndex = indexHint;
584            } else {
585                mIndex = mAlbumDataAdapter.getActiveStart();
586                if (!mAlbumDataAdapter.isActive(mIndex)) {
587                    return -1;
588                }
589            }
590            return mIndex;
591        }
592
593        public MediaDetails getDetails() {
594            MediaObject item = mAlbumDataAdapter.get(mIndex);
595            if (item != null) {
596                mHighlightDrawer.setHighlightItem(item.getPath());
597                return item.getDetails();
598            } else {
599                return null;
600            }
601        }
602    }
603}
604