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