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.Bitmap;
23import android.os.Bundle;
24import android.os.Handler;
25import android.os.Message;
26import android.view.MotionEvent;
27
28import com.android.gallery3d.common.Utils;
29import com.android.gallery3d.data.ContentListener;
30import com.android.gallery3d.data.MediaItem;
31import com.android.gallery3d.data.MediaObject;
32import com.android.gallery3d.data.MediaSet;
33import com.android.gallery3d.data.Path;
34import com.android.gallery3d.ui.GLCanvas;
35import com.android.gallery3d.ui.GLView;
36import com.android.gallery3d.ui.SlideshowView;
37import com.android.gallery3d.ui.SynchronizedHandler;
38import com.android.gallery3d.util.Future;
39import com.android.gallery3d.util.FutureListener;
40
41import java.util.ArrayList;
42import java.util.Random;
43
44public class SlideshowPage extends ActivityState {
45    private static final String TAG = "SlideshowPage";
46
47    public static final String KEY_SET_PATH = "media-set-path";
48    public static final String KEY_ITEM_PATH = "media-item-path";
49    public static final String KEY_PHOTO_INDEX = "photo-index";
50    public static final String KEY_RANDOM_ORDER = "random-order";
51    public static final String KEY_REPEAT = "repeat";
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 | FLAG_SCREEN_ON);
109
110        mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
111            @Override
112            public void handleMessage(Message message) {
113                switch (message.what) {
114                    case MSG_SHOW_PENDING_BITMAP:
115                        showPendingBitmap();
116                        break;
117                    case MSG_LOAD_NEXT_BITMAP:
118                        loadNextBitmap();
119                        break;
120                    default: throw new AssertionError();
121                }
122            }
123        };
124        initializeViews();
125        initializeData(data);
126    }
127
128    private void loadNextBitmap() {
129        mModel.nextSlide(new FutureListener<Slide>() {
130            public void onFutureDone(Future<Slide> future) {
131                mPendingSlide = future.get();
132                mHandler.sendEmptyMessage(MSG_SHOW_PENDING_BITMAP);
133            }
134        });
135    }
136
137    private void showPendingBitmap() {
138        // mPendingBitmap could be null, if
139        // 1.) there is no more items
140        // 2.) mModel is paused
141        Slide slide = mPendingSlide;
142        if (slide == null) {
143            if (mIsActive) {
144                mActivity.getStateManager().finishState(SlideshowPage.this);
145            }
146            return;
147        }
148
149        mSlideshowView.next(slide.bitmap, slide.item.getRotation());
150
151        setStateResult(Activity.RESULT_OK, mResultIntent
152                .putExtra(KEY_ITEM_PATH, slide.item.getPath().toString())
153                .putExtra(KEY_PHOTO_INDEX, slide.index));
154        mHandler.sendEmptyMessageDelayed(MSG_LOAD_NEXT_BITMAP, SLIDESHOW_DELAY);
155    }
156
157    @Override
158    public void onPause() {
159        super.onPause();
160        mIsActive = false;
161        mModel.pause();
162        mSlideshowView.release();
163
164        mHandler.removeMessages(MSG_LOAD_NEXT_BITMAP);
165        mHandler.removeMessages(MSG_SHOW_PENDING_BITMAP);
166    }
167
168    @Override
169    public void onResume() {
170        super.onResume();
171        mIsActive = true;
172        mModel.resume();
173
174        if (mPendingSlide != null) {
175            showPendingBitmap();
176        } else {
177            loadNextBitmap();
178        }
179    }
180
181    private void initializeData(Bundle data) {
182        boolean random = data.getBoolean(KEY_RANDOM_ORDER, false);
183
184        // We only want to show slideshow for images only, not videos.
185        String mediaPath = data.getString(KEY_SET_PATH);
186        mediaPath = FilterUtils.newFilterPath(mediaPath, FilterUtils.FILTER_IMAGE_ONLY);
187        MediaSet mediaSet = mActivity.getDataManager().getMediaSet(mediaPath);
188
189        if (random) {
190            boolean repeat = data.getBoolean(KEY_REPEAT);
191            mModel = new SlideshowDataAdapter(mActivity,
192                    new ShuffleSource(mediaSet, repeat), 0, null);
193            setStateResult(Activity.RESULT_OK, mResultIntent.putExtra(KEY_PHOTO_INDEX, 0));
194        } else {
195            int index = data.getInt(KEY_PHOTO_INDEX);
196            String itemPath = data.getString(KEY_ITEM_PATH);
197            Path path = itemPath != null ? Path.fromString(itemPath) : null;
198            boolean repeat = data.getBoolean(KEY_REPEAT);
199            mModel = new SlideshowDataAdapter(mActivity, new SequentialSource(mediaSet, repeat),
200                    index, path);
201            setStateResult(Activity.RESULT_OK, mResultIntent.putExtra(KEY_PHOTO_INDEX, index));
202        }
203    }
204
205    private void initializeViews() {
206        mSlideshowView = new SlideshowView();
207        mRootPane.addComponent(mSlideshowView);
208        setContentPane(mRootPane);
209    }
210
211    private static MediaItem findMediaItem(MediaSet mediaSet, int index) {
212        for (int i = 0, n = mediaSet.getSubMediaSetCount(); i < n; ++i) {
213            MediaSet subset = mediaSet.getSubMediaSet(i);
214            int count = subset.getTotalMediaItemCount();
215            if (index < count) {
216                return findMediaItem(subset, index);
217            }
218            index -= count;
219        }
220        ArrayList<MediaItem> list = mediaSet.getMediaItem(index, 1);
221        return list.isEmpty() ? null : list.get(0);
222    }
223
224    private static class ShuffleSource implements SlideshowDataAdapter.SlideshowSource {
225        private static final int RETRY_COUNT = 5;
226        private final MediaSet mMediaSet;
227        private final Random mRandom = new Random();
228        private int mOrder[] = new int[0];
229        private final boolean mRepeat;
230        private long mSourceVersion = MediaSet.INVALID_DATA_VERSION;
231        private int mLastIndex = -1;
232
233        public ShuffleSource(MediaSet mediaSet, boolean repeat) {
234            mMediaSet = Utils.checkNotNull(mediaSet);
235            mRepeat = repeat;
236        }
237
238        public int findItemIndex(Path path, int hint) {
239            return hint;
240        }
241
242        public MediaItem getMediaItem(int index) {
243            if (!mRepeat && index >= mOrder.length) return null;
244            if (mOrder.length == 0) return null;
245            mLastIndex = mOrder[index % mOrder.length];
246            MediaItem item = findMediaItem(mMediaSet, mLastIndex);
247            for (int i = 0; i < RETRY_COUNT && item == null; ++i) {
248                Log.w(TAG, "fail to find image: " + mLastIndex);
249                mLastIndex = mRandom.nextInt(mOrder.length);
250                item = findMediaItem(mMediaSet, mLastIndex);
251            }
252            return item;
253        }
254
255        public long reload() {
256            long version = mMediaSet.reload();
257            if (version != mSourceVersion) {
258                mSourceVersion = version;
259                int count = mMediaSet.getTotalMediaItemCount();
260                if (count != mOrder.length) generateOrderArray(count);
261            }
262            return version;
263        }
264
265        private void generateOrderArray(int totalCount) {
266            if (mOrder.length != totalCount) {
267                mOrder = new int[totalCount];
268                for (int i = 0; i < totalCount; ++i) {
269                    mOrder[i] = i;
270                }
271            }
272            for (int i = totalCount - 1; i > 0; --i) {
273                Utils.swap(mOrder, i, mRandom.nextInt(i + 1));
274            }
275            if (mOrder[0] == mLastIndex && totalCount > 1) {
276                Utils.swap(mOrder, 0, mRandom.nextInt(totalCount - 1) + 1);
277            }
278        }
279
280        public void addContentListener(ContentListener listener) {
281            mMediaSet.addContentListener(listener);
282        }
283
284        public void removeContentListener(ContentListener listener) {
285            mMediaSet.removeContentListener(listener);
286        }
287    }
288
289    private static class SequentialSource implements SlideshowDataAdapter.SlideshowSource {
290        private static final int DATA_SIZE = 32;
291
292        private ArrayList<MediaItem> mData = new ArrayList<MediaItem>();
293        private int mDataStart = 0;
294        private long mDataVersion = MediaObject.INVALID_DATA_VERSION;
295        private final MediaSet mMediaSet;
296        private final boolean mRepeat;
297
298        public SequentialSource(MediaSet mediaSet, boolean repeat) {
299            mMediaSet = mediaSet;
300            mRepeat = repeat;
301        }
302
303        public int findItemIndex(Path path, int hint) {
304            return mMediaSet.getIndexOfItem(path, hint);
305        }
306
307        public MediaItem getMediaItem(int index) {
308            int dataEnd = mDataStart + mData.size();
309
310            if (mRepeat) {
311                int count = mMediaSet.getMediaItemCount();
312                if (count == 0) return null;
313                index = index % count;
314            }
315            if (index < mDataStart || index >= dataEnd) {
316                mData = mMediaSet.getMediaItem(index, DATA_SIZE);
317                mDataStart = index;
318                dataEnd = index + mData.size();
319            }
320
321            return (index < mDataStart || index >= dataEnd) ? null : mData.get(index - mDataStart);
322        }
323
324        public long reload() {
325            long version = mMediaSet.reload();
326            if (version != mDataVersion) {
327                mDataVersion = version;
328                mData.clear();
329            }
330            return mDataVersion;
331        }
332
333        public void addContentListener(ContentListener listener) {
334            mMediaSet.addContentListener(listener);
335        }
336
337        public void removeContentListener(ContentListener listener) {
338            mMediaSet.removeContentListener(listener);
339        }
340    }
341}
342