am ef50c458: Merge "Add slideshow page." into gingerbread

Merge commit 'ef50c458c9f9e970e51e3363a46015b69ba2aa6d' into gingerbread-plus-aosp

* commit 'ef50c458c9f9e970e51e3363a46015b69ba2aa6d':
  Add slideshow page.
diff --git a/new3d/src/com/android/gallery3d/anim/FloatAnimation.java b/new3d/src/com/android/gallery3d/anim/FloatAnimation.java
index 084383f..ef177c9 100644
--- a/new3d/src/com/android/gallery3d/anim/FloatAnimation.java
+++ b/new3d/src/com/android/gallery3d/anim/FloatAnimation.java
@@ -9,6 +9,7 @@
     public FloatAnimation(float from, float to, int duration) {
         mFrom = from;
         mTo = to;
+        mCurrent = from;
         setDuration(duration);
     }
 
diff --git a/new3d/src/com/android/gallery3d/app/AlbumSetPage.java b/new3d/src/com/android/gallery3d/app/AlbumSetPage.java
index 92cbbc2..dd78e01 100644
--- a/new3d/src/com/android/gallery3d/app/AlbumSetPage.java
+++ b/new3d/src/com/android/gallery3d/app/AlbumSetPage.java
@@ -25,8 +25,8 @@
 import com.android.gallery3d.R;
 import com.android.gallery3d.data.MediaSet;
 import com.android.gallery3d.ui.AdaptiveBackground;
-import com.android.gallery3d.ui.GLView;
 import com.android.gallery3d.ui.AlbumSetView;
+import com.android.gallery3d.ui.GLView;
 import com.android.gallery3d.ui.HeadUpDisplay;
 import com.android.gallery3d.ui.SelectionManager;
 import com.android.gallery3d.ui.SlotView;
