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