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