@@ -76,6 +76,8 @@
         if (!mSelectionManager.isSelectionMode()) {
             Bundle data = new Bundle();
             data.putInt(AlbumPage.KEY_BUCKET_INDEX, slotIndex);
+            // uncomment the following line to test slideshow mode
+            // mContext.getStateManager().startState(SlideshowPage.class, data);
             mContext.getStateManager().startState(AlbumPage.class, data);
         } else {
             mSelectionManager.selectSlot(slotIndex);
diff --git a/new3d/src/com/android/gallery3d/app/SlideshowDataAdapter.java b/new3d/src/com/android/gallery3d/app/SlideshowDataAdapter.java
new file mode 100644
index 0000000..04b7695
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/app/SlideshowDataAdapter.java
@@ -0,0 +1,191 @@
+/*
+ * 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.graphics.Bitmap;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+import com.android.gallery3d.app.SlideshowPage.ModelListener;
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.data.MediaSet;
+import com.android.gallery3d.data.MediaSet.MediaSetListener;
+import com.android.gallery3d.ui.SynchronizedHandler;
+import com.android.gallery3d.util.Future;
+import com.android.gallery3d.util.FutureListener;
+import com.android.gallery3d.util.Utils;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+
+public class SlideshowDataAdapter implements SlideshowPage.Model {
+    private static final String TAG = "SlideshowDataAdapter";
+
+    private static final int IMAGE_QUEUE_CAPACITY = 3;
+
+    private static final int MSG_LOAD_MEDIA_ITEM = 0;
+    private static final int MSG_FILL_BITMAP = 1;
+
+    private static final int MSG_LOAD_DATA = 2;
+    private static final int MSG_UPDATE_DATA = 3;
+
+    private final MediaSet mSource;
+
+    private int mIndex = 0;
+    private int mSize = 0;
+
+    private LinkedList<Bitmap> mImageQueue = new LinkedList<Bitmap>();
+    private int mImageRequestCount = IMAGE_QUEUE_CAPACITY;
+
+    private Handler mMainHandler;
+    private Handler mDataHandler;
+
+    private SlideshowPage.ModelListener mListener;
+
+    public SlideshowDataAdapter(GalleryContext context, MediaSet source) {
+        mSource = source;
+        mSource.setContentListener(new SourceListener());
+
+        mMainHandler = new SynchronizedHandler(context.getGLRoot()) {
+            @Override
+            public void handleMessage(Message message) {
+                switch (message.what) {
+                    case MSG_UPDATE_DATA:
+                        ((ReloadTask) message.obj).updateContent();
+                        break;
+                    case MSG_FILL_BITMAP:
+                        ((LoadNextImageTask) message.obj).fillBitmap();
+                        break;
+                    default: throw new AssertionError(message.what);
+                }
+            }
+        };
+
+        mDataHandler = new Handler(context.getDataManager().getDataLooper()) {
+            @Override
+            public void handleMessage(Message message) {
+                switch (message.what) {
+                    case MSG_LOAD_DATA:
+                        ((ReloadTask) message.obj).reloadData();
+                        break;
+                    case MSG_LOAD_MEDIA_ITEM:
+                        ((LoadNextImageTask) message.obj).loadMediaItem();
+                        break;
+                    default:
+                        throw new AssertionError(message.what);
+                }
+            }
+        };
+
+        new ReloadTask().execute();
+    }
+
+    public boolean hasNext() {
+        return !mImageQueue.isEmpty();
+    }
+
+    public Bitmap nextSlideBitmap() {
+        new LoadNextImageTask(mIndex++).execute();
+        return mImageQueue.removeFirst();
+    }
+
+    private class ReloadTask {
+        private int mUpdateSize = -1;
+
+        public void execute() {
+            mDataHandler.sendMessage(
+                    mDataHandler.obtainMessage(MSG_LOAD_DATA, this));
+        }
+
+        public void reloadData() {
+            mSource.reload();
+            mUpdateSize = mSource.getMediaItemCount();
+            mMainHandler.sendMessage(
+                    mMainHandler.obtainMessage(MSG_UPDATE_DATA, this));
+        }
+
+        public void updateContent() {
+            int size = mSize;
+            mSize = mUpdateSize;
+            if (size == mIndex && size < mUpdateSize && mImageRequestCount > 0) {
+                --mImageRequestCount;
+                new LoadNextImageTask(mIndex++).execute();
+            }
+        }
+    }
+
+    private class LoadNextImageTask implements FutureListener<Bitmap>{
+        private int mItemIndex;
+        private MediaItem mItem;
+        private Bitmap mBitmap;
+
+        public LoadNextImageTask(int index) {
+            mItemIndex = index;
+        }
+
+        public void execute() {
+            mDataHandler.sendMessage(
+                    mDataHandler.obtainMessage(MSG_LOAD_MEDIA_ITEM, this));
+        }
+
+        public void loadMediaItem() {
+            ArrayList<MediaItem> list = mSource.getMediaItem(mItemIndex, 1);
+            // If list is empty, assume it is the end of the list
+            if (!list.isEmpty()) {
+                mItem = list.get(0);
+                mItem.requestImage(MediaItem.TYPE_FULL_IMAGE, this);
+            }
+        }
+
+        public void onFutureDone(Future<? extends Bitmap> future) {
+            try {
+                mBitmap = future.get();
+                if (mBitmap != null) {
+                    mBitmap = Utils.resizeBitmap(mBitmap, 640);
+                }
+            } catch (Throwable e) {
+                Log.w(TAG, "fail to get bitmap", e);
+            }
+            mMainHandler.sendMessage(
+                    mMainHandler.obtainMessage(MSG_FILL_BITMAP, this));
+        }
+
+        public void fillBitmap() {
+            if (mBitmap != null) {
+                mImageQueue.addLast(mBitmap);
+                if (mImageQueue.size() == 1 && mListener != null) {
+                    mListener.onContentChanged();
+                }
+            }
+            if (mImageRequestCount > 0 && mIndex < mSize) {
+                --mImageRequestCount;
+                new LoadNextImageTask(mIndex++).execute();
+            }
+        }
+    }
+
+    private class SourceListener implements MediaSetListener {
+        public void onContentDirty() {
+            new ReloadTask().execute();
+        }
+    }
+
+    public void setListener(ModelListener listener) {
+        mListener = listener;
+    }
+}
diff --git a/new3d/src/com/android/gallery3d/app/SlideshowPage.java b/new3d/src/com/android/gallery3d/app/SlideshowPage.java
new file mode 100644
index 0000000..7e7053f
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/app/SlideshowPage.java
@@ -0,0 +1,127 @@
+/*
+ * 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.graphics.Bitmap;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+
+import com.android.gallery3d.data.MediaSet;
+import com.android.gallery3d.ui.GLCanvas;
+import com.android.gallery3d.ui.GLView;
+import com.android.gallery3d.ui.SlideshowView;
+import com.android.gallery3d.ui.SynchronizedHandler;
+import com.android.gallery3d.util.Utils;
+
+public class SlideshowPage extends ActivityState {
+    private static final String TAG = "SlideshowPage";
+
+    public static final String KEY_BUCKET_INDEX = "keyBucketIndex";
+
+    private static final long SLIDESHOW_DELAY = 3000; // 2 seconds
+    private static final int MSG_SHOW_NEXT_SLIDE = 1;
+
+    public static interface Model {
+        public boolean hasNext();
+        public Bitmap nextSlideBitmap();
+        public void setListener(ModelListener listener);
+    }
+
+    public static interface ModelListener {
+        public void onContentChanged();
+    }
+
+    private Handler mHandler;
+    private Model mModel;
+    private SlideshowView mSlideshowView;
+    private boolean mSlideshowActive = false;
+
+    private 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 void renderBackground(GLCanvas canvas) {
+            canvas.clearBuffer();
+        }
+    };
+
+    @Override
+    public void onCreate(Bundle data, Bundle restoreState) {
+        mHandler = new SynchronizedHandler(mContext.getGLRoot()) {
+            @Override
+            public void handleMessage(Message message) {
+                Utils.Assert(message.what == MSG_SHOW_NEXT_SLIDE);
+                showNextSlide();
+            }
+        };
+        initializeViews();
+        intializeData(data);
+    }
+
+    private void showNextSlide() {
+        if (!mModel.hasNext()) {
+            mSlideshowActive = false;
+            return;
+        }
+        mSlideshowActive = true;
+        mSlideshowView.next(mModel.nextSlideBitmap());
+        mHandler.sendEmptyMessageDelayed(MSG_SHOW_NEXT_SLIDE, SLIDESHOW_DELAY);
+    }
+
+    @Override
+    public void onPause() {
+        mHandler.removeMessages(MSG_SHOW_NEXT_SLIDE);
+    }
+
+    @Override
+    public void onResume() {
+        mHandler.sendEmptyMessage(MSG_SHOW_NEXT_SLIDE);
+    }
+
+    private void initializeViews() {
+        mSlideshowView = new SlideshowView();
+        mRootPane.addComponent(mSlideshowView);
+        setContentPane(mRootPane);
+    }
+
+    private void intializeData(Bundle data) {
+        int bucketIndex = data.getInt(KEY_BUCKET_INDEX);
+        MediaSet mediaSet = mContext.getDataManager()
+                .getRootSet().getSubMediaSet(bucketIndex);
+        SlideshowDataAdapter adapter =
+                new SlideshowDataAdapter(mContext, mediaSet);
+        setModel(adapter);
+    }
+
+    public void setModel(Model source) {
+        mHandler.removeMessages(MSG_SHOW_NEXT_SLIDE);
+        mModel = source;
+        mModel.setListener(new MyModelListener());
+        showNextSlide();
+    }
+
+    private class MyModelListener implements ModelListener {
+        public void onContentChanged() {
+            if (!mSlideshowActive) showNextSlide();
+        }
+    }
+}
diff --git a/new3d/src/com/android/gallery3d/ui/BitmapTexture.java b/new3d/src/com/android/gallery3d/ui/BitmapTexture.java
index 261b20b..16df82d 100644
--- a/new3d/src/com/android/gallery3d/ui/BitmapTexture.java
+++ b/new3d/src/com/android/gallery3d/ui/BitmapTexture.java
@@ -42,4 +42,8 @@
     protected Bitmap onGetBitmap() {
         return mContentBitmap;
     }
+
+    public Bitmap getBitmap() {
+        return mContentBitmap;
+    }
 }
diff --git a/new3d/src/com/android/gallery3d/ui/SlideshowView.java b/new3d/src/com/android/gallery3d/ui/SlideshowView.java
new file mode 100644
index 0000000..0c830d2
--- /dev/null
+++ b/new3d/src/com/android/gallery3d/ui/SlideshowView.java
@@ -0,0 +1,162 @@
+/*
+ * 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.ui;
+
+import android.graphics.Bitmap;
+import android.graphics.PointF;
+
+import com.android.gallery3d.anim.CanvasAnimation;
+import com.android.gallery3d.anim.FloatAnimation;
+
+import java.util.Random;
+
+import javax.microedition.khronos.opengles.GL11;
+
+public class SlideshowView extends GLView {
+    private static final String TAG = "SlideshowView";
+
+    private static final int SLIDESHOW_DURATION = 3500;
+    private static final int TRANSITION_DURATION = 1000;
+
+    private static final float SCALE_SPEED = 0.20f ;
+    private static final float MOVE_SPEED = SCALE_SPEED;
+
+    private BitmapTexture mCurrentTexture;
+    private SlideshowAnimation mCurrentAnimation;
+
+    private BitmapTexture mPrevTexture;
+    private SlideshowAnimation mPrevAnimation;
+
+    private final FloatAnimation mTransitionAnimation =
+            new FloatAnimation(0, 1, TRANSITION_DURATION);
+
+    private Random mRandom = new Random();
+
+    public static interface SlideshowListener {
+        public void onSlideshowEnd();
+    }
+
+    public SlideshowView() {
+    }
+
+    public void next(Bitmap bitmap) {
+
+        mTransitionAnimation.start();
+
+        if (mPrevTexture != null) {
+            mPrevTexture.getBitmap().recycle();
+            mPrevTexture.recycle();
+        }
+
+        mPrevTexture = mCurrentTexture;
+        mPrevAnimation = mCurrentAnimation;
+
+        mCurrentTexture = new BitmapTexture(bitmap);
+        mCurrentAnimation = new SlideshowAnimation(mCurrentTexture, mRandom);
+        mCurrentAnimation.start();
+
+        invalidate();
+    }
+
+    public void close() {
+        if (mPrevTexture != null) {
+            mPrevTexture.getBitmap().recycle();
+            mPrevTexture.recycle();
+            mPrevTexture = null;
+        }
+        if (mCurrentTexture != null) {
+            mCurrentTexture.getBitmap().recycle();
+            mCurrentTexture.recycle();
+            mCurrentTexture = null;
+        }
+    }
+
+    @Override
+    protected void render(GLCanvas canvas) {
+        long currentTimeMillis = canvas.currentAnimationTimeMillis();
+        boolean requestRender = mTransitionAnimation.calculate(currentTimeMillis);
+        GL11 gl = canvas.getGLInstance();
+        gl.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE);
+        float alpha = mPrevTexture == null ? 1f : mTransitionAnimation.get();
+
+        if (mPrevTexture != null && alpha != 1f) {
+            requestRender |= mPrevAnimation.calculate(currentTimeMillis);
+            canvas.save(GLCanvas.SAVE_FLAG_ALPHA
+                    | mPrevAnimation.getCanvasSaveFlags());
+            canvas.setAlpha(1f - alpha);
+            mPrevAnimation.apply(canvas);
+            mPrevTexture.draw(canvas, -mPrevTexture.getWidth() / 2,
+                    -mPrevTexture.getHeight() / 2);
+            canvas.restore();
+        }
+        if (mCurrentTexture != null) {
+            requestRender |= mCurrentAnimation.calculate(currentTimeMillis);
+            canvas.save(GLCanvas.SAVE_FLAG_ALPHA
+                    | mCurrentAnimation.getCanvasSaveFlags());
+            canvas.setAlpha(alpha);
+            mCurrentAnimation.apply(canvas);
+            mCurrentTexture.draw(canvas, -mCurrentTexture.getWidth() / 2,
+                    -mCurrentTexture.getHeight() / 2);
+            canvas.restore();
+        }
+        if (requestRender) invalidate();
+        gl.glBlendFunc(GL11.GL_ONE, GL11.GL_ONE_MINUS_SRC_ALPHA);
+    }
+
+    private class SlideshowAnimation extends CanvasAnimation {
+        private final int mWidth;
+        private final int mHeight;
+
+        private final PointF mMovingVector;
+        private float mProgress;
+
+        public SlideshowAnimation(Texture texture, Random random) {
+            mWidth = texture.getWidth();
+            mHeight = texture.getHeight();
+            mMovingVector = new PointF(
+                    MOVE_SPEED * mWidth * (random.nextFloat() - 0.5f),
+                    MOVE_SPEED * mHeight * (random.nextFloat() - 0.5f));
+            setDuration(SLIDESHOW_DURATION);
+        }
+
+        @Override
+        public void apply(GLCanvas canvas) {
+            int viewWidth = getWidth();
+            int viewHeight = getHeight();
+
+            float initScale = Math.min(2f, Math.min((float)
+                    viewWidth / mWidth, (float) viewHeight / mHeight));
+            float scale = initScale * (1 + SCALE_SPEED * mProgress);
+
+            float centerX = viewWidth / 2 + mMovingVector.x * mProgress;
+            float centerY = viewHeight / 2 + mMovingVector.y * mProgress;
+
+            canvas.translate(centerX, centerY, 0);
+            canvas.scale(scale, scale, 0);
+        }
+
+        @Override
+        public int getCanvasSaveFlags() {
+            return GLCanvas.SAVE_FLAG_MATRIX;
+        }
+
+        @Override
+        protected void onCalculate(float progress) {
+            mProgress = progress;
+        }
+    }
+}
diff --git a/new3d/src/com/android/gallery3d/ui/UploadedTexture.java b/new3d/src/com/android/gallery3d/ui/UploadedTexture.java
index f9248e5..1be54a6 100644
--- a/new3d/src/com/android/gallery3d/ui/UploadedTexture.java
+++ b/new3d/src/com/android/gallery3d/ui/UploadedTexture.java
@@ -16,11 +16,11 @@
 
 package com.android.gallery3d.ui;
 
-import com.android.gallery3d.util.Utils;
-
 import android.graphics.Bitmap;
 import android.opengl.GLUtils;
 
+import com.android.gallery3d.util.Utils;
+
 import javax.microedition.khronos.opengles.GL11;
 import javax.microedition.khronos.opengles.GL11Ext;
 
diff --git a/new3d/src/com/android/gallery3d/util/Utils.java b/new3d/src/com/android/gallery3d/util/Utils.java
index 686f3c6..cac7d3f 100644
--- a/new3d/src/com/android/gallery3d/util/Utils.java
+++ b/new3d/src/com/android/gallery3d/util/Utils.java
@@ -91,6 +91,24 @@
         return bitmap;
     }
 
+    public static final Bitmap resizeBitmap(Bitmap bitmap, int maxSize) {
+        int srcWidth = bitmap.getWidth();
+        int srcHeight = bitmap.getHeight();
+        float scale = Math.min(
+                (float) maxSize / srcWidth, (float) maxSize / srcHeight);
+        if (scale >= 1.0f) return bitmap;
+
+        int width = Math.round(srcWidth * scale);
+        int height = Math.round(srcHeight * scale);
+        Bitmap target = Bitmap.createBitmap(width, height,
+                bitmap.hasAlpha() ? Config.ARGB_8888 : Config.RGB_565);
+        Canvas canvas = new Canvas(target);
+        canvas.scale(scale, scale);
+        canvas.drawBitmap(bitmap, 0, 0, null);
+        bitmap.recycle();
+        return target;
+    }
+
     // Throws AssertionError if the input is false.
     public static void Assert(boolean cond) {
         if (!cond) {