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