/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.gallery3d.app; import android.app.Activity; import android.content.Intent; import android.graphics.Bitmap; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.MotionEvent; import com.android.gallery3d.R; import com.android.gallery3d.common.Utils; import com.android.gallery3d.data.ContentListener; import com.android.gallery3d.data.MediaItem; import com.android.gallery3d.data.MediaObject; import com.android.gallery3d.data.MediaSet; import com.android.gallery3d.data.Path; import com.android.gallery3d.glrenderer.GLCanvas; import com.android.gallery3d.ui.GLView; import com.android.gallery3d.ui.SlideshowView; import com.android.gallery3d.ui.SynchronizedHandler; import com.android.gallery3d.util.Future; import com.android.gallery3d.util.FutureListener; import java.util.ArrayList; import java.util.Random; public class SlideshowPage extends ActivityState { private static final String TAG = "SlideshowPage"; public static final String KEY_SET_PATH = "media-set-path"; public static final String KEY_ITEM_PATH = "media-item-path"; public static final String KEY_PHOTO_INDEX = "photo-index"; public static final String KEY_RANDOM_ORDER = "random-order"; public static final String KEY_REPEAT = "repeat"; public static final String KEY_DREAM = "dream"; private static final long SLIDESHOW_DELAY = 3000; // 3 seconds private static final int MSG_LOAD_NEXT_BITMAP = 1; private static final int MSG_SHOW_PENDING_BITMAP = 2; public static interface Model { public void pause(); public void resume(); public Future nextSlide(FutureListener listener); } public static class Slide { public Bitmap bitmap; public MediaItem item; public int index; public Slide(MediaItem item, int index, Bitmap bitmap) { this.bitmap = bitmap; this.item = item; this.index = index; } } private Handler mHandler; private Model mModel; private SlideshowView mSlideshowView; private Slide mPendingSlide = null; private boolean mIsActive = false; private final Intent mResultIntent = new Intent(); @Override protected int getBackgroundColorId() { return R.color.slideshow_background; } private final GLView mRootPane = new GLView() { @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { mSlideshowView.layout(0, 0, right - left, bottom - top); } @Override protected boolean onTouch(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP) { onBackPressed(); } return true; } @Override protected void renderBackground(GLCanvas canvas) { canvas.clearBuffer(getBackgroundColor()); } }; @Override public void onCreate(Bundle data, Bundle restoreState) { super.onCreate(data, restoreState); mFlags |= (FLAG_HIDE_ACTION_BAR | FLAG_HIDE_STATUS_BAR); if (data.getBoolean(KEY_DREAM)) { // Dream screensaver only keeps screen on for plugged devices. mFlags |= FLAG_SCREEN_ON_WHEN_PLUGGED | FLAG_SHOW_WHEN_LOCKED; } else { // User-initiated slideshow would always keep screen on. mFlags |= FLAG_SCREEN_ON_ALWAYS; } mHandler = new SynchronizedHandler(mActivity.getGLRoot()) { @Override public void handleMessage(Message message) { switch (message.what) { case MSG_SHOW_PENDING_BITMAP: showPendingBitmap(); break; case MSG_LOAD_NEXT_BITMAP: loadNextBitmap(); break; default: throw new AssertionError(); } } }; initializeViews(); initializeData(data); } private void loadNextBitmap() { mModel.nextSlide(new FutureListener() { @Override public void onFutureDone(Future future) { mPendingSlide = future.get(); mHandler.sendEmptyMessage(MSG_SHOW_PENDING_BITMAP); } }); } private void showPendingBitmap() { // mPendingBitmap could be null, if // 1.) there is no more items // 2.) mModel is paused Slide slide = mPendingSlide; if (slide == null) { if (mIsActive) { mActivity.getStateManager().finishState(SlideshowPage.this); } return; } mSlideshowView.next(slide.bitmap, slide.item.getRotation()); setStateResult(Activity.RESULT_OK, mResultIntent .putExtra(KEY_ITEM_PATH, slide.item.getPath().toString()) .putExtra(KEY_PHOTO_INDEX, slide.index)); mHandler.sendEmptyMessageDelayed(MSG_LOAD_NEXT_BITMAP, SLIDESHOW_DELAY); } @Override public void onPause() { super.onPause(); mIsActive = false; mModel.pause(); mSlideshowView.release(); mHandler.removeMessages(MSG_LOAD_NEXT_BITMAP); mHandler.removeMessages(MSG_SHOW_PENDING_BITMAP); } @Override public void onResume() { super.onResume(); mIsActive = true; mModel.resume(); if (mPendingSlide != null) { showPendingBitmap(); } else { loadNextBitmap(); } } private void initializeData(Bundle data) { boolean random = data.getBoolean(KEY_RANDOM_ORDER, false); // We only want to show slideshow for images only, not videos. String mediaPath = data.getString(KEY_SET_PATH); mediaPath = FilterUtils.newFilterPath(mediaPath, FilterUtils.FILTER_IMAGE_ONLY); MediaSet mediaSet = mActivity.getDataManager().getMediaSet(mediaPath); if (random) { boolean repeat = data.getBoolean(KEY_REPEAT); mModel = new SlideshowDataAdapter(mActivity, new ShuffleSource(mediaSet, repeat), 0, null); setStateResult(Activity.RESULT_OK, mResultIntent.putExtra(KEY_PHOTO_INDEX, 0)); } else { int index = data.getInt(KEY_PHOTO_INDEX); String itemPath = data.getString(KEY_ITEM_PATH); Path path = itemPath != null ? Path.fromString(itemPath) : null; boolean repeat = data.getBoolean(KEY_REPEAT); mModel = new SlideshowDataAdapter(mActivity, new SequentialSource(mediaSet, repeat), index, path); setStateResult(Activity.RESULT_OK, mResultIntent.putExtra(KEY_PHOTO_INDEX, index)); } } private void initializeViews() { mSlideshowView = new SlideshowView(); mRootPane.addComponent(mSlideshowView); setContentPane(mRootPane); } private static MediaItem findMediaItem(MediaSet mediaSet, int index) { for (int i = 0, n = mediaSet.getSubMediaSetCount(); i < n; ++i) { MediaSet subset = mediaSet.getSubMediaSet(i); int count = subset.getTotalMediaItemCount(); if (index < count) { return findMediaItem(subset, index); } index -= count; } ArrayList list = mediaSet.getMediaItem(index, 1); return list.isEmpty() ? null : list.get(0); } private static class ShuffleSource implements SlideshowDataAdapter.SlideshowSource { private static final int RETRY_COUNT = 5; private final MediaSet mMediaSet; private final Random mRandom = new Random(); private int mOrder[] = new int[0]; private final boolean mRepeat; private long mSourceVersion = MediaSet.INVALID_DATA_VERSION; private int mLastIndex = -1; public ShuffleSource(MediaSet mediaSet, boolean repeat) { mMediaSet = Utils.checkNotNull(mediaSet); mRepeat = repeat; } @Override public int findItemIndex(Path path, int hint) { return hint; } @Override public MediaItem getMediaItem(int index) { if (!mRepeat && index >= mOrder.length) return null; if (mOrder.length == 0) return null; mLastIndex = mOrder[index % mOrder.length]; MediaItem item = findMediaItem(mMediaSet, mLastIndex); for (int i = 0; i < RETRY_COUNT && item == null; ++i) { Log.w(TAG, "fail to find image: " + mLastIndex); mLastIndex = mRandom.nextInt(mOrder.length); item = findMediaItem(mMediaSet, mLastIndex); } return item; } @Override public long reload() { long version = mMediaSet.reload(); if (version != mSourceVersion) { mSourceVersion = version; int count = mMediaSet.getTotalMediaItemCount(); if (count != mOrder.length) generateOrderArray(count); } return version; } private void generateOrderArray(int totalCount) { if (mOrder.length != totalCount) { mOrder = new int[totalCount]; for (int i = 0; i < totalCount; ++i) { mOrder[i] = i; } } for (int i = totalCount - 1; i > 0; --i) { Utils.swap(mOrder, i, mRandom.nextInt(i + 1)); } if (mOrder[0] == mLastIndex && totalCount > 1) { Utils.swap(mOrder, 0, mRandom.nextInt(totalCount - 1) + 1); } } @Override public void addContentListener(ContentListener listener) { mMediaSet.addContentListener(listener); } @Override public void removeContentListener(ContentListener listener) { mMediaSet.removeContentListener(listener); } } private static class SequentialSource implements SlideshowDataAdapter.SlideshowSource { private static final int DATA_SIZE = 32; private ArrayList mData = new ArrayList(); private int mDataStart = 0; private long mDataVersion = MediaObject.INVALID_DATA_VERSION; private final MediaSet mMediaSet; private final boolean mRepeat; public SequentialSource(MediaSet mediaSet, boolean repeat) { mMediaSet = mediaSet; mRepeat = repeat; } @Override public int findItemIndex(Path path, int hint) { return mMediaSet.getIndexOfItem(path, hint); } @Override public MediaItem getMediaItem(int index) { int dataEnd = mDataStart + mData.size(); if (mRepeat) { int count = mMediaSet.getMediaItemCount(); if (count == 0) return null; index = index % count; } if (index < mDataStart || index >= dataEnd) { mData = mMediaSet.getMediaItem(index, DATA_SIZE); mDataStart = index; dataEnd = index + mData.size(); } return (index < mDataStart || index >= dataEnd) ? null : mData.get(index - mDataStart); } @Override public long reload() { long version = mMediaSet.reload(); if (version != mDataVersion) { mDataVersion = version; mData.clear(); } return mDataVersion; } @Override public void addContentListener(ContentListener listener) { mMediaSet.addContentListener(listener); } @Override public void removeContentListener(ContentListener listener) { mMediaSet.removeContentListener(listener); } } }