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