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