SlideshowPage.java revision 7817979db0c52ffeacb951625b1e821eba303285
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.Intent;
21import android.graphics.Bitmap;
22import android.os.Bundle;
23import android.os.Handler;
24import android.os.Message;
25import android.view.MotionEvent;
26
27import com.android.gallery3d.common.Utils;
28import com.android.gallery3d.data.ContentListener;
29import com.android.gallery3d.data.MediaItem;
30import com.android.gallery3d.data.MediaObject;
31import com.android.gallery3d.data.MediaSet;
32import com.android.gallery3d.data.Path;
33import com.android.gallery3d.ui.GLCanvas;
34import com.android.gallery3d.ui.GLView;
35import com.android.gallery3d.ui.SlideshowView;
36import com.android.gallery3d.ui.SynchronizedHandler;
37import com.android.gallery3d.util.Future;
38import com.android.gallery3d.util.FutureListener;
39
40import java.util.ArrayList;
41import java.util.Random;
42
43public class SlideshowPage extends ActivityState {
44    private static final String TAG = "SlideshowPage";
45
46    public static final String KEY_SET_PATH = "media-set-path";
47    public static final String KEY_ITEM_PATH = "media-item-path";
48    public static final String KEY_PHOTO_INDEX = "photo-index";
49    public static final String KEY_RANDOM_ORDER = "random-order";
50    public static final String KEY_REPEAT = "repeat";
51    public static final String KEY_DREAM = "dream";
52
53    private static final long SLIDESHOW_DELAY = 3000; // 3 seconds
54
55    private static final int MSG_LOAD_NEXT_BITMAP = 1;
56    private static final int MSG_SHOW_PENDING_BITMAP = 2;
57
58    public static interface Model {
59        public void pause();
60
61        public void resume();
62
63        public Future<Slide> nextSlide(FutureListener<Slide> listener);
64    }
65
66    public static class Slide {
67        public Bitmap bitmap;
68        public MediaItem item;
69        public int index;
70
71        public Slide(MediaItem item, int index, Bitmap bitmap) {
72            this.bitmap = bitmap;
73            this.item = item;
74            this.index = index;
75        }
76    }
77
78    private Handler mHandler;
79    private Model mModel;
80    private SlideshowView mSlideshowView;
81
82    private Slide mPendingSlide = null;
83    private boolean mIsActive = false;
84    private final Intent mResultIntent = new Intent();
85
86    private final GLView mRootPane = new GLView() {
87        @Override
88        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
89            mSlideshowView.layout(0, 0, right - left, bottom - top);
90        }
91
92        @Override
93        protected boolean onTouch(MotionEvent event) {
94            if (event.getAction() == MotionEvent.ACTION_UP) {
95                onBackPressed();
96            }
97            return true;
98        }
99
100        @Override
101        protected void renderBackground(GLCanvas canvas) {
102            canvas.clearBuffer();
103        }
104    };
105
106    @Override
107    public void onCreate(Bundle data, Bundle restoreState) {
108        mFlags |= (FLAG_HIDE_ACTION_BAR | FLAG_HIDE_STATUS_BAR);
109        if (data.getBoolean(KEY_DREAM)) {
110            // Dream screensaver only keeps screen on for plugged devices.
111            mFlags |= FLAG_SCREEN_ON_WHEN_PLUGGED;
112        } else {
113            // User-initiated slideshow would always keep screen on.
114            mFlags |= FLAG_SCREEN_ON_ALWAYS;
115        }
116
117        mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
118            @Override
119            public void handleMessage(Message message) {
120                switch (message.what) {
121                    case MSG_SHOW_PENDING_BITMAP:
122                        showPendingBitmap();
123                        break;
124                    case MSG_LOAD_NEXT_BITMAP:
125                        loadNextBitmap();
126                        break;
127                    default: throw new AssertionError();
128                }
129            }
130        };
131        initializeViews();
132        initializeData(data);
133    }
134
135    private void loadNextBitmap() {
136        mModel.nextSlide(new FutureListener<Slide>() {
137            @Override
138            public void onFutureDone(Future<Slide> future) {
139                mPendingSlide = future.get();
140                mHandler.sendEmptyMessage(MSG_SHOW_PENDING_BITMAP);
141            }
142        });
143    }
144
145    private void showPendingBitmap() {
146        // mPendingBitmap could be null, if
147        // 1.) there is no more items
148        // 2.) mModel is paused
149        Slide slide = mPendingSlide;
150        if (slide == null) {
151            if (mIsActive) {
152                mActivity.getStateManager().finishState(SlideshowPage.this);
153            }
154            return;
155        }
156
157        mSlideshowView.next(slide.bitmap, slide.item.getRotation());
158
159        setStateResult(Activity.RESULT_OK, mResultIntent
160                .putExtra(KEY_ITEM_PATH, slide.item.getPath().toString())
161                .putExtra(KEY_PHOTO_INDEX, slide.index));
162        mHandler.sendEmptyMessageDelayed(MSG_LOAD_NEXT_BITMAP, SLIDESHOW_DELAY);
163    }
164
165    @Override
166    public void onPause() {
167        super.onPause();
168        mIsActive = false;
169        mModel.pause();
170        mSlideshowView.release();
171
172        mHandler.removeMessages(MSG_LOAD_NEXT_BITMAP);
173        mHandler.removeMessages(MSG_SHOW_PENDING_BITMAP);
174    }
175
176    @Override
177    public void onResume() {
178        super.onResume();
179        mIsActive = true;
180        mModel.resume();
181
182        if (mPendingSlide != null) {
183            showPendingBitmap();
184        } else {
185            loadNextBitmap();
186        }
187    }
188
189    private void initializeData(Bundle data) {
190        boolean random = data.getBoolean(KEY_RANDOM_ORDER, false);
191
192        // We only want to show slideshow for images only, not videos.
193        String mediaPath = data.getString(KEY_SET_PATH);
194        mediaPath = FilterUtils.newFilterPath(mediaPath, FilterUtils.FILTER_IMAGE_ONLY);
195        MediaSet mediaSet = mActivity.getDataManager().getMediaSet(mediaPath);
196
197        if (random) {
198            boolean repeat = data.getBoolean(KEY_REPEAT);
199            mModel = new SlideshowDataAdapter(mActivity,
200                    new ShuffleSource(mediaSet, repeat), 0, null);
201            setStateResult(Activity.RESULT_OK, mResultIntent.putExtra(KEY_PHOTO_INDEX, 0));
202        } else {
203            int index = data.getInt(KEY_PHOTO_INDEX);
204            String itemPath = data.getString(KEY_ITEM_PATH);
205            Path path = itemPath != null ? Path.fromString(itemPath) : null;
206            boolean repeat = data.getBoolean(KEY_REPEAT);
207            mModel = new SlideshowDataAdapter(mActivity, new SequentialSource(mediaSet, repeat),
208                    index, path);
209            setStateResult(Activity.RESULT_OK, mResultIntent.putExtra(KEY_PHOTO_INDEX, index));
210        }
211    }
212
213    private void initializeViews() {
214        mSlideshowView = new SlideshowView();
215        mRootPane.addComponent(mSlideshowView);
216        setContentPane(mRootPane);
217    }
218
219    private static MediaItem findMediaItem(MediaSet mediaSet, int index) {
220        for (int i = 0, n = mediaSet.getSubMediaSetCount(); i < n; ++i) {
221            MediaSet subset = mediaSet.getSubMediaSet(i);
222            int count = subset.getTotalMediaItemCount();
223            if (index < count) {
224                return findMediaItem(subset, index);
225            }
226            index -= count;
227        }
228        ArrayList<MediaItem> list = mediaSet.getMediaItem(index, 1);
229        return list.isEmpty() ? null : list.get(0);
230    }
231
232    private static class ShuffleSource implements SlideshowDataAdapter.SlideshowSource {
233        private static final int RETRY_COUNT = 5;
234        private final MediaSet mMediaSet;
235        private final Random mRandom = new Random();
236        private int mOrder[] = new int[0];
237        private final boolean mRepeat;
238        private long mSourceVersion = MediaSet.INVALID_DATA_VERSION;
239        private int mLastIndex = -1;
240
241        public ShuffleSource(MediaSet mediaSet, boolean repeat) {
242            mMediaSet = Utils.checkNotNull(mediaSet);
243            mRepeat = repeat;
244        }
245
246        @Override
247        public int findItemIndex(Path path, int hint) {
248            return hint;
249        }
250
251        @Override
252        public MediaItem getMediaItem(int index) {
253            if (!mRepeat && index >= mOrder.length) return null;
254            if (mOrder.length == 0) return null;
255            mLastIndex = mOrder[index % mOrder.length];
256            MediaItem item = findMediaItem(mMediaSet, mLastIndex);
257            for (int i = 0; i < RETRY_COUNT && item == null; ++i) {
258                Log.w(TAG, "fail to find image: " + mLastIndex);
259                mLastIndex = mRandom.nextInt(mOrder.length);
260                item = findMediaItem(mMediaSet, mLastIndex);
261            }
262            return item;
263        }
264
265        @Override
266        public long reload() {
267            long version = mMediaSet.reload();
268            if (version != mSourceVersion) {
269                mSourceVersion = version;
270                int count = mMediaSet.getTotalMediaItemCount();
271                if (count != mOrder.length) generateOrderArray(count);
272            }
273            return version;
274        }
275
276        private void generateOrderArray(int totalCount) {
277            if (mOrder.length != totalCount) {
278                mOrder = new int[totalCount];
279                for (int i = 0; i < totalCount; ++i) {
280                    mOrder[i] = i;
281                }
282            }
283            for (int i = totalCount - 1; i > 0; --i) {
284                Utils.swap(mOrder, i, mRandom.nextInt(i + 1));
285            }
286            if (mOrder[0] == mLastIndex && totalCount > 1) {
287                Utils.swap(mOrder, 0, mRandom.nextInt(totalCount - 1) + 1);
288            }
289        }
290
291        @Override
292        public void addContentListener(ContentListener listener) {
293            mMediaSet.addContentListener(listener);
294        }
295
296        @Override
297        public void removeContentListener(ContentListener listener) {
298            mMediaSet.removeContentListener(listener);
299        }
300    }
301
302    private static class SequentialSource implements SlideshowDataAdapter.SlideshowSource {
303        private static final int DATA_SIZE = 32;
304
305        private ArrayList<MediaItem> mData = new ArrayList<MediaItem>();
306        private int mDataStart = 0;
307        private long mDataVersion = MediaObject.INVALID_DATA_VERSION;
308        private final MediaSet mMediaSet;
309        private final boolean mRepeat;
310
311        public SequentialSource(MediaSet mediaSet, boolean repeat) {
312            mMediaSet = mediaSet;
313            mRepeat = repeat;
314        }
315
316        @Override
317        public int findItemIndex(Path path, int hint) {
318            return mMediaSet.getIndexOfItem(path, hint);
319        }
320
321        @Override
322        public MediaItem getMediaItem(int index) {
323            int dataEnd = mDataStart + mData.size();
324
325            if (mRepeat) {
326                int count = mMediaSet.getMediaItemCount();
327                if (count == 0) return null;
328                index = index % count;
329            }
330            if (index < mDataStart || index >= dataEnd) {
331                mData = mMediaSet.getMediaItem(index, DATA_SIZE);
332                mDataStart = index;
333                dataEnd = index + mData.size();
334            }
335
336            return (index < mDataStart || index >= dataEnd) ? null : mData.get(index - mDataStart);
337        }
338
339        @Override
340        public long reload() {
341            long version = mMediaSet.reload();
342            if (version != mDataVersion) {
343                mDataVersion = version;
344                mData.clear();
345            }
346            return mDataVersion;
347        }
348
349        @Override
350        public void addContentListener(ContentListener listener) {
351            mMediaSet.addContentListener(listener);
352        }
353
354        @Override
355        public void removeContentListener(ContentListener listener) {
356            mMediaSet.removeContentListener(listener);
357        }
358    }
359}
360