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