AlbumPage.java revision 8cab3e872dd95e55ba34fdb94269a0c5069e72ae
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_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";
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 int BIT_LOADING_RELOAD = 1;
76    private static final int BIT_LOADING_SYNC = 2;
77
78    private static final float USER_DISTANCE_METER = 0.3f;
79
80    private boolean mIsActive = false;
81    private AlbumView mAlbumView;
82    private Path mMediaSetPath;
83    private String mParentMediaSetString;
84
85    private AlbumDataAdapter mAlbumDataAdapter;
86
87    protected SelectionManager mSelectionManager;
88    private Vibrator mVibrator;
89    private GridDrawer mGridDrawer;
90    private HighlightDrawer mHighlightDrawer;
91
92    private boolean mGetContent;
93    private boolean mShowClusterMenu;
94
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
103
104    private Future<Integer> mSyncTask = null;
105
106    private int mLoadingBits = 0;
107    private boolean mInitialSynced = false;
108
109    private final GLView mRootPane = new GLView() {
110        private final float mMatrix[] = new float[16];
111
112        @Override
113        protected void renderBackground(GLCanvas view) {
114            view.clearBuffer();
115        }
116
117        @Override
118        protected void onLayout(
119                boolean changed, int left, int top, int right, int bottom) {
120
121            int slotViewTop = mActivity.getGalleryActionBar().getHeight();
122            int slotViewBottom = bottom - top;
123            int slotViewRight = right - left;
124
125            if (mShowDetails) {
126                mDetailsHelper.layout(left, slotViewTop, right, bottom);
127            } else {
128                mAlbumView.setSelectionDrawer(mGridDrawer);
129            }
130
131            mAlbumView.layout(0, slotViewTop, slotViewRight, slotViewBottom);
132            GalleryUtils.setViewPointMatrix(mMatrix,
133                    (right - left) / 2, (bottom - top) / 2, -mUserDistance);
134            PositionRepository.getInstance(mActivity).setOffset(
135                    0, slotViewTop);
136        }
137
138        @Override
139        protected void render(GLCanvas canvas) {
140            canvas.save(GLCanvas.SAVE_FLAG_MATRIX);
141            canvas.multiplyMatrix(mMatrix, 0);
142            super.render(canvas);
143            canvas.restore();
144        }
145    };
146
147    @Override
148    protected void onBackPressed() {
149        if (mShowDetails) {
150            hideDetails();
151        } else if (mSelectionManager.inSelectionMode()) {
152            mSelectionManager.leaveSelectionMode();
153        } else {
154            mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
155            super.onBackPressed();
156        }
157    }
158
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();
164    }
165
166    private void onUp() {
167        mSelectionManager.setPressedPath(null);
168        mAlbumView.invalidate();
169    }
170
171    public void onSingleTapUp(int slotIndex) {
172        MediaItem item = mAlbumDataAdapter.get(slotIndex);
173        if (item == null) {
174            Log.w(TAG, "item not ready yet, ignore the click");
175            return;
176        }
177        if (mShowDetails) {
178            mHighlightDrawer.setHighlightItem(item.getPath());
179            mDetailsHelper.reloadDetails(slotIndex);
180        } else if (!mSelectionManager.inSelectionMode()) {
181            if (mGetContent) {
182                onGetContent(item);
183            } else {
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);
194            }
195        } else {
196            mSelectionManager.toggle(item.getPath());
197            mDetailsSource.findIndex(slotIndex);
198            mAlbumView.invalidate();
199        }
200    }
201
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);
213            }
214            activity.startActivity(intent);
215            activity.finish();
216        } else {
217            activity.setResult(Activity.RESULT_OK,
218                    new Intent(null, item.getContentUri()));
219            activity.finish();
220        }
221    }
222
223    public void onLongTap(int slotIndex) {
224        if (mGetContent) return;
225        if (mShowDetails) {
226            onSingleTapUp(slotIndex);
227        } else {
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();
234        }
235    }
236
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));
247        }
248
249        mAlbumView.savePositions(PositionRepository.getInstance(mActivity));
250        mActivity.getStateManager().startStateForResult(
251                AlbumSetPage.class, REQUEST_DO_ANIMATION, data);
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        Context context = mActivity.getAndroidContext();
263        mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
264
265        startTransition(data);
266
267        // Enable auto-select-all for mtp album
268        if (data.getBoolean(KEY_AUTO_SELECT_ALL)) {
269            mSelectionManager.selectAll();
270        }
271    }
272
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;
284            }
285        });
286    }
287
288    private void startTransition(Bundle data) {
289        final PositionRepository repository =
290                PositionRepository.getInstance(mActivity);
291        final int[] center = data == null
292                ? 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);
304                } else {
305                    mTempPosition.set(target);
306                    mTempPosition.z = 128;
307                }
308                return mTempPosition;
309            }
310        });
311    }
312
313    @Override
314    protected void onResume() {
315        super.onResume();
316        mIsActive = true;
317        setContentPane(mRootPane);
318
319        Path path = mMediaSet.getPath();
320        boolean enableHomeButton = (mActivity.getStateManager().getStateCount() > 1) |
321                mParentMediaSetString != null;
322        mActivity.getGalleryActionBar().setDisplayOptions(enableHomeButton, true);
323
324        // Set the reload bit here to prevent it exit this page in clearLoadingBit().
325        setLoadingBit(BIT_LOADING_RELOAD);
326        mAlbumDataAdapter.resume();
327
328        mAlbumView.resume();
329        mActionModeHandler.resume();
330        if (!mInitialSynced) {
331            setLoadingBit(BIT_LOADING_SYNC);
332            mSyncTask = mMediaSet.requestSync(this);
333        }
334    }
335
336    @Override
337    protected void onPause() {
338        super.onPause();
339        mIsActive = false;
340        mAlbumDataAdapter.pause();
341        mAlbumView.pause();
342        DetailsHelper.pause();
343
344        if (mSyncTask != null) {
345            mSyncTask.cancel();
346            mSyncTask = null;
347            clearLoadingBit(BIT_LOADING_SYNC);
348        }
349        mActionModeHandler.pause();
350        GalleryUtils.setSpinnerVisibility((Activity) mActivity, false);
351    }
352
353    @Override
354    protected void onDestroy() {
355        super.onDestroy();
356        if (mAlbumDataAdapter != null) {
357            mAlbumDataAdapter.setLoadingListener(null);
358        }
359    }
360
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() {
371            @Override
372            public void onDown(int index) {
373                AlbumPage.this.onDown(index);
374            }
375
376            @Override
377            public void onUp() {
378                AlbumPage.this.onUp();
379            }
380
381            @Override
382            public void onSingleTapUp(int slotIndex) {
383                AlbumPage.this.onSingleTapUp(slotIndex);
384            }
385
386            @Override
387            public void onLongTap(int slotIndex) {
388                AlbumPage.this.onLongTap(slotIndex);
389            }
390        });
391        mActionModeHandler = new ActionModeHandler(mActivity, mSelectionManager);
392        mActionModeHandler.setActionModeListener(new ActionModeListener() {
393            public boolean onActionItemClicked(MenuItem item) {
394                return onItemSelected(item);
395            }
396        });
397    }
398
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);
405        }
406        mSelectionManager.setSourceMediaSet(mMediaSet);
407        mAlbumDataAdapter = new AlbumDataAdapter(mActivity, mMediaSet);
408        mAlbumDataAdapter.setLoadingListener(new MyLoadingListener());
409        mAlbumView.setModel(mAlbumDataAdapter);
410    }
411
412    private void showDetails() {
413        mShowDetails = true;
414        if (mDetailsHelper == null) {
415            mHighlightDrawer = new HighlightDrawer(mActivity.getAndroidContext(),
416                    mSelectionManager);
417            mDetailsHelper = new DetailsHelper(mActivity, mRootPane, mDetailsSource);
418            mDetailsHelper.setCloseListener(new CloseListener() {
419                public void onClose() {
420                    hideDetails();
421                }
422            });
423        }
424        mAlbumView.setSelectionDrawer(mHighlightDrawer);
425        mDetailsHelper.show();
426    }
427
428    private void hideDetails() {
429        mShowDetails = false;
430        mDetailsHelper.hide();
431        mAlbumView.setSelectionDrawer(mGridDrawer);
432        mAlbumView.invalidate();
433    }
434
435    @Override
436    protected boolean onCreateActionBar(Menu menu) {
437        Activity activity = (Activity) mActivity;
438        GalleryActionBar actionBar = mActivity.getGalleryActionBar();
439        MenuInflater inflater = activity.getMenuInflater();
440
441        if (mGetContent) {
442            inflater.inflate(R.menu.pickup, menu);
443            int typeBits = mData.getInt(Gallery.KEY_TYPE_BITS,
444                    DataManager.INCLUDE_IMAGE);
445
446            actionBar.setTitle(GalleryUtils.getSelectionModePrompt(typeBits));
447        } else {
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);
452            } else {
453                menu.findItem(R.id.action_slideshow).setVisible(true);
454            }
455
456            MenuItem groupBy = menu.findItem(R.id.action_group_by);
457            FilterUtils.setupMenuItems(actionBar, mMediaSetPath, true);
458
459            if (groupBy != null) {
460                groupBy.setVisible(mShowClusterMenu);
461            }
462
463            actionBar.setTitle(mMediaSet.getName());
464        }
465        actionBar.setSubtitle(null);
466
467        return true;
468    }
469
470    @Override
471    protected boolean onItemSelected(MenuItem item) {
472        switch (item.getItemId()) {
473            case android.R.id.home: {
474                if (mActivity.getStateManager().getStateCount() > 1) {
475                    onBackPressed();
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)
481                            .setFlags(flags);
482                    a.startActivity(intent);
483                }
484                return true;
485            }
486            case R.id.action_cancel:
487                mActivity.getStateManager().finishState(this);
488                return true;
489            case R.id.action_select:
490                mSelectionManager.setAutoLeaveSelectionMode(false);
491                mSelectionManager.enterSelectionMode();
492                return true;
493            case R.id.action_group_by: {
494                mActivity.getGalleryActionBar().showClusterDialog(this);
495                return true;
496            }
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);
504                return true;
505            }
506            case R.id.action_details: {
507                if (mShowDetails) {
508                    hideDetails();
509                } else {
510                    showDetails();
511                }
512                return true;
513            }
514            default:
515                return false;
516        }
517    }
518
519    @Override
520    protected void onStateResult(int request, int result, Intent data) {
521        switch (request) {
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);
527                break;
528            }
529            case REQUEST_PHOTO: {
530                if (data == null) return;
531                mFocusIndex = data.getIntExtra(PhotoPage.KEY_INDEX_HINT, 0);
532                mAlbumView.setCenterIndex(mFocusIndex);
533                startTransition();
534                break;
535            }
536            case REQUEST_DO_ANIMATION: {
537                startTransition(null);
538                break;
539            }
540        }
541    }
542
543    public void onSelectionModeChange(int mode) {
544        switch (mode) {
545            case SelectionManager.ENTER_SELECTION_MODE: {
546                mActionMode = mActionModeHandler.startActionMode();
547                mVibrator.vibrate(100);
548                break;
549            }
550            case SelectionManager.LEAVE_SELECTION_MODE: {
551                mActionMode.finish();
552                mRootPane.invalidate();
553                break;
554            }
555            case SelectionManager.SELECT_ALL_MODE: {
556                mActionModeHandler.updateSupportedOperation();
557                mRootPane.invalidate();
558                break;
559            }
560        }
561    }
562
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);
570    }
571
572    @Override
573    public void onSyncDone(final MediaSet mediaSet, final int resultCode) {
574        Log.d(TAG, "onSyncDone: " + Utils.maskDebugInfo(mediaSet.getName()) + " result="
575                + resultCode);
576        ((Activity) mActivity).runOnUiThread(new Runnable() {
577            @Override
578            public void run() {
579                if (resultCode == MediaSet.SYNC_RESULT_SUCCESS) {
580                    mInitialSynced = true;
581                }
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();
586                }
587            }
588        });
589    }
590
591    private void setLoadingBit(int loadTaskBit) {
592        if (mLoadingBits == 0 && mIsActive) {
593            GalleryUtils.setSpinnerVisibility((Activity) mActivity, true);
594        }
595        mLoadingBits |= loadTaskBit;
596    }
597
598    private void clearLoadingBit(int loadTaskBit) {
599        mLoadingBits &= ~loadTaskBit;
600        if (mLoadingBits == 0 && mIsActive) {
601            GalleryUtils.setSpinnerVisibility((Activity) mActivity, false);
602
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);
607            }
608        }
609    }
610
611    private class MyLoadingListener implements LoadingListener {
612        @Override
613        public void onLoadingStarted() {
614            setLoadingBit(BIT_LOADING_RELOAD);
615        }
616
617        @Override
618        public void onLoadingFinished() {
619            clearLoadingBit(BIT_LOADING_RELOAD);
620        }
621    }
622
623    private class MyDetailsSource implements DetailsHelper.DetailsSource {
624        private int mIndex;
625
626        public int size() {
627            return mAlbumDataAdapter.size();
628        }
629
630        public int getIndex() {
631            return mIndex;
632        }
633
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)) {
638                mIndex = indexHint;
639            } else {
640                mIndex = mAlbumDataAdapter.getActiveStart();
641                if (!mAlbumDataAdapter.isActive(mIndex)) {
642                    return -1;
643                }
644            }
645            return mIndex;
646        }
647
648        public MediaDetails getDetails() {
649            MediaObject item = mAlbumDataAdapter.get(mIndex);
650            if (item != null) {
651                mHighlightDrawer.setHighlightItem(item.getPath());
652                return item.getDetails();
653            } else {
654                return null;
655            }
656        }
657    }
658}
659