AlbumSetPage.java revision 2bb717f1ea38e2ce33dd102a23afe6bfacb5675c
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.graphics.Rect;
23import android.os.Bundle;
24import android.os.Handler;
25import android.os.Message;
26import android.os.Vibrator;
27import android.view.Menu;
28import android.view.MenuItem;
29import android.widget.Toast;
30
31import com.android.gallery3d.R;
32import com.android.gallery3d.common.Utils;
33import com.android.gallery3d.data.DataManager;
34import com.android.gallery3d.data.MediaDetails;
35import com.android.gallery3d.data.MediaItem;
36import com.android.gallery3d.data.MediaObject;
37import com.android.gallery3d.data.MediaSet;
38import com.android.gallery3d.data.Path;
39import com.android.gallery3d.picasasource.PicasaSource;
40import com.android.gallery3d.settings.GallerySettings;
41import com.android.gallery3d.ui.ActionModeHandler;
42import com.android.gallery3d.ui.ActionModeHandler.ActionModeListener;
43import com.android.gallery3d.ui.AlbumSetSlotRenderer;
44import com.android.gallery3d.ui.DetailsHelper;
45import com.android.gallery3d.ui.DetailsHelper.CloseListener;
46import com.android.gallery3d.ui.FadeTexture;
47import com.android.gallery3d.ui.GLCanvas;
48import com.android.gallery3d.ui.GLRoot;
49import com.android.gallery3d.ui.GLView;
50import com.android.gallery3d.ui.SelectionManager;
51import com.android.gallery3d.ui.SlotView;
52import com.android.gallery3d.ui.SynchronizedHandler;
53import com.android.gallery3d.util.Future;
54import com.android.gallery3d.util.GalleryUtils;
55import com.android.gallery3d.util.HelpUtils;
56
57public class AlbumSetPage extends ActivityState implements
58        SelectionManager.SelectionListener, GalleryActionBar.ClusterRunner,
59        EyePosition.EyePositionListener, MediaSet.SyncListener {
60    @SuppressWarnings("unused")
61    private static final String TAG = "AlbumSetPage";
62
63    private static final int MSG_PICK_ALBUM = 1;
64
65    public static final String KEY_MEDIA_PATH = "media-path";
66    public static final String KEY_SET_TITLE = "set-title";
67    public static final String KEY_SET_SUBTITLE = "set-subtitle";
68    public static final String KEY_SELECTED_CLUSTER_TYPE = "selected-cluster";
69
70    private static final int DATA_CACHE_SIZE = 256;
71    private static final int REQUEST_DO_ANIMATION = 1;
72
73    private static final int BIT_LOADING_RELOAD = 1;
74    private static final int BIT_LOADING_SYNC = 2;
75
76    private boolean mIsActive = false;
77    private SlotView mSlotView;
78    private AlbumSetSlotRenderer mAlbumSetView;
79    private Config.AlbumSetPage mConfig;
80
81    private MediaSet mMediaSet;
82    private String mTitle;
83    private String mSubtitle;
84    private boolean mShowClusterMenu;
85    private GalleryActionBar mActionBar;
86    private int mSelectedAction;
87    private Vibrator mVibrator;
88
89    protected SelectionManager mSelectionManager;
90    private AlbumSetDataLoader mAlbumSetDataAdapter;
91
92    private boolean mGetContent;
93    private boolean mGetAlbum;
94    private ActionModeHandler mActionModeHandler;
95    private DetailsHelper mDetailsHelper;
96    private MyDetailsSource mDetailsSource;
97    private boolean mShowDetails;
98    private EyePosition mEyePosition;
99    private Handler mHandler;
100
101    // The eyes' position of the user, the origin is at the center of the
102    // device and the unit is in pixels.
103    private float mX;
104    private float mY;
105    private float mZ;
106
107    private Future<Integer> mSyncTask = null;
108
109    private int mLoadingBits = 0;
110    private boolean mInitialSynced = false;
111
112    private final GLView mRootPane = new GLView() {
113        private final float mMatrix[] = new float[16];
114
115        @Override
116        protected void renderBackground(GLCanvas view) {
117            view.clearBuffer();
118        }
119
120        @Override
121        protected void onLayout(
122                boolean changed, int left, int top, int right, int bottom) {
123            mEyePosition.resetPosition();
124
125            int slotViewTop = mActionBar.getHeight() + mConfig.paddingTop;
126            int slotViewBottom = bottom - top - mConfig.paddingBottom;
127            int slotViewRight = right - left;
128
129            if (mShowDetails) {
130                mDetailsHelper.layout(left, slotViewTop, right, bottom);
131            } else {
132                mAlbumSetView.setHighlightItemPath(null);
133            }
134
135            mSlotView.layout(0, slotViewTop, slotViewRight, slotViewBottom);
136        }
137
138        @Override
139        protected void render(GLCanvas canvas) {
140            canvas.save(GLCanvas.SAVE_FLAG_MATRIX);
141            GalleryUtils.setViewPointMatrix(mMatrix,
142                    getWidth() / 2 + mX, getHeight() / 2 + mY, mZ);
143            canvas.multiplyMatrix(mMatrix, 0);
144            super.render(canvas);
145            canvas.restore();
146        }
147    };
148
149    @Override
150    public void onEyePositionChanged(float x, float y, float z) {
151        mRootPane.lockRendering();
152        mX = x;
153        mY = y;
154        mZ = z;
155        mRootPane.unlockRendering();
156        mRootPane.invalidate();
157    }
158
159    @Override
160    public void onBackPressed() {
161        if (mShowDetails) {
162            hideDetails();
163        } else if (mSelectionManager.inSelectionMode()) {
164            mSelectionManager.leaveSelectionMode();
165        } else {
166            super.onBackPressed();
167        }
168    }
169
170    private void getSlotCenter(int slotIndex, int center[]) {
171        Rect offset = new Rect();
172        mRootPane.getBoundsOf(mSlotView, offset);
173        Rect r = mSlotView.getSlotRect(slotIndex);
174        int scrollX = mSlotView.getScrollX();
175        int scrollY = mSlotView.getScrollY();
176        center[0] = offset.left + (r.left + r.right) / 2 - scrollX;
177        center[1] = offset.top + (r.top + r.bottom) / 2 - scrollY;
178    }
179
180    public void onSingleTapUp(int slotIndex) {
181        if (!mIsActive) return;
182
183        if (mSelectionManager.inSelectionMode()) {
184            MediaSet targetSet = mAlbumSetDataAdapter.getMediaSet(slotIndex);
185            if (targetSet == null) return; // Content is dirty, we shall reload soon
186            mSelectionManager.toggle(targetSet.getPath());
187            mSlotView.invalidate();
188        } else {
189            // Show pressed-up animation for the single-tap.
190            mAlbumSetView.setPressedIndex(slotIndex);
191            mAlbumSetView.setPressedUp();
192            mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_PICK_ALBUM, slotIndex, 0),
193                    FadeTexture.DURATION);
194        }
195    }
196
197    private void pickAlbum(int slotIndex) {
198        if (!mIsActive) return;
199
200        MediaSet targetSet = mAlbumSetDataAdapter.getMediaSet(slotIndex);
201        if (targetSet == null) return; // Content is dirty, we shall reload soon
202        String mediaPath = targetSet.getPath().toString();
203
204        Bundle data = new Bundle(getData());
205        int[] center = new int[2];
206        getSlotCenter(slotIndex, center);
207        data.putIntArray(AlbumPage.KEY_SET_CENTER, center);
208        if (mGetAlbum && targetSet.isLeafAlbum()) {
209            Activity activity = (Activity) mActivity;
210            Intent result = new Intent()
211                    .putExtra(AlbumPicker.KEY_ALBUM_PATH, targetSet.getPath().toString());
212            activity.setResult(Activity.RESULT_OK, result);
213            activity.finish();
214        } else if (targetSet.getSubMediaSetCount() > 0) {
215            data.putString(AlbumSetPage.KEY_MEDIA_PATH, mediaPath);
216            mActivity.getStateManager().startStateForResult(
217                    AlbumSetPage.class, REQUEST_DO_ANIMATION, data);
218        } else {
219            if (!mGetContent && (targetSet.getSupportedOperations()
220                    & MediaObject.SUPPORT_IMPORT) != 0) {
221                data.putBoolean(AlbumPage.KEY_AUTO_SELECT_ALL, true);
222            }
223            data.putString(AlbumPage.KEY_MEDIA_PATH, mediaPath);
224            boolean inAlbum = mActivity.getStateManager().hasStateClass(AlbumPage.class);
225            // We only show cluster menu in the first AlbumPage in stack
226            data.putBoolean(AlbumPage.KEY_SHOW_CLUSTER_MENU, !inAlbum);
227            mActivity.getStateManager().startStateForResult(
228                    AlbumPage.class, REQUEST_DO_ANIMATION, data);
229        }
230    }
231
232    private void onDown(int index) {
233        mAlbumSetView.setPressedIndex(index);
234    }
235
236    private void onUp(boolean followedByLongPress) {
237        if (followedByLongPress) {
238            // Avoid showing press-up animations for long-press.
239            mAlbumSetView.setPressedIndex(-1);
240        } else {
241            mAlbumSetView.setPressedUp();
242        }
243    }
244
245    public void onLongTap(int slotIndex) {
246        if (mGetContent || mGetAlbum) return;
247        MediaSet set = mAlbumSetDataAdapter.getMediaSet(slotIndex);
248        if (set == null) return;
249        mSelectionManager.setAutoLeaveSelectionMode(true);
250        mSelectionManager.toggle(set.getPath());
251        mSlotView.invalidate();
252    }
253
254    @Override
255    public void doCluster(int clusterType) {
256        String basePath = mMediaSet.getPath().toString();
257        String newPath = FilterUtils.switchClusterPath(basePath, clusterType);
258        Bundle data = new Bundle(getData());
259        data.putString(AlbumSetPage.KEY_MEDIA_PATH, newPath);
260        data.putInt(KEY_SELECTED_CLUSTER_TYPE, clusterType);
261        mActivity.getStateManager().switchState(this, AlbumSetPage.class, data);
262    }
263
264    @Override
265    public void onCreate(Bundle data, Bundle restoreState) {
266        initializeViews();
267        initializeData(data);
268        Context context = mActivity.getAndroidContext();
269        mGetContent = data.getBoolean(Gallery.KEY_GET_CONTENT, false);
270        mGetAlbum = data.getBoolean(Gallery.KEY_GET_ALBUM, false);
271        mTitle = data.getString(AlbumSetPage.KEY_SET_TITLE);
272        mSubtitle = data.getString(AlbumSetPage.KEY_SET_SUBTITLE);
273        mEyePosition = new EyePosition(context, this);
274        mDetailsSource = new MyDetailsSource();
275        mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
276        mActionBar = mActivity.getGalleryActionBar();
277        mSelectedAction = data.getInt(AlbumSetPage.KEY_SELECTED_CLUSTER_TYPE,
278                FilterUtils.CLUSTER_BY_ALBUM);
279
280        mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
281            @Override
282            public void handleMessage(Message message) {
283                switch (message.what) {
284                    case MSG_PICK_ALBUM: {
285                        pickAlbum(message.arg1);
286                        break;
287                    }
288                    default: throw new AssertionError(message.what);
289                }
290            }
291        };
292    }
293
294    private void clearLoadingBit(int loadingBit) {
295        mLoadingBits &= ~loadingBit;
296        if (mLoadingBits == 0 && mIsActive) {
297            // Only show toast when there's no album and we are going to finish
298            // the page. Toast is redundant if we are going to stay on this page.
299            if ((mAlbumSetDataAdapter.size() == 0)) {
300                if (mActivity.getStateManager().getStateCount() > 1) {
301                    Toast.makeText((Context) mActivity,
302                            R.string.empty_album, Toast.LENGTH_LONG).show();
303                    mActivity.getStateManager().finishState(this);
304                }
305            }
306        }
307    }
308
309    private void setLoadingBit(int loadingBit) {
310        mLoadingBits |= loadingBit;
311    }
312
313    @Override
314    public void onPause() {
315        super.onPause();
316        mIsActive = false;
317        mActionModeHandler.pause();
318        mAlbumSetDataAdapter.pause();
319        mAlbumSetView.pause();
320        mEyePosition.pause();
321        DetailsHelper.pause();
322        // Call disableClusterMenu to avoid receiving callback after paused.
323        // Don't hide menu here otherwise the list menu will disappear earlier than
324        // the action bar, which is janky and unwanted behavior.
325        mActionBar.disableClusterMenu(false);
326        if (mSyncTask != null) {
327            mSyncTask.cancel();
328            mSyncTask = null;
329            clearLoadingBit(BIT_LOADING_SYNC);
330        }
331    }
332
333    @Override
334    public void onResume() {
335        super.onResume();
336        mIsActive = true;
337        setContentPane(mRootPane);
338
339        // Set the reload bit here to prevent it exit this page in clearLoadingBit().
340        setLoadingBit(BIT_LOADING_RELOAD);
341        mAlbumSetDataAdapter.resume();
342
343        mAlbumSetView.resume();
344        mEyePosition.resume();
345        mActionModeHandler.resume();
346        if (mShowClusterMenu) {
347            mActionBar.enableClusterMenu(mSelectedAction, this);
348        }
349        if (!mInitialSynced) {
350            setLoadingBit(BIT_LOADING_SYNC);
351            mSyncTask = mMediaSet.requestSync(AlbumSetPage.this);
352        }
353    }
354
355    private void initializeData(Bundle data) {
356        String mediaPath = data.getString(AlbumSetPage.KEY_MEDIA_PATH);
357        mMediaSet = mActivity.getDataManager().getMediaSet(mediaPath);
358        mSelectionManager.setSourceMediaSet(mMediaSet);
359        mAlbumSetDataAdapter = new AlbumSetDataLoader(
360                mActivity, mMediaSet, DATA_CACHE_SIZE);
361        mAlbumSetDataAdapter.setLoadingListener(new MyLoadingListener());
362        mAlbumSetView.setModel(mAlbumSetDataAdapter);
363    }
364
365    private void initializeViews() {
366        mSelectionManager = new SelectionManager(mActivity, true);
367        mSelectionManager.setSelectionListener(this);
368
369        mConfig = Config.AlbumSetPage.get((Context) mActivity);
370        mSlotView = new SlotView(mActivity, mConfig.slotViewSpec);
371        mAlbumSetView = new AlbumSetSlotRenderer(
372                mActivity, mSelectionManager, mSlotView, mConfig.labelSpec);
373        mSlotView.setSlotRenderer(mAlbumSetView);
374        mSlotView.setListener(new SlotView.SimpleListener() {
375            @Override
376            public void onDown(int index) {
377                AlbumSetPage.this.onDown(index);
378            }
379
380            @Override
381            public void onUp(boolean followedByLongPress) {
382                AlbumSetPage.this.onUp(followedByLongPress);
383            }
384
385            @Override
386            public void onSingleTapUp(int slotIndex) {
387                AlbumSetPage.this.onSingleTapUp(slotIndex);
388            }
389
390            @Override
391            public void onLongTap(int slotIndex) {
392                AlbumSetPage.this.onLongTap(slotIndex);
393            }
394        });
395
396        mActionModeHandler = new ActionModeHandler(mActivity, mSelectionManager);
397        mActionModeHandler.setActionModeListener(new ActionModeListener() {
398            @Override
399            public boolean onActionItemClicked(MenuItem item) {
400                return onItemSelected(item);
401            }
402        });
403        mRootPane.addComponent(mSlotView);
404    }
405
406    @Override
407    protected boolean onCreateActionBar(Menu menu) {
408        Activity activity = (Activity) mActivity;
409        final boolean inAlbum = mActivity.getStateManager().hasStateClass(AlbumPage.class);
410        boolean result;
411        if (mGetContent) {
412            result = mActionBar.createActionMenu(menu, R.menu.pickup);
413            int typeBits = mData.getInt(
414                    Gallery.KEY_TYPE_BITS, DataManager.INCLUDE_IMAGE);
415            int id = R.string.select_image;
416            if ((typeBits & DataManager.INCLUDE_VIDEO) != 0) {
417                id = (typeBits & DataManager.INCLUDE_IMAGE) == 0
418                        ? R.string.select_video
419                        : R.string.select_item;
420            }
421            mActionBar.setTitle(id);
422        } else  if (mGetAlbum) {
423            result = mActionBar.createActionMenu(menu, R.menu.pickup);
424            mActionBar.setTitle(R.string.select_album);
425        } else {
426            result = mActionBar.createActionMenu(menu, R.menu.albumset);
427            mShowClusterMenu = !inAlbum;
428            boolean selectAlbums = !inAlbum &&
429                    mActionBar.getClusterTypeAction() == FilterUtils.CLUSTER_BY_ALBUM;
430            mActionBar.setMenuItemTitle(R.id.action_select, activity.getString(
431                    selectAlbums ? R.string.select_album : R.string.select_group));
432
433            FilterUtils.setupMenuItems(mActionBar, mMediaSet.getPath(), false);
434
435            mActionBar.setMenuItemVisible(
436                    R.id.action_camera, GalleryUtils.isCameraAvailable(activity));
437
438            Intent helpIntent = HelpUtils.getHelpIntent(activity, R.string.help_url_gallery_main);
439            if (helpIntent == null) {
440                mActionBar.setMenuItemVisible(R.id.action_general_help, false);
441            } else {
442                mActionBar.setMenuItemVisible(R.id.action_general_help, true);
443                mActionBar.setMenuItemIntent(R.id.action_general_help, helpIntent);
444            }
445            mActionBar.setTitle(mTitle);
446            mActionBar.setSubtitle(mSubtitle);
447        }
448        return result;
449    }
450
451    @Override
452    protected boolean onItemSelected(MenuItem item) {
453        Activity activity = (Activity) mActivity;
454        switch (item.getItemId()) {
455            case R.id.action_cancel:
456                activity.setResult(Activity.RESULT_CANCELED);
457                activity.finish();
458                return true;
459            case R.id.action_select:
460                mSelectionManager.setAutoLeaveSelectionMode(false);
461                mSelectionManager.enterSelectionMode();
462                return true;
463            case R.id.action_details:
464                if (mAlbumSetDataAdapter.size() != 0) {
465                    if (mShowDetails) {
466                        hideDetails();
467                    } else {
468                        showDetails();
469                    }
470                } else {
471                    Toast.makeText(activity,
472                            activity.getText(R.string.no_albums_alert),
473                            Toast.LENGTH_SHORT).show();
474                }
475                return true;
476            case R.id.action_camera: {
477                GalleryUtils.startCameraActivity(activity);
478                return true;
479            }
480            case R.id.action_manage_offline: {
481                Bundle data = new Bundle();
482                String mediaPath = mActivity.getDataManager().getTopSetPath(
483                    DataManager.INCLUDE_ALL);
484                data.putString(AlbumSetPage.KEY_MEDIA_PATH, mediaPath);
485                mActivity.getStateManager().startState(ManageCachePage.class, data);
486                return true;
487            }
488            case R.id.action_sync_picasa_albums: {
489                PicasaSource.requestSync(activity);
490                return true;
491            }
492            case R.id.action_settings: {
493                activity.startActivity(new Intent(activity, GallerySettings.class));
494                return true;
495            }
496            default:
497                return false;
498        }
499    }
500
501    @Override
502    protected void onStateResult(int requestCode, int resultCode, Intent data) {
503        switch (requestCode) {
504            case REQUEST_DO_ANIMATION: {
505                mSlotView.startRisingAnimation();
506            }
507        }
508    }
509
510    private String getSelectedString() {
511        int count = mSelectionManager.getSelectedCount();
512        int action = mActionBar.getClusterTypeAction();
513        int string = action == FilterUtils.CLUSTER_BY_ALBUM
514                ? R.plurals.number_of_albums_selected
515                : R.plurals.number_of_groups_selected;
516        String format = mActivity.getResources().getQuantityString(string, count);
517        return String.format(format, count);
518    }
519
520    @Override
521    public void onSelectionModeChange(int mode) {
522        switch (mode) {
523            case SelectionManager.ENTER_SELECTION_MODE: {
524                mActionBar.disableClusterMenu(true);
525                mActionModeHandler.startActionMode();
526                if (mHapticsEnabled) mVibrator.vibrate(100);
527                break;
528            }
529            case SelectionManager.LEAVE_SELECTION_MODE: {
530                mActionModeHandler.finishActionMode();
531                if (mShowClusterMenu) {
532                    mActionBar.enableClusterMenu(mSelectedAction, this);
533                }
534                mRootPane.invalidate();
535                break;
536            }
537            case SelectionManager.SELECT_ALL_MODE: {
538                mActionModeHandler.updateSupportedOperation();
539                mRootPane.invalidate();
540                break;
541            }
542        }
543    }
544
545    @Override
546    public void onSelectionChange(Path path, boolean selected) {
547        mActionModeHandler.setTitle(getSelectedString());
548        mActionModeHandler.updateSupportedOperation(path, selected);
549    }
550
551    private void hideDetails() {
552        mShowDetails = false;
553        mDetailsHelper.hide();
554        mAlbumSetView.setHighlightItemPath(null);
555        mSlotView.invalidate();
556    }
557
558    private void showDetails() {
559        mShowDetails = true;
560        if (mDetailsHelper == null) {
561            mDetailsHelper = new DetailsHelper(mActivity, mRootPane, mDetailsSource);
562            mDetailsHelper.setCloseListener(new CloseListener() {
563                @Override
564                public void onClose() {
565                    hideDetails();
566                }
567            });
568        }
569        mDetailsHelper.show();
570    }
571
572    @Override
573    public void onSyncDone(final MediaSet mediaSet, final int resultCode) {
574        if (resultCode == MediaSet.SYNC_RESULT_ERROR) {
575            Log.d(TAG, "onSyncDone: " + Utils.maskDebugInfo(mediaSet.getName()) + " result="
576                    + resultCode);
577        }
578        ((Activity) mActivity).runOnUiThread(new Runnable() {
579            @Override
580            public void run() {
581                GLRoot root = mActivity.getGLRoot();
582                root.lockRenderThread();
583                try {
584                    if (resultCode == MediaSet.SYNC_RESULT_SUCCESS) {
585                        mInitialSynced = true;
586                    }
587                    clearLoadingBit(BIT_LOADING_SYNC);
588                    if (resultCode == MediaSet.SYNC_RESULT_ERROR && mIsActive) {
589                        Log.w(TAG, "failed to load album set");
590                    }
591                } finally {
592                    root.unlockRenderThread();
593                }
594            }
595        });
596    }
597
598    private class MyLoadingListener implements LoadingListener {
599        @Override
600        public void onLoadingStarted() {
601            setLoadingBit(BIT_LOADING_RELOAD);
602        }
603
604        @Override
605        public void onLoadingFinished() {
606            clearLoadingBit(BIT_LOADING_RELOAD);
607        }
608    }
609
610    private class MyDetailsSource implements DetailsHelper.DetailsSource {
611        private int mIndex;
612
613        @Override
614        public int size() {
615            return mAlbumSetDataAdapter.size();
616        }
617
618        @Override
619        public int setIndex() {
620            Path id = mSelectionManager.getSelected(false).get(0);
621            mIndex = mAlbumSetDataAdapter.findSet(id);
622            return mIndex;
623        }
624
625        @Override
626        public MediaDetails getDetails() {
627            MediaObject item = mAlbumSetDataAdapter.getMediaSet(mIndex);
628            if (item != null) {
629                mAlbumSetView.setHighlightItemPath(item.getPath());
630                return item.getDetails();
631            } else {
632                return null;
633            }
634        }
635    }
636}
637