summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJim Miller <jaggies@google.com>2010-09-02 13:17:24 -0700
committerJim Miller <jaggies@google.com>2010-09-02 17:06:39 -0700
commit5ce730797a8a7278dfe19dac8a9460b25675fed0 (patch)
treef7b506eeef5ccc496e206ea36609e201e189aedc
parent1fa3a8f74d46a616e27c23ed1512f4b7de2ad66d (diff)
downloadandroid_frameworks_ex-5ce730797a8a7278dfe19dac8a9460b25675fed0.tar.gz
android_frameworks_ex-5ce730797a8a7278dfe19dac8a9460b25675fed0.tar.bz2
android_frameworks_ex-5ce730797a8a7278dfe19dac8a9460b25675fed0.zip
Add carousel as static library to build.
Change-Id: I5e3eb0a6c3ea6deeee3856c486bbb469c8d20360
-rw-r--r--carousel/Android.mk33
-rw-r--r--carousel/java/com/android/ex/carousel/CarouselRS.java372
-rw-r--r--carousel/java/com/android/ex/carousel/CarouselView.java274
-rw-r--r--carousel/java/com/android/ex/carousel/carousel.rs788
-rw-r--r--carousel/test/Android.mk29
-rw-r--r--carousel/test/AndroidManifest.xml69
-rw-r--r--carousel/test/res/anim/zoom_enter.xml30
-rw-r--r--carousel/test/res/anim/zoom_exit.xml33
-rw-r--r--carousel/test/res/drawable/blank_album.pngbin0 -> 280310 bytes
-rw-r--r--carousel/test/res/drawable/emo_im_angel.pngbin0 -> 1534 bytes
-rw-r--r--carousel/test/res/drawable/emo_im_cool.pngbin0 -> 1321 bytes
-rw-r--r--carousel/test/res/drawable/emo_im_crying.pngbin0 -> 1401 bytes
-rw-r--r--carousel/test/res/drawable/emo_im_foot_in_mouth.pngbin0 -> 1387 bytes
-rw-r--r--carousel/test/res/drawable/emo_im_happy.pngbin0 -> 1486 bytes
-rw-r--r--carousel/test/res/drawable/emo_im_kissing.pngbin0 -> 1156 bytes
-rw-r--r--carousel/test/res/drawable/emo_im_laughing.pngbin0 -> 1382 bytes
-rw-r--r--carousel/test/res/drawable/emo_im_lips_are_sealed.pngbin0 -> 1543 bytes
-rw-r--r--carousel/test/res/drawable/emo_im_money_mouth.pngbin0 -> 1404 bytes
-rw-r--r--carousel/test/res/drawable/emo_im_sad.pngbin0 -> 1455 bytes
-rw-r--r--carousel/test/res/drawable/emo_im_surprised.pngbin0 -> 1252 bytes
-rw-r--r--carousel/test/res/drawable/emo_im_tongue_sticking_out.pngbin0 -> 1577 bytes
-rw-r--r--carousel/test/res/drawable/emo_im_undecided.pngbin0 -> 1400 bytes
-rw-r--r--carousel/test/res/drawable/emo_im_winking.pngbin0 -> 1322 bytes
-rw-r--r--carousel/test/res/drawable/emo_im_wtf.pngbin0 -> 1385 bytes
-rw-r--r--carousel/test/res/drawable/emo_im_yelling.pngbin0 -> 1433 bytes
-rw-r--r--carousel/test/res/drawable/specularmap.pngbin0 -> 67185 bytes
-rw-r--r--carousel/test/res/drawable/unknown.pngbin0 -> 3489 bytes
-rw-r--r--carousel/test/res/drawable/wait.pngbin0 -> 70476 bytes
-rw-r--r--carousel/test/res/layout/music_demo.xml33
-rw-r--r--carousel/test/res/layout/taskswitcher.xml55
-rw-r--r--carousel/test/res/raw/book.a3dbin0 -> 3640 bytes
-rw-r--r--carousel/test/res/values/strings.xml29
-rw-r--r--carousel/test/src/com/android/carouseltest/CarouselTestActivity.java183
-rw-r--r--carousel/test/src/com/android/carouseltest/MusicDemoActivity.java248
-rw-r--r--carousel/test/src/com/android/carouseltest/MyCarouselView.java39
-rw-r--r--carousel/test/src/com/android/carouseltest/TaskSwitcherActivity.java315
36 files changed, 2530 insertions, 0 deletions
diff --git a/carousel/Android.mk b/carousel/Android.mk
new file mode 100644
index 0000000..04e5d4f
--- /dev/null
+++ b/carousel/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2009 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.
+
+LOCAL_PATH := $(call my-dir)
+
+# Note: the source code is in java/, not src/, because this code is also part of
+# the framework library, and build/core/pathmap.mk expects a java/ subdirectory.
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-common-carousel
+#LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, java) \
+ $(call all-logtags-files-under, java) \
+ $(call all-renderscript-files-under, java)
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Include this library in the build server's output directory
+$(call dist-for-goals, droidcore, $(LOCAL_BUILT_MODULE):android-common-carousel.jar)
+
+
diff --git a/carousel/java/com/android/ex/carousel/CarouselRS.java b/carousel/java/com/android/ex/carousel/CarouselRS.java
new file mode 100644
index 0000000..fde8549
--- /dev/null
+++ b/carousel/java/com/android/ex/carousel/CarouselRS.java
@@ -0,0 +1,372 @@
+/*
+ * 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.ex.carousel;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.renderscript.*;
+import android.renderscript.RenderScript.RSMessage;
+import android.renderscript.Sampler.Value;
+import android.renderscript.ProgramRaster.CullMode;
+import android.util.Log;
+
+import com.android.internal.R;
+
+import static android.renderscript.Element.*;
+import static android.renderscript.Sampler.Value.LINEAR;
+import static android.renderscript.Sampler.Value.WRAP;
+import static android.renderscript.Sampler.Value.CLAMP;
+
+public class CarouselRS {
+ private static final int DEFAULT_VISIBLE_SLOTS = 1;
+ private static final int DEFAULT_CARD_COUNT = 1;
+
+ // Client messages *** THIS LIST MUST MATCH THOSE IN carousel.rs ***
+ public static final int CMD_CARD_SELECTED = 100;
+ public static final int CMD_REQUEST_TEXTURE = 200;
+ public static final int CMD_INVALIDATE_TEXTURE = 210;
+ public static final int CMD_REQUEST_GEOMETRY = 300;
+ public static final int CMD_INVALIDATE_GEOMETRY = 310;
+ public static final int CMD_ANIMATION_STARTED = 400;
+ public static final int CMD_ANIMATION_FINISHED = 500;
+ public static final int CMD_PING = 600; // for debugging
+
+ private static final String TAG = "CarouselRS";
+ private static final int DEFAULT_SLOT_COUNT = 10;
+ private static final boolean MIPMAP = false;
+
+ private RenderScriptGL mRS;
+ private Resources mRes;
+ private ScriptC_Carousel mScript;
+ private ScriptField_Card mCards;
+ private Sampler mSampler;
+ private ProgramRaster mProgramRaster;
+ private ProgramStore mProgramStore;
+ private ProgramFragment mFragmentProgram;
+ private ProgramVertex mVertexProgram;
+ private ProgramRaster mRasterProgram;
+ private CarouselCallback mCallback;
+ private float[] mEyePoint = new float[3];
+ private float[] mAtPoint = new float[3];
+ private float[] mUp = new float[3];
+
+ public static interface CarouselCallback {
+ /**
+ * Called when a card is selected
+ * @param n the id of the card
+ */
+ void onCardSelected(int n);
+
+ /**
+ * Called when texture is needed for card n. This happens when the given card becomes
+ * visible.
+ * @param n the id of the card
+ */
+ void onRequestTexture(int n);
+
+ /**
+ * Called when a texture is no longer needed for card n. This happens when the card
+ * goes out of view.
+ * @param n the id of the card
+ */
+ void onInvalidateTexture(int n);
+
+ /**
+ * Called when geometry is needed for card n.
+ * @param n the id of the card.
+ */
+ void onRequestGeometry(int n);
+
+ /**
+ * Called when geometry is no longer needed for card n. This happens when the card goes
+ * out of view.
+ * @param n the id of the card
+ */
+ void onInvalidateGeometry(int n);
+
+ /**
+ * Called when card animation (e.g. a fling) has started.
+ */
+ void onAnimationStarted();
+
+ /**
+ * Called when card animation has stopped.
+ */
+ void onAnimationFinished();
+ };
+
+ private RSMessage mRsMessage = new RSMessage() {
+ public void run() {
+ if (mCallback == null) return;
+ switch (mID) {
+ case CMD_CARD_SELECTED:
+ mCallback.onCardSelected(mData[0]);
+ break;
+
+ case CMD_REQUEST_TEXTURE:
+ mCallback.onRequestTexture(mData[0]);
+ break;
+
+ case CMD_INVALIDATE_TEXTURE:
+ mCallback.onInvalidateTexture(mData[0]);
+ break;
+
+ case CMD_REQUEST_GEOMETRY:
+ mCallback.onRequestGeometry(mData[0]);
+ break;
+
+ case CMD_INVALIDATE_GEOMETRY:
+ mCallback.onInvalidateGeometry(mData[0]);
+ break;
+
+ case CMD_ANIMATION_STARTED:
+ mCallback.onAnimationStarted();
+ break;
+
+ case CMD_ANIMATION_FINISHED:
+ mCallback.onAnimationFinished();
+ break;
+
+ case CMD_PING:
+ Log.v(TAG, "PING...");
+ break;
+
+ default:
+ Log.e(TAG, "Unknown RSMessage: " + mID);
+ }
+ }
+ };
+
+ public void init(RenderScriptGL rs, Resources res, int resId) {
+ mRS = rs;
+ mRes = res;
+
+ // create the script object
+ mScript = new ScriptC_Carousel(mRS, mRes, resId, true);
+ mRS.mMessageCallback = mRsMessage;
+
+ initProgramStore();
+ initFragmentProgram();
+ initRasterProgram();
+ initVertexProgram();
+
+ setSlotCount(DEFAULT_SLOT_COUNT);
+ setVisibleSlots(DEFAULT_VISIBLE_SLOTS);
+ createCards(DEFAULT_CARD_COUNT);
+
+ setStartAngle(0.0f);
+ setRadius(1.0f);
+
+ // update the camera
+ boolean pcam = true;
+ if (pcam) {
+ float eye[] = { 20.6829f, 2.77081f, 16.7314f };
+ float at[] = { 14.7255f, -3.40001f, -1.30184f };
+ float up[] = { 0.0f, 1.0f, 0.0f };
+ setLookAt(eye, at, up);
+ setRadius(20.0f);
+ // Fov: 25
+ } else {
+ mScript.invoke_lookAt(2.5f, 2.0f, 2.5f, 0.0f, -0.75f, 0.0f, 0.0f, 1.0f, 0.0f);
+ mScript.set_cardRotation(0.0f);
+ setRadius(1.5f);
+ }
+
+ resumeRendering();
+ }
+
+ public void setLookAt(float[] eye, float[] at, float[] up) {
+ for (int i = 0; i < 3; i++) {
+ mEyePoint[i] = eye[i];
+ mAtPoint[i] = at[i];
+ mUp[i] = up[i];
+ }
+ mScript.invoke_lookAt(eye[0], eye[1], eye[2], at[0], at[1], at[2], up[0], up[1], up[2]);
+ }
+
+ public void setRadius(float radius) {
+ mScript.set_radius(radius);
+ }
+
+ private void initVertexProgram() {
+ ProgramVertex.Builder pvb = new ProgramVertex.Builder(mRS, null, null);
+ mVertexProgram = pvb.create();
+ ProgramVertex.MatrixAllocation pva = new ProgramVertex.MatrixAllocation(mRS);
+ mVertexProgram.bindAllocation(pva);
+ pva.setupProjectionNormalized(1, 1);
+ mScript.set_vertexProgram(mVertexProgram);
+ }
+
+ private void initRasterProgram() {
+ ProgramRaster.Builder programRasterBuilder = new ProgramRaster.Builder(mRS);
+ mRasterProgram = programRasterBuilder.create();
+ //mRasterProgram.setCullMode(CullMode.NONE);
+ mScript.set_rasterProgram(mRasterProgram);
+ }
+
+ private void initFragmentProgram() {
+ Sampler.Builder sampleBuilder = new Sampler.Builder(mRS);
+ sampleBuilder.setMin(Value.LINEAR_MIP_LINEAR);
+ sampleBuilder.setMag(LINEAR);
+ sampleBuilder.setWrapS(CLAMP);
+ sampleBuilder.setWrapT(CLAMP);
+ mSampler = sampleBuilder.create();
+ ProgramFragment.Builder fragmentBuilder = new ProgramFragment.Builder(mRS);
+ fragmentBuilder.setTexture(ProgramFragment.Builder.EnvMode.DECAL,
+ ProgramFragment.Builder.Format.RGBA, 0);
+ mFragmentProgram = fragmentBuilder.create();
+ mFragmentProgram.bindSampler(mSampler, 0);
+ mScript.set_fragmentProgram(mFragmentProgram);
+ }
+
+ private void initProgramStore() {
+ ProgramStore.Builder programStoreBuilder = new ProgramStore.Builder(mRS, null, null);
+ programStoreBuilder.setDepthFunc(ProgramStore.DepthFunc.LESS);
+ programStoreBuilder.setBlendFunc(ProgramStore.BlendSrcFunc.SRC_ALPHA,
+ ProgramStore.BlendDstFunc.ONE_MINUS_SRC_ALPHA);
+ programStoreBuilder.setDitherEnable(false);
+ programStoreBuilder.setDepthMask(true);
+ mProgramStore = programStoreBuilder.create();
+ mScript.set_programStore(mProgramStore);
+ }
+
+ public void createCards(int count)
+ {
+ mCards = count > 0 ? new ScriptField_Card(mRS, count) : null;
+ mScript.bind_cards(mCards);
+ mScript.invoke_createCards(count);
+ }
+
+ public void setVisibleSlots(int count)
+ {
+ mScript.set_visibleSlotCount(count);
+ }
+
+ public void setDefaultBitmap(Bitmap bitmap)
+ {
+ mScript.set_defaultTexture(allocationFromBitmap(bitmap, MIPMAP));
+ }
+
+ public void setLoadingBitmap(Bitmap bitmap)
+ {
+ mScript.set_loadingTexture(allocationFromBitmap(bitmap, MIPMAP));
+ }
+
+ public void setDefaultGeometry(Mesh mesh)
+ {
+ mScript.set_defaultGeometry(mesh);
+ }
+
+ public void setLoadingGeometry(Mesh mesh)
+ {
+ mScript.set_loadingGeometry(mesh);
+ }
+
+ public void setStartAngle(float theta)
+ {
+ mScript.set_startAngle(theta);
+ }
+
+ public void setCallback(CarouselCallback callback)
+ {
+ mCallback = callback;
+ }
+
+ private Allocation allocationFromBitmap(Bitmap bitmap, boolean mipmap)
+ {
+ if (bitmap == null) return null;
+ Allocation allocation = Allocation.createFromBitmap(mRS, bitmap, RGB_565(mRS), mipmap);
+ allocation.uploadToTexture(0);
+ return allocation;
+ }
+
+ public void setTexture(int n, Bitmap bitmap)
+ {
+ ScriptField_Card.Item item = mCards.get(n);
+ if (item == null) {
+ Log.v(TAG, "setTexture(): no item at index " + n);
+ item = new ScriptField_Card.Item();
+ }
+ if (bitmap != null) {
+ Log.v(TAG, "creating new bitmap");
+ item.texture = Allocation.createFromBitmap(mRS, bitmap, RGB_565(mRS), MIPMAP);
+ Log.v(TAG, "uploadToTexture(" + n + ")");
+ item.texture.uploadToTexture(0);
+ Log.v(TAG, "done...");
+ } else {
+ if (item.texture != null) {
+ Log.v(TAG, "unloading texture " + n);
+ // Don't wait for GC to free native memory.
+ // Only works if textures are not shared.
+ item.texture.destroy();
+ item.texture = null;
+ }
+ }
+ mCards.set(item, n, false); // This is primarily used for reference counting.
+ mScript.invoke_setTexture(n, item.texture);
+ }
+
+ public void setGeometry(int n, Mesh geometry)
+ {
+ final boolean mipmap = false;
+ ScriptField_Card.Item item = mCards.get(n);
+ if (item == null) {
+ Log.v(TAG, "setGeometry(): no item at index " + n);
+ item = new ScriptField_Card.Item();
+ }
+ if (geometry != null) {
+ item.geometry = geometry;
+ } else {
+ Log.v(TAG, "unloading geometry " + n);
+ if (item.geometry != null) {
+ // item.geometry.destroy();
+ item.geometry = null;
+ }
+ }
+ mCards.set(item, n, false);
+ mScript.invoke_setGeometry(n, item.geometry);
+ }
+
+ public void pauseRendering() {
+ // Used to update multiple states at once w/o redrawing for each.
+ mRS.contextBindRootScript(null);
+ }
+
+ public void resumeRendering() {
+ mRS.contextBindRootScript(mScript);
+ }
+
+ public void doMotion(float x, float y) {
+ mScript.invoke_doMotion(x,y);
+ }
+
+ public void doSelection(float x, float y) {
+ mScript.invoke_doSelection(x, y);
+ }
+
+ public void doStart(float x, float y) {
+ mScript.invoke_doStart(x, y);
+ }
+
+ public void doStop(float x, float y) {
+ mScript.invoke_doStop(x, y);
+ }
+
+ public void setSlotCount(int n) {
+ mScript.set_slotCount(n);
+ }
+}
diff --git a/carousel/java/com/android/ex/carousel/CarouselView.java b/carousel/java/com/android/ex/carousel/CarouselView.java
new file mode 100644
index 0000000..7588600
--- /dev/null
+++ b/carousel/java/com/android/ex/carousel/CarouselView.java
@@ -0,0 +1,274 @@
+/*
+ * 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.ex.carousel;
+
+import com.android.ex.carousel.CarouselRS.CarouselCallback;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.Bitmap.Config;
+import android.renderscript.FileA3D;
+import android.renderscript.Mesh;
+import android.renderscript.RSSurfaceView;
+import android.renderscript.RenderScriptGL;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+
+public abstract class CarouselView extends RSSurfaceView {
+ private static final boolean USE_DEPTH_BUFFER = true;
+ private final int DEFAULT_SLOT_COUNT = 10;
+ private final Bitmap DEFAULT_BITMAP = Bitmap.createBitmap(1, 1, Config.RGB_565);
+ private static final String TAG = "CarouselView";
+ private CarouselRS mRenderScript;
+ private RenderScriptGL mRS;
+ private Context mContext;
+ private boolean mTracking;
+ private Bitmap mDefaultBitmap;
+ private Bitmap mLoadingBitmap;
+ private Mesh mDefaultGeometry;
+ private Mesh mLoadingGeometry;
+ private int mCardCount = 0;
+ private int mVisibleSlots = 0;
+ private float mStartAngle;
+ private int mSlotCount = DEFAULT_SLOT_COUNT;
+
+ public static class Info {
+ public Info(int _resId) { resId = _resId; }
+ public int resId; // resource for renderscript resource (e.g. R.raw.carousel)
+ }
+
+ public abstract Info getRenderScriptInfo();
+
+ public CarouselView(Context context) {
+ this(context, null);
+ }
+
+ /**
+ * Constructor used when this widget is created from a layout file.
+ */
+ public CarouselView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mContext = context;
+ boolean useDepthBuffer = true;
+ ensureRenderScript();
+ // TODO: add parameters to layout
+ }
+
+ private void ensureRenderScript() {
+ mRS = createRenderScript(USE_DEPTH_BUFFER);
+ mRenderScript = new CarouselRS();
+ mRenderScript.init(mRS, getResources(), getRenderScriptInfo().resId);
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
+ super.surfaceChanged(holder, format, w, h);
+ //mRS.contextSetSurface(w, h, holder.getSurface());
+ mRenderScript.init(mRS, getResources(), getRenderScriptInfo().resId);
+ setSlotCount(mSlotCount);
+ createCards(mCardCount);
+ setVisibleSlots(mVisibleSlots);
+ setCallback(mCarouselCallback);
+ setDefaultBitmap(mDefaultBitmap);
+ setLoadingBitmap(mLoadingBitmap);
+ setDefaultGeometry(mDefaultGeometry);
+ setLoadingGeometry(mLoadingGeometry);
+ setStartAngle(mStartAngle);
+ }
+
+ /**
+ * Loads geometry from a resource id.
+ *
+ * @param resId
+ * @return the loaded mesh or null if it cannot be loaded
+ */
+ public Mesh loadGeometry(int resId) {
+ Resources res = mContext.getResources();
+ FileA3D model = FileA3D.createFromResource(mRS, res, resId);
+ FileA3D.IndexEntry entry = model.getIndexEntry(0);
+ if(entry == null || entry.getClassID() != FileA3D.ClassID.MESH) {
+ return null;
+ }
+ return (Mesh) entry.getObject();
+ }
+
+ /**
+ * Load A3D file from resource. If resId == 0, will clear geometry for this item.
+ * @param n
+ * @param resId
+ */
+ public void setGeometryForItem(int n, Mesh mesh) {
+ mRenderScript.setGeometry(n, mesh);
+ }
+
+ public void setSlotCount(int n) {
+ mSlotCount = n;
+ if (mRenderScript != null) {
+ mRenderScript.setSlotCount(n);
+ }
+ }
+
+ public void setVisibleSlots(int n) {
+ mVisibleSlots = n;
+ if (mRenderScript != null) {
+ mRenderScript.setVisibleSlots(n);
+ }
+ }
+
+ public void createCards(int n) {
+ mCardCount = n;
+ if (mRenderScript != null) {
+ mRenderScript.createCards(n);
+ }
+ }
+
+ public void setTextureForItem(int n, Bitmap bitmap) {
+ if (mRenderScript != null) {
+ Log.v(TAG, "setTextureForItem(" + n + ")");
+ mRenderScript.setTexture(n, bitmap);
+ Log.v(TAG, "done");
+ }
+ }
+
+ public void setDefaultBitmap(Bitmap bitmap) {
+ mDefaultBitmap = bitmap;
+ if (mRenderScript != null) {
+ mRenderScript.setDefaultBitmap(bitmap);
+ }
+ }
+
+ public void setLoadingBitmap(Bitmap bitmap) {
+ mLoadingBitmap = bitmap;
+ if (mRenderScript != null) {
+ mRenderScript.setLoadingBitmap(bitmap);
+ }
+ }
+
+ public void setDefaultGeometry(Mesh mesh) {
+ mDefaultGeometry = mesh;
+ if (mRenderScript != null) {
+ mRenderScript.setDefaultGeometry(mesh);
+ }
+ }
+
+ public void setLoadingGeometry(Mesh mesh) {
+ mLoadingGeometry = mesh;
+ if (mRenderScript != null) {
+ mRenderScript.setLoadingGeometry(mesh);
+ }
+ }
+
+ public void setCallback(CarouselCallback callback)
+ {
+ mCarouselCallback = callback;
+ if (mRenderScript != null) {
+ mRenderScript.setCallback(callback);
+ }
+ }
+
+ public void setStartAngle(float angle)
+ {
+ mStartAngle = angle;
+ if (mRenderScript != null) {
+ mRenderScript.setStartAngle(angle);
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if(mRS != null) {
+ mRS = null;
+ destroyRenderScript();
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ ensureRenderScript();
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ final int action = event.getAction();
+ final float x = event.getX();
+ final float y = event.getY();
+
+ if (mRenderScript == null) {
+ return true;
+ }
+
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mTracking = true;
+ mRenderScript.doStart(x, y);
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ if (mTracking) {
+ mRenderScript.doMotion(x, y);
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ mRenderScript.doStop(x, y);
+ mTracking = false;
+ break;
+ }
+
+ return true;
+ }
+
+ private final CarouselCallback DEBUG_CALLBACK = new CarouselCallback() {
+ public void onAnimationStarted() {
+ Log.v(TAG, "onAnimationStarted()");
+ }
+
+ public void onAnimationFinished() {
+ Log.v(TAG, "onAnimationFinished()");
+ }
+
+ public void onCardSelected(int n) {
+ Log.v(TAG, "onCardSelected(" + n + ")");
+ }
+
+ public void onRequestGeometry(int n) {
+ Log.v(TAG, "onRequestGeometry(" + n + ")");
+ }
+
+ public void onInvalidateGeometry(int n) {
+ Log.v(TAG, "onInvalidateGeometry(" + n + ")");
+ }
+
+ public void onRequestTexture(final int n) {
+ Log.v(TAG, "onRequestTexture(" + n + ")");
+ }
+
+ public void onInvalidateTexture(int n) {
+ Log.v(TAG, "onInvalidateTexture(" + n + ")");
+ }
+
+ };
+
+ private CarouselCallback mCarouselCallback = DEBUG_CALLBACK;
+}
diff --git a/carousel/java/com/android/ex/carousel/carousel.rs b/carousel/java/com/android/ex/carousel/carousel.rs
new file mode 100644
index 0000000..e7d3550
--- /dev/null
+++ b/carousel/java/com/android/ex/carousel/carousel.rs
@@ -0,0 +1,788 @@
+/*
+ * 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.
+ */
+
+#pragma version(1)
+#pragma rs java_package_name(com.android.ex.carousel);
+#pragma rs set_reflect_license()
+
+#include "rs_graphics.rsh"
+
+typedef struct __attribute__((aligned(4))) Card {
+ rs_allocation texture;
+ rs_mesh geometry;
+ //rs_matrix4x4 matrix; // custom transform for this card/geometry
+ int textureState; // whether or not the texture is loaded.
+ int geometryState; // whether or not geometry is loaded
+ int visible; // not bool because of packing bug?
+} Card_t;
+
+typedef struct Ray_s {
+ float3 position;
+ float3 direction;
+} Ray;
+
+typedef struct PerspectiveCamera_s {
+ float3 from;
+ float3 at;
+ float3 up;
+ float fov;
+ float aspect;
+ float near;
+ float far;
+} PerspectiveCamera;
+
+// Request states. Used for loading 3D object properties from the Java client.
+// Typical properties: texture, geometry and matrices.
+enum {
+ STATE_INVALID = 0, // item hasn't been loaded
+ STATE_LOADING, // we've requested an item but are waiting for it to load
+ STATE_LOADED // item was delivered
+};
+
+// Client messages *** THIS LIST MUST MATCH THOSE IN CarouselRS.java. ***
+static const int CMD_CARD_SELECTED = 100;
+static const int CMD_REQUEST_TEXTURE = 200;
+static const int CMD_INVALIDATE_TEXTURE = 210;
+static const int CMD_REQUEST_GEOMETRY = 300;
+static const int CMD_INVALIDATE_GEOMETRY = 310;
+static const int CMD_ANIMATION_STARTED = 400;
+static const int CMD_ANIMATION_FINISHED = 500;
+static const int CMD_PING = 600;
+
+// Constants
+static const int ANIMATION_SCALE_TIME = 200; // Time it takes to animate selected card, in ms
+static const float3 SELECTED_SCALE_FACTOR = { 0.2f, 0.2f, 0.2f }; // increase by this %
+
+// Debug flags
+bool debugCamera = false; // dumps ray/camera coordinate stuff
+bool debugPicking = false; // renders picking area on top of geometry
+
+// Exported variables. These will be reflected to Java set_* variables.
+Card_t *cards; // array of cards to draw
+float startAngle; // position of initial card, in radians
+int slotCount; // number of positions where a card can be
+int cardCount; // number of cards in stack
+int visibleSlotCount; // number of visible slots (for culling)
+float radius; // carousel radius. Cards will be centered on a circle with this radius
+float cardRotation; // rotation of card in XY plane relative to Z=1
+rs_program_store programStore;
+rs_program_fragment fragmentProgram;
+rs_program_vertex vertexProgram;
+rs_program_raster rasterProgram;
+rs_allocation defaultTexture; // shown when no other texture is assigned
+rs_allocation loadingTexture; // progress texture (shown when app is fetching the texture)
+rs_mesh defaultGeometry; // shown when no geometry is loaded
+rs_mesh loadingGeometry; // shown when geometry is loading
+rs_matrix4x4 projectionMatrix;
+rs_matrix4x4 modelviewMatrix;
+
+#pragma rs export_var(radius, cards, slotCount, visibleSlotCount, cardRotation)
+#pragma rs export_var(programStore, fragmentProgram, vertexProgram, rasterProgram)
+#pragma rs export_var(startAngle, defaultTexture, loadingTexture, defaultGeometry, loadingGeometry)
+#pragma rs export_func(createCards, lookAt, doStart, doStop, doMotion, doSelection, setTexture)
+#pragma rs export_func(setGeometry, debugCamera, debugPicking)
+
+// Local variables
+static float bias; // rotation bias, in radians. Used for animation and dragging.
+static bool updateCamera; // force a recompute of projection and lookat matrices
+static bool initialized;
+static float3 backgroundColor = { 0.0f, 0.0f, 0.0f };
+static const float FLT_MAX = 1.0e37;
+static int currentSelection = -1;
+static int64_t touchTime = -1; // time of first touch (see doStart())
+static float touchBias = 0.0f; // bias on first touch
+
+// Default geometry when card.geometry is not set.
+static const float3 cardVertices[4] = {
+ { -1.0, -1.0, 0.0 },
+ { 1.0, -1.0, 0.0 },
+ { 1.0, 1.0, 0.0 },
+ {-1.0, 1.0, 0.0 }
+};
+
+// Default camera
+static PerspectiveCamera camera = {
+ {2,2,2}, // from
+ {0,0,0}, // at
+ {0,1,0}, // up
+ 25.0f, // field of view
+ 1.0f, // aspect
+ 0.1f, // near
+ 100.0f // far
+};
+
+// Forward references
+static int intersectGeometry(Ray* ray, float *bestTime);
+static bool makeRayForPixelAt(Ray* ray, float x, float y);
+static float deltaTimeInSeconds(int64_t current);
+
+void init() {
+ // initializers currently have a problem when the variables are exported, so initialize
+ // globals here.
+ rsDebug("Renderscript: init()", 0);
+ startAngle = 0.0f;
+ slotCount = 10;
+ visibleSlotCount = 1;
+ bias = 0.0f;
+ radius = 1.0f;
+ cardRotation = 0.0f;
+ updateCamera = true;
+ initialized = false;
+}
+
+static void updateAllocationVars()
+{
+ // Cards
+ rs_allocation cardAlloc = rsGetAllocation(cards);
+ // TODO: use new rsIsObject()
+ cardCount = cardAlloc.p != 0 ? rsAllocationGetDimX(cardAlloc) : 0;
+}
+
+void createCards(int n)
+{
+ rsDebug("CreateCards: ", n);
+ initialized = false;
+ updateAllocationVars();
+}
+
+// Return angle for position p. Typically p will be an integer position, but can be fractional.
+static float cardPosition(float p)
+{
+ return startAngle + bias + 2.0f * M_PI * p / slotCount;
+}
+
+// Return slot for a card in position p. Typically p will be an integer slot, but can be fractional.
+static float slotPosition(float p)
+{
+ return startAngle + 2.0f * M_PI * p / slotCount;
+}
+
+// Return the lowest slot number for a given angular position.
+static int cardIndex(float angle)
+{
+ return floor(angle - startAngle - bias) * slotCount / (2.0f * M_PI);
+}
+
+// Set basic camera properties:
+// from - position of the camera in x,y,z
+// at - target we're looking at - used to compute view direction
+// up - a normalized vector indicating up (typically { 0, 1, 0})
+//
+// NOTE: the view direction and up vector cannot be parallel/antiparallel with each other
+void lookAt(float fromX, float fromY, float fromZ,
+ float atX, float atY, float atZ,
+ float upX, float upY, float upZ)
+{
+ camera.from.x = fromX;
+ camera.from.y = fromY;
+ camera.from.z = fromZ;
+ camera.at.x = atX;
+ camera.at.y = atY;
+ camera.at.z = atZ;
+ camera.up.x = upX;
+ camera.up.y = upY;
+ camera.up.z = upZ;
+ updateCamera = true;
+}
+
+// Load a projection matrix for the given parameters. This is equivalent to gluPerspective()
+static void loadPerspectiveMatrix(rs_matrix4x4* matrix, float fovy, float aspect, float near, float far)
+{
+ rsMatrixLoadIdentity(matrix);
+ float top = near * tan((float) (fovy * M_PI / 360.0f));
+ float bottom = -top;
+ float left = bottom * aspect;
+ float right = top * aspect;
+ rsMatrixLoadFrustum(matrix, left, right, bottom, top, near, far);
+}
+
+// Construct a matrix based on eye point, center and up direction. Based on the
+// man page for gluLookat(). Up must be normalized.
+static void loadLookatMatrix(rs_matrix4x4* matrix, float3 eye, float3 center, float3 up)
+{
+ float3 f = normalize(center - eye);
+ float3 s = normalize(cross(f, up));
+ float3 u = cross(s, f);
+ float m[16];
+ m[0] = s.x;
+ m[4] = s.y;
+ m[8] = s.z;
+ m[12] = 0.0f;
+ m[1] = u.x;
+ m[5] = u.y;
+ m[9] = u.z;
+ m[13] = 0.0f;
+ m[2] = -f.x;
+ m[6] = -f.y;
+ m[10] = -f.z;
+ m[14] = 0.0f;
+ m[3] = m[7] = m[11] = 0.0f;
+ m[15] = 1.0f;
+ rsMatrixLoad(matrix, m);
+ rsMatrixTranslate(matrix, -eye.x, -eye.y, -eye.z);
+}
+
+void setTexture(int n, rs_allocation texture)
+{
+ cards[n].texture = texture;
+ if (cards[n].texture.p != 0)
+ cards[n].textureState = STATE_LOADED;
+ else
+ cards[n].textureState = STATE_INVALID;
+}
+
+void setGeometry(int n, rs_mesh geometry)
+{
+ cards[n].geometry = geometry;
+ if (cards[n].geometry.p != 0)
+ cards[n].geometryState = STATE_LOADED;
+ else
+ cards[n].geometryState = STATE_INVALID;
+}
+
+static float3 getAnimatedScaleForSelected()
+{
+ int64_t dt = (rsUptimeMillis() - touchTime);
+ float fraction = (dt < ANIMATION_SCALE_TIME) ? (float) dt / ANIMATION_SCALE_TIME : 1.0f;
+ const float3 one = { 1.0f, 1.0f, 1.0f };
+ return one + fraction * SELECTED_SCALE_FACTOR;
+}
+
+static void getMatrixForCard(rs_matrix4x4* matrix, int i)
+{
+ float theta = cardPosition(i);
+ rsMatrixRotate(matrix, degrees(theta), 0, 1, 0);
+ rsMatrixTranslate(matrix, radius, 0, 0);
+ rsMatrixRotate(matrix, degrees(-theta + cardRotation), 0, 1, 0);
+ if (i == currentSelection) {
+ float3 scale = getAnimatedScaleForSelected();
+ rsMatrixScale(matrix, scale.x, scale.y, scale.z);
+ }
+ // TODO: apply custom matrix for cards[i].geometry
+}
+
+static void drawCards()
+{
+ float depth = 1.0f;
+ for (int i = 0; i < cardCount; i++) {
+ if (cards[i].visible) {
+ // Bind texture
+ if (cards[i].textureState == STATE_LOADED) {
+ rsgBindTexture(fragmentProgram, 0, cards[i].texture);
+ } else if (cards[i].textureState == STATE_LOADING) {
+ rsgBindTexture(fragmentProgram, 0, loadingTexture);
+ } else {
+ rsgBindTexture(fragmentProgram, 0, defaultTexture);
+ }
+
+ // Draw geometry
+ rs_matrix4x4 matrix = modelviewMatrix;
+ getMatrixForCard(&matrix, i);
+ rsgProgramVertexLoadModelMatrix(&matrix);
+ if (cards[i].geometryState == STATE_LOADED && cards[i].geometry.p != 0) {
+ rsgDrawMesh(cards[i].geometry);
+ } else if (cards[i].geometryState == STATE_LOADING && loadingGeometry.p != 0) {
+ rsgDrawMesh(loadingGeometry);
+ } else if (defaultGeometry.p != 0) {
+ rsgDrawMesh(defaultGeometry);
+ } else {
+ // Draw place-holder geometry
+ rsgDrawQuad(
+ cardVertices[0].x, cardVertices[0].y, cardVertices[0].z,
+ cardVertices[1].x, cardVertices[1].y, cardVertices[1].z,
+ cardVertices[2].x, cardVertices[2].y, cardVertices[2].z,
+ cardVertices[3].x, cardVertices[3].y, cardVertices[3].z);
+ }
+ }
+ }
+}
+
+static void updateCameraMatrix(float width, float height)
+{
+ float aspect = width / height;
+ if (aspect != camera.aspect || updateCamera) {
+ camera.aspect = aspect;
+ loadPerspectiveMatrix(&projectionMatrix, camera.fov, camera.aspect, camera.near, camera.far);
+ rsgProgramVertexLoadProjectionMatrix(&projectionMatrix);
+
+ loadLookatMatrix(&modelviewMatrix, camera.from, camera.at, camera.up);
+ rsgProgramVertexLoadModelMatrix(&modelviewMatrix);
+ updateCamera = false;
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Behavior/Physics
+////////////////////////////////////////////////////////////////////////////////////////////////////
+static float velocity = 0.0f; // angular velocity in radians/s
+static bool isDragging;
+static int64_t lastTime = 0L; // keep track of how much time has passed between frames
+static float2 lastPosition;
+static bool animating = false;
+static float velocityThreshold = 0.1f * M_PI / 180.0f;
+static float velocityTracker;
+static int velocityTrackerCount;
+static float mass = 5.0f; // kg
+
+static const float G = 9.80f; // gravity constant, in m/s
+static const float springConstant = 0.0f;
+static const float frictionCoeff = 10.0f;
+static const float dragFactor = 0.25f;
+
+static float dragFunction(float x, float y)
+{
+ return dragFactor * ((x - lastPosition.x) / rsgGetWidth()) * M_PI;
+}
+
+static float deltaTimeInSeconds(int64_t current)
+{
+ return (lastTime > 0L) ? (float) (current - lastTime) / 1000.0f : 0.0f;
+}
+
+int doSelection(float x, float y)
+{
+ Ray ray;
+ if (makeRayForPixelAt(&ray, x, y)) {
+ float bestTime = FLT_MAX;
+ return intersectGeometry(&ray, &bestTime);
+ }
+ return -1;
+}
+
+void doStart(float x, float y)
+{
+ lastPosition.x = x;
+ lastPosition.y = y;
+ velocity = 0.0f;
+ if (animating) {
+ rsSendToClient(CMD_ANIMATION_FINISHED);
+ animating = false;
+ }
+ velocityTracker = 0.0f;
+ velocityTrackerCount = 0;
+ touchTime = rsUptimeMillis();
+ touchBias = bias;
+ currentSelection = doSelection(x, y);
+}
+
+
+void doStop(float x, float y)
+{
+ int64_t currentTime = rsUptimeMillis();
+ updateAllocationVars();
+ if (currentSelection != -1 && (currentTime - touchTime) < ANIMATION_SCALE_TIME) {
+ rsDebug("HIT!", currentSelection);
+ int data[1];
+ data[0] = currentSelection;
+ rsSendToClientBlocking(CMD_CARD_SELECTED, data, sizeof(data));
+ } else {
+ velocity = velocityTrackerCount > 0 ?
+ (velocityTracker / velocityTrackerCount) : 0.0f; // avg velocity
+ if (fabs(velocity) > velocityThreshold) {
+ animating = true;
+ rsSendToClient(CMD_ANIMATION_STARTED);
+ }
+ }
+ currentSelection = -1;
+ lastTime = rsUptimeMillis();
+}
+
+void doMotion(float x, float y)
+{
+ int64_t currentTime = rsUptimeMillis();
+ float deltaOmega = dragFunction(x, y);
+ bias += deltaOmega;
+ lastPosition.x = x;
+ lastPosition.y = y;
+ float dt = deltaTimeInSeconds(currentTime);
+ if (dt > 0.0f) {
+ float v = deltaOmega / dt;
+ //if ((velocityTracker > 0.0f) == (v > 0.0f)) {
+ velocityTracker += v;
+ velocityTrackerCount++;
+ //} else {
+ // velocityTracker = v;
+ // velocityTrackerCount = 1;
+ //}
+ }
+
+ // Drop current selection if user drags position +- a partial slot
+ if (currentSelection != -1) {
+ const float slotMargin = 0.5f * (2.0f * M_PI / slotCount);
+ if (fabs(touchBias - bias) > slotMargin) {
+ currentSelection = -1;
+ }
+ }
+ lastTime = currentTime;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// Hit detection using ray casting.
+////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static bool
+rayTriangleIntersect(Ray* ray, float3 p0, float3 p1, float3 p2, float *tout)
+{
+ static const float tmin = 0.0f;
+
+ float3 e1 = p1 - p0;
+ float3 e2 = p2 - p0;
+ float3 s1 = cross(ray->direction, e2);
+
+ float div = dot(s1, e1);
+ if (div == 0.0f) return false; // ray is parallel to plane.
+
+ float3 d = ray->position - p0;
+ float invDiv = 1.0f / div;
+
+ float u = dot(d, s1) * invDiv;
+ if (u < 0.0f || u > 1.0f) return false;
+
+ float3 s2 = cross(d, e1);
+ float v = dot(ray->direction, s2) * invDiv;
+ if ( v < 0.0f || (u+v) > 1.0f) return false;
+
+ float t = dot(e2, s2) * invDiv;
+ if (t < tmin || t > *tout)
+ return false;
+ *tout = t;
+ return true;
+}
+
+// Creates a ray for an Android pixel coordinate.
+// Note that the Y coordinate is opposite of GL rendering coordinates.
+static bool makeRayForPixelAt(Ray* ray, float x, float y)
+{
+ if (debugCamera) {
+ rsDebug("------ makeRay() -------", 0);
+ rsDebug("Camera.from:", camera.from);
+ rsDebug("Camera.at:", camera.at);
+ rsDebug("Camera.dir:", normalize(camera.at - camera.from));
+ }
+
+ // Vector math. This has the potential to be much faster.
+ // TODO: pre-compute lowerLeftRay, du, dv to eliminate most of this math.
+ if (true) {
+ const float u = x / rsgGetWidth();
+ const float v = 1.0f - (y / rsgGetHeight());
+ const float aspect = (float) rsgGetWidth() / rsgGetHeight();
+ const float tanfov2 = 2.0f * tan(radians(camera.fov / 2.0f));
+ float3 dir = normalize(camera.at - camera.from);
+ float3 du = tanfov2 * normalize(cross(dir, camera.up));
+ float3 dv = tanfov2 * normalize(cross(du, dir));
+ du *= aspect;
+ float3 lowerLeftRay = dir - (0.5f * du) - (0.5f * dv);
+ const float3 rayPoint = camera.from;
+ const float3 rayDir = normalize(lowerLeftRay + u*du + v*dv);
+ if (debugCamera) {
+ rsDebug("Ray direction (vector math) = ", rayDir);
+ }
+
+ ray->position = rayPoint;
+ ray->direction = rayDir;
+ }
+
+ // Matrix math. This is more generic if we allow setting model view and projection matrices
+ // directly
+ else {
+ rs_matrix4x4 pm = modelviewMatrix;
+ rsMatrixLoadMultiply(&pm, &projectionMatrix, &modelviewMatrix);
+ if (!rsMatrixInverse(&pm)) {
+ rsDebug("ERROR: SINGULAR PM MATRIX", 0);
+ return false;
+ }
+ const float width = rsgGetWidth();
+ const float height = rsgGetHeight();
+ const float winx = 2.0f * x / width - 1.0f;
+ const float winy = 2.0f * y / height - 1.0f;
+
+ float4 eye = { 0.0f, 0.0f, 0.0f, 1.0f };
+ float4 at = { winx, winy, 1.0f, 1.0f };
+
+ eye = rsMatrixMultiply(&pm, eye);
+ eye *= 1.0f / eye.w;
+
+ at = rsMatrixMultiply(&pm, at);
+ at *= 1.0f / at.w;
+
+ const float3 rayPoint = { eye.x, eye.y, eye.z };
+ const float3 atPoint = { at.x, at.y, at.z };
+ const float3 rayDir = normalize(atPoint - rayPoint);
+ if (debugCamera) {
+ rsDebug("winx: ", winx);
+ rsDebug("winy: ", winy);
+ rsDebug("Ray position (transformed) = ", eye);
+ rsDebug("Ray direction (transformed) = ", rayDir);
+ }
+ ray->position = rayPoint;
+ ray->direction = rayDir;
+ }
+
+ return true;
+}
+
+static int intersectGeometry(Ray* ray, float *bestTime)
+{
+ int hit = -1;
+ for (int id = 0; id < cardCount; id++) {
+ if (cards[id].visible) {
+ rs_matrix4x4 matrix;
+ float3 p[4];
+
+ // Transform card vertices to world space
+ rsMatrixLoadIdentity(&matrix);
+ getMatrixForCard(&matrix, id);
+ for (int vertex = 0; vertex < 4; vertex++) {
+ float4 tmp = rsMatrixMultiply(&matrix, cardVertices[vertex]);
+ if (tmp.w != 0.0f) {
+ p[vertex].x = tmp.x;
+ p[vertex].y = tmp.y;
+ p[vertex].z = tmp.z;
+ p[vertex] *= 1.0f / tmp.w;
+ } else {
+ rsDebug("Bad w coord: ", tmp);
+ }
+ }
+
+ // Intersect card geometry
+ if (rayTriangleIntersect(ray, p[0], p[1], p[2], bestTime)
+ || rayTriangleIntersect(ray, p[2], p[3], p[0], bestTime)) {
+ hit = id;
+ }
+ }
+ }
+ return hit;
+}
+
+// This method computes the position of all the cards by updating bias based on a
+// simple physics model.
+// If the cards are still in motion, returns true.
+static bool updateNextPosition(int64_t currentTime)
+{
+ if (animating) {
+ float dt = deltaTimeInSeconds(currentTime);
+ if (dt <= 0.0f)
+ return animating;
+ const float minStepTime = 1.0f / 300.0f; // ~5 steps per frame
+ const int N = (dt > minStepTime) ? (1 + round(dt / minStepTime)) : 1;
+ dt /= N;
+ for (int i = 0; i < N; i++) {
+ // Force friction - always opposes motion
+ const float Ff = -frictionCoeff * velocity;
+
+ // Restoring force to match cards with slots
+ const float theta = startAngle + bias;
+ const float dtheta = 2.0f * M_PI / slotCount;
+ const float position = theta / dtheta;
+ const float fraction = position - floor(position); // fractional position between slots
+ float x;
+ if (fraction > 0.5f) {
+ x = - (1.0f - fraction);
+ } else {
+ x = fraction;
+ }
+ const float Fr = - springConstant * x;
+
+ // compute velocity
+ const float momentum = mass * velocity + (Ff + Fr)*dt;
+ velocity = momentum / mass;
+ bias += velocity * dt;
+ }
+
+ // TODO: Add animation to smoothly move back to slots. Currently snaps to location.
+ if (cardCount <= visibleSlotCount) {
+ // TODO: this aligns the cards to the first slot (theta = startAngle) when there aren't
+ // enough visible cards. It should be generalized to allow alignment to front,
+ // middle or back of the stack.
+ if (cardPosition(0) != slotPosition(0)) {
+ bias = 0.0f;
+ }
+ } else {
+ if (cardPosition(cardCount) < 0.0f) {
+ bias = -slotPosition(cardCount);
+ } else if (cardPosition(0) > slotPosition(0)) {
+ bias = 0.0f;
+ }
+ }
+
+ animating = fabs(velocity) > velocityThreshold;
+ if (!animating) {
+ const float dtheta = 2.0f * M_PI / slotCount;
+ bias = round((startAngle + bias) / dtheta) * dtheta - startAngle;
+ rsSendToClient(CMD_ANIMATION_FINISHED);
+ }
+ }
+ lastTime = currentTime;
+
+ return animating;
+}
+
+// Cull cards based on visibility and visibleSlotCount.
+// If visibleSlotCount is > 0, then only show those slots and cull the rest.
+// Otherwise, it should cull based on bounds of geometry.
+static int cullCards()
+{
+ const float thetaFirst = slotPosition(-1); // -1 keeps the card in front around a bit longer
+ const float thetaLast = slotPosition(visibleSlotCount);
+
+ int count = 0;
+ for (int i = 0; i < cardCount; i++) {
+ if (visibleSlotCount > 0) {
+ // If visibleSlotCount is specified, then only show up to visibleSlotCount cards.
+ float p = cardPosition(i);
+ if (p >= thetaFirst && p < thetaLast) {
+ cards[i].visible = true;
+ count++;
+ } else {
+ cards[i].visible = false;
+ }
+ } else {
+ // Cull the rest of the cards using bounding box of geometry.
+ // TODO
+ cards[i].visible = true;
+ count++;
+ }
+ }
+ return count;
+}
+
+// Request texture/geometry for items that have come into view
+// or doesn't have a texture yet.
+static void updateCardResources()
+{
+ for (int i = 0; i < cardCount; i++) {
+ int data[1];
+ if (cards[i].visible) {
+ // request texture from client if not loaded
+ if (cards[i].textureState == STATE_INVALID) {
+ data[0] = i;
+ bool enqueued = rsSendToClient(CMD_REQUEST_TEXTURE, data, sizeof(data));
+ if (enqueued) {
+ cards[i].textureState = STATE_LOADING;
+ } else {
+ rsDebug("Couldn't send CMD_REQUEST_TEXTURE", 0);
+ }
+ }
+ // request geometry from client if not loaded
+ if (cards[i].geometryState == STATE_INVALID) {
+ data[0] = i;
+ bool enqueued = rsSendToClient(CMD_REQUEST_GEOMETRY, data, sizeof(data));
+ if (enqueued) {
+ cards[i].geometryState = STATE_LOADING;
+ } else {
+ rsDebug("Couldn't send CMD_REQUEST_GEOMETRY", 0);
+ }
+ }
+ } else {
+ // ask the host to remove the texture
+ if (cards[i].textureState == STATE_LOADED) {
+ data[0] = i;
+ bool enqueued = rsSendToClient(CMD_INVALIDATE_TEXTURE, data, sizeof(data));
+ if (enqueued) {
+ cards[i].textureState = STATE_INVALID;
+ } else {
+ rsDebug("Couldn't send CMD_INVALIDATE_TEXTURE", 0);
+ }
+ }
+ // ask the host to remove the geometry
+ if (cards[i].geometryState == STATE_LOADED) {
+ data[0] = i;
+ bool enqueued = rsSendToClient(CMD_INVALIDATE_GEOMETRY, data, sizeof(data));
+ if (enqueued) {
+ cards[i].geometryState = STATE_INVALID;
+ } else {
+ rsDebug("Couldn't send CMD_INVALIDATE_GEOMETRY", 0);
+ }
+ }
+
+ }
+ }
+}
+
+// Places dots on geometry to visually inspect that objects can be seen by rays.
+// NOTE: the color of the dot is somewhat random, as it depends on texture of previously-rendered
+// card.
+static void renderWithRays()
+{
+ const float w = rsgGetWidth();
+ const float h = rsgGetHeight();
+ const int skip = 8;
+ color(1.0f, 0.0f, 0.0f, 1.0f);
+ for (int j = 0; j < (int) h; j+=skip) {
+ float posY = (float) j;
+ for (int i = 0; i < (int) w; i+=skip) {
+ float posX = (float) i;
+ Ray ray;
+ if (makeRayForPixelAt(&ray, posX, posY)) {
+ float bestTime = FLT_MAX;
+ if (intersectGeometry(&ray, &bestTime) != -1) {
+ rsgDrawSpriteScreenspace(posX, h - posY - 1, 0.0f, 2.0f, 2.0f);
+ }
+ }
+ }
+ }
+}
+
+int root() {
+ int64_t currentTime = rsUptimeMillis();
+
+ rsgClearDepth(1.0f);
+ rsgBindProgramVertex(vertexProgram);
+ rsgBindProgramFragment(fragmentProgram);
+ rsgBindProgramStore(programStore);
+ rsgBindProgramRaster(rasterProgram);
+
+ updateAllocationVars();
+
+ if (!initialized) {
+ for (int i = 0; i < cardCount; i++)
+ cards[i].textureState = STATE_INVALID;
+ initialized = true;
+ }
+
+ if (false) { // for debugging - flash the screen so we know we're still rendering
+ static bool toggle;
+ if (toggle)
+ rsgClearColor(backgroundColor.x, backgroundColor.y, backgroundColor.z, 1.0);
+ else
+ rsgClearColor(1.0f, 0.0f, 0.0f, 1.f);
+ toggle = !toggle;
+ } else {
+ rsgClearColor(backgroundColor.x, backgroundColor.y, backgroundColor.z, 1.0);
+ }
+
+ updateCameraMatrix(rsgGetWidth(), rsgGetHeight());
+
+ const bool timeExpired = (currentTime - touchTime) > ANIMATION_SCALE_TIME;
+ if (timeExpired) {
+ //currentSelection = -1;
+ }
+ bool stillAnimating = updateNextPosition(currentTime) || !timeExpired;
+
+ cullCards();
+
+ updateCardResources();
+
+ drawCards();
+
+ if (debugPicking) {
+ renderWithRays();
+ }
+
+ //rsSendToClient(CMD_PING);
+
+ return stillAnimating ? 1 : 0;
+}
diff --git a/carousel/test/Android.mk b/carousel/test/Android.mk
new file mode 100644
index 0000000..e00c83c
--- /dev/null
+++ b/carousel/test/Android.mk
@@ -0,0 +1,29 @@
+#
+# Copyright (C) 2009 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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files) \
+ $(call all-renderscript-files-under, src) \
+ ../../../../frameworks/ex/carousel/java/com/android/ex/carousel/carousel.rs
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-common-carousel
+
+LOCAL_PACKAGE_NAME := CarouselWidgetTests
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
diff --git a/carousel/test/AndroidManifest.xml b/carousel/test/AndroidManifest.xml
new file mode 100644
index 0000000..78b819d
--- /dev/null
+++ b/carousel/test/AndroidManifest.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Copyright (C) 2009 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.carouseltest">
+
+ <uses-permission android:name="android.permission.GET_TASKS" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <application>
+
+ <activity
+ android:name="MusicDemoActivity"
+ android:label="@string/music_demo_activity_label"
+ android:theme="@android:style/Theme.NoTitleBar"
+ android:configChanges="orientation">
+
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+
+ </activity>
+
+ <activity
+ android:name="CarouselTestActivity"
+ android:label="@string/carousel_test_activity_label"
+ android:theme="@android:style/Theme.NoTitleBar"
+ android:configChanges="orientation">
+
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+
+ </activity>
+
+ <activity
+ android:name="TaskSwitcherActivity"
+ android:label="@string/task_switcher_activity_label"
+ android:theme="@android:style/Theme.NoTitleBar"
+ android:configChanges="orientation">
+
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+
+ </activity>
+
+ </application>
+</manifest>
diff --git a/carousel/test/res/anim/zoom_enter.xml b/carousel/test/res/anim/zoom_enter.xml
new file mode 100644
index 0000000..f037208
--- /dev/null
+++ b/carousel/test/res/anim/zoom_enter.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2009, 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.
+*/
+-->
+
+<!-- Special window zoom animation: this is the element that enters the screen,
+ it starts at 200% and scales down. Goes with zoom_exit.xml. -->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:anim/decelerate_interpolator">
+ <scale android:fromXScale="0.0" android:toXScale="1.0"
+ android:fromYScale="0.0" android:toYScale="1.0"
+ android:pivotX="50%p" android:pivotY="50%p"
+ android:duration="500" />
+ <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
+ android:duration="500"/>
+</set>
diff --git a/carousel/test/res/anim/zoom_exit.xml b/carousel/test/res/anim/zoom_exit.xml
new file mode 100644
index 0000000..207f570
--- /dev/null
+++ b/carousel/test/res/anim/zoom_exit.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2009, 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.
+*/
+-->
+
+<!-- Special window zoom animation: this is the element that exits the
+ screen, it is forced above the entering element and starts at its
+ normal size (filling the screen) and scales down while fading out.
+ This goes with zoom_enter.xml. -->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:anim/decelerate_interpolator"
+ android:zAdjustment="top">
+ <scale android:fromXScale="1.0" android:toXScale="4.0"
+ android:fromYScale="1.0" android:toYScale="4.0"
+ android:pivotX="50%p" android:pivotY="50%p"
+ android:duration="500" />
+ <alpha android:fromAlpha="1.0" android:toAlpha="0"
+ android:duration="500"/>
+</set>
diff --git a/carousel/test/res/drawable/blank_album.png b/carousel/test/res/drawable/blank_album.png
new file mode 100644
index 0000000..76331d5
--- /dev/null
+++ b/carousel/test/res/drawable/blank_album.png
Binary files differ
diff --git a/carousel/test/res/drawable/emo_im_angel.png b/carousel/test/res/drawable/emo_im_angel.png
new file mode 100644
index 0000000..10742a6
--- /dev/null
+++ b/carousel/test/res/drawable/emo_im_angel.png
Binary files differ
diff --git a/carousel/test/res/drawable/emo_im_cool.png b/carousel/test/res/drawable/emo_im_cool.png
new file mode 100644
index 0000000..e3c8654
--- /dev/null
+++ b/carousel/test/res/drawable/emo_im_cool.png
Binary files differ
diff --git a/carousel/test/res/drawable/emo_im_crying.png b/carousel/test/res/drawable/emo_im_crying.png
new file mode 100644
index 0000000..b23791c
--- /dev/null
+++ b/carousel/test/res/drawable/emo_im_crying.png
Binary files differ
diff --git a/carousel/test/res/drawable/emo_im_foot_in_mouth.png b/carousel/test/res/drawable/emo_im_foot_in_mouth.png
new file mode 100644
index 0000000..050b7be
--- /dev/null
+++ b/carousel/test/res/drawable/emo_im_foot_in_mouth.png
Binary files differ
diff --git a/carousel/test/res/drawable/emo_im_happy.png b/carousel/test/res/drawable/emo_im_happy.png
new file mode 100644
index 0000000..69e3bed
--- /dev/null
+++ b/carousel/test/res/drawable/emo_im_happy.png
Binary files differ
diff --git a/carousel/test/res/drawable/emo_im_kissing.png b/carousel/test/res/drawable/emo_im_kissing.png
new file mode 100644
index 0000000..0cca68e
--- /dev/null
+++ b/carousel/test/res/drawable/emo_im_kissing.png
Binary files differ
diff --git a/carousel/test/res/drawable/emo_im_laughing.png b/carousel/test/res/drawable/emo_im_laughing.png
new file mode 100644
index 0000000..8406ad0
--- /dev/null
+++ b/carousel/test/res/drawable/emo_im_laughing.png
Binary files differ
diff --git a/carousel/test/res/drawable/emo_im_lips_are_sealed.png b/carousel/test/res/drawable/emo_im_lips_are_sealed.png
new file mode 100644
index 0000000..222f175
--- /dev/null
+++ b/carousel/test/res/drawable/emo_im_lips_are_sealed.png
Binary files differ
diff --git a/carousel/test/res/drawable/emo_im_money_mouth.png b/carousel/test/res/drawable/emo_im_money_mouth.png
new file mode 100644
index 0000000..d711bfb
--- /dev/null
+++ b/carousel/test/res/drawable/emo_im_money_mouth.png
Binary files differ
diff --git a/carousel/test/res/drawable/emo_im_sad.png b/carousel/test/res/drawable/emo_im_sad.png
new file mode 100644
index 0000000..40017f1
--- /dev/null
+++ b/carousel/test/res/drawable/emo_im_sad.png
Binary files differ
diff --git a/carousel/test/res/drawable/emo_im_surprised.png b/carousel/test/res/drawable/emo_im_surprised.png
new file mode 100644
index 0000000..4b2af7a
--- /dev/null
+++ b/carousel/test/res/drawable/emo_im_surprised.png
Binary files differ
diff --git a/carousel/test/res/drawable/emo_im_tongue_sticking_out.png b/carousel/test/res/drawable/emo_im_tongue_sticking_out.png
new file mode 100644
index 0000000..42ac80d
--- /dev/null
+++ b/carousel/test/res/drawable/emo_im_tongue_sticking_out.png
Binary files differ
diff --git a/carousel/test/res/drawable/emo_im_undecided.png b/carousel/test/res/drawable/emo_im_undecided.png
new file mode 100644
index 0000000..2cf5bd2
--- /dev/null
+++ b/carousel/test/res/drawable/emo_im_undecided.png
Binary files differ
diff --git a/carousel/test/res/drawable/emo_im_winking.png b/carousel/test/res/drawable/emo_im_winking.png
new file mode 100644
index 0000000..a3a0876
--- /dev/null
+++ b/carousel/test/res/drawable/emo_im_winking.png
Binary files differ
diff --git a/carousel/test/res/drawable/emo_im_wtf.png b/carousel/test/res/drawable/emo_im_wtf.png
new file mode 100644
index 0000000..86c4bda
--- /dev/null
+++ b/carousel/test/res/drawable/emo_im_wtf.png
Binary files differ
diff --git a/carousel/test/res/drawable/emo_im_yelling.png b/carousel/test/res/drawable/emo_im_yelling.png
new file mode 100644
index 0000000..cfd991a
--- /dev/null
+++ b/carousel/test/res/drawable/emo_im_yelling.png
Binary files differ
diff --git a/carousel/test/res/drawable/specularmap.png b/carousel/test/res/drawable/specularmap.png
new file mode 100644
index 0000000..6b1c0d9
--- /dev/null
+++ b/carousel/test/res/drawable/specularmap.png
Binary files differ
diff --git a/carousel/test/res/drawable/unknown.png b/carousel/test/res/drawable/unknown.png
new file mode 100644
index 0000000..74bead5
--- /dev/null
+++ b/carousel/test/res/drawable/unknown.png
Binary files differ
diff --git a/carousel/test/res/drawable/wait.png b/carousel/test/res/drawable/wait.png
new file mode 100644
index 0000000..e3f892f
--- /dev/null
+++ b/carousel/test/res/drawable/wait.png
Binary files differ
diff --git a/carousel/test/res/layout/music_demo.xml b/carousel/test/res/layout/music_demo.xml
new file mode 100644
index 0000000..ba3e628
--- /dev/null
+++ b/carousel/test/res/layout/music_demo.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2008, 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.
+*/
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <com.android.carouseltest.MyCarouselView
+ android:id="@+id/carousel"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1">
+ </com.android.carouseltest.MyCarouselView>>
+
+</LinearLayout>
diff --git a/carousel/test/res/layout/taskswitcher.xml b/carousel/test/res/layout/taskswitcher.xml
new file mode 100644
index 0000000..2d07d54
--- /dev/null
+++ b/carousel/test/res/layout/taskswitcher.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2008, 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.
+*/
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <!-- Title -->
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="#80FFFFFF"
+ android:textStyle="bold"
+ android:singleLine="true"
+ android:text="@string/recent_tasks_title"
+ android:visibility="gone"/>
+
+ <!-- This is only intended to be visible when carousel is invisible -->
+ <TextView
+ android:id="@+id/no_applications_message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:text="@string/no_recent_tasks"
+ android:visibility="gone"/>
+
+ <com.android.carouseltest.MyCarouselView
+ android:id="@+id/carousel"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:layout_weight="1">
+ </com.android.carouseltest.MyCarouselView>>
+
+</LinearLayout>
diff --git a/carousel/test/res/raw/book.a3d b/carousel/test/res/raw/book.a3d
new file mode 100644
index 0000000..c38cc44
--- /dev/null
+++ b/carousel/test/res/raw/book.a3d
Binary files differ
diff --git a/carousel/test/res/values/strings.xml b/carousel/test/res/values/strings.xml
new file mode 100644
index 0000000..a64a04c
--- /dev/null
+++ b/carousel/test/res/values/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright (C) 2009 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.
+*/
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- General --><skip />
+ <string name="music_demo_activity_label">MusicCarousel2</string>
+ <string name="carousel_test_activity_label">CarouselTest2</string>
+ <string name="task_switcher_activity_label">TaskSwitcher2</string>
+
+ <string name="recent_tasks_title">Recent Applications</string>
+ <string name="no_recent_tasks">No recent tasks</string>
+
+</resources>
diff --git a/carousel/test/src/com/android/carouseltest/CarouselTestActivity.java b/carousel/test/src/com/android/carouseltest/CarouselTestActivity.java
new file mode 100644
index 0000000..f67ce77
--- /dev/null
+++ b/carousel/test/src/com/android/carouseltest/CarouselTestActivity.java
@@ -0,0 +1,183 @@
+/*
+ * 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.carouseltest;
+
+import com.android.carouseltest.MyCarouselView;
+import com.android.ex.carousel.CarouselView;
+import com.android.ex.carousel.CarouselRS.CarouselCallback;
+
+import android.app.Activity;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+
+public class CarouselTestActivity extends Activity {
+ private static final int HOLDOFF_DELAY = 0;
+ private static final int CARD_SLOTS = 56;
+ private static final int TOTAL_CARDS = 1000;
+ private static final int TEXTURE_HEIGHT = 255;
+ private static final int TEXTURE_WIDTH = 255;
+ private static final String TAG = "CarouselTestActivity";
+
+ private static final int SLOTS_VISIBLE = 7;
+ protected static final boolean DBG = true;
+ protected static final int SET_TEXTURE_N = 1000;
+ private CarouselView mView;
+ private Paint mPaint = new Paint();
+
+ private HandlerThread mTextureThread;
+ private HandlerThread mGeometryThread;
+ private Handler mTextureHandler;
+ private Handler mGeometryHandler;
+ private Handler mSetTextureHandler;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mView = new MyCarouselView(this);
+ mPaint.setColor(0xffffffff);
+ final Resources res = getResources();
+
+ mTextureThread = new HandlerThread(TAG);
+ mGeometryThread = new HandlerThread(TAG);
+ mTextureThread.start();
+ mGeometryThread.start();
+
+ mTextureHandler = new Handler(mTextureThread.getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what < TOTAL_CARDS) {
+ final int id = (Integer) msg.arg1;
+ final int n = msg.what;
+ final Bitmap bitmap = createBitmap(id);
+ mSetTextureHandler.obtainMessage(SET_TEXTURE_N + n, bitmap).sendToTarget();
+ }
+ }
+ };
+
+ mGeometryHandler = new Handler(mGeometryThread.getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ // TODO
+ }
+ };
+
+ mSetTextureHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what >= SET_TEXTURE_N) {
+ final Bitmap bitmap = (Bitmap) msg.obj;
+ mView.setTextureForItem(msg.what - SET_TEXTURE_N, bitmap);
+ }
+ }
+ };
+
+ mView.setCallback(mCarouselCallback);
+ mView.setSlotCount(CARD_SLOTS);
+ mView.createCards(TOTAL_CARDS);
+ mView.setVisibleSlots(SLOTS_VISIBLE);
+ mView.setStartAngle((float) -(2.0f*Math.PI * 5 / CARD_SLOTS));
+ mView.setDefaultBitmap(BitmapFactory.decodeResource(res, R.drawable.unknown));
+ mView.setLoadingBitmap(BitmapFactory.decodeResource(res, R.drawable.wait));
+
+ /*
+ int flags = WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+ WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ mView.getWidth(), mView.getHeight(), WindowManager.LayoutParams.TYPE_APPLICATION,
+ flags, PixelFormat.TRANSLUCENT);
+ mView.setBackgroundColor(0x80000000);
+
+ getWindow().setAttributes(lp);
+ */
+ setContentView(mView);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mView.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mView.onPause();
+ }
+
+ Bitmap createBitmap(int n) {
+ Bitmap bitmap = Bitmap.createBitmap(TEXTURE_WIDTH, TEXTURE_HEIGHT,
+ Bitmap.Config.RGB_565);
+ Canvas canvas = new Canvas(bitmap);
+ canvas.drawARGB(255, 64, 64, 64);
+ mPaint.setTextSize(100.0f);
+ canvas.drawText(""+n, 0, TEXTURE_HEIGHT-10, mPaint);
+ return bitmap;
+ }
+
+ private CarouselCallback mCarouselCallback = new CarouselCallback() {
+
+ public void onRequestTexture(int n) {
+ if (DBG) Log.v(TAG, "onRequestTexture(" + n + ")" );
+ mTextureHandler.removeMessages(n);
+ Message message = mTextureHandler.obtainMessage(n, n, 0);
+ mTextureHandler.sendMessageDelayed(message, HOLDOFF_DELAY);
+ }
+
+ public void onInvalidateTexture(final int n) {
+ if (DBG) Log.v(TAG, "onInvalidateTexture(" + n + ")");
+ mTextureHandler.removeMessages(n);
+ }
+
+ public void onRequestGeometry(int n) {
+ if (DBG) Log.v(TAG, "onRequestGeometry(" + n + ")");
+ mGeometryHandler.removeMessages(n);
+ mGeometryHandler.sendMessage(mGeometryHandler.obtainMessage(n));
+ }
+
+ public void onInvalidateGeometry(int n) {
+ if (DBG) Log.v(TAG, "onInvalidateGeometry(" + n + ")");
+ mGeometryHandler.removeMessages(n);
+ }
+
+ public void onCardSelected(int n) {
+ if (DBG) Log.v(TAG, "onCardSelected(" + n + ")");
+ }
+
+ public void onAnimationStarted() {
+
+ }
+
+ public void onAnimationFinished() {
+
+ }
+
+ };
+
+}
diff --git a/carousel/test/src/com/android/carouseltest/MusicDemoActivity.java b/carousel/test/src/com/android/carouseltest/MusicDemoActivity.java
new file mode 100644
index 0000000..e5f3504
--- /dev/null
+++ b/carousel/test/src/com/android/carouseltest/MusicDemoActivity.java
@@ -0,0 +1,248 @@
+/*
+ * 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.carouseltest;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashMap;
+
+import com.android.carouseltest.MyCarouselView;
+import com.android.ex.carousel.CarouselView;
+import com.android.ex.carousel.CarouselRS.CarouselCallback;
+
+import android.app.Activity;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PaintFlagsDrawFilter;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
+import android.graphics.Xfermode;
+import android.media.MediaScannerConnection;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.renderscript.Mesh;
+import android.util.Log;
+
+public class MusicDemoActivity extends Activity {
+ private static final int HOLDOFF_DELAY = 0; // larger # gives smoother animation but lag
+ private static final int CD_GEOMETRY = R.raw.book;
+ private static final int VISIBLE_SLOTS = 7;
+ private static final int CARD_SLOTS = 56;
+ private static final int TOTAL_CARDS = 10000;
+ private static final String TAG = "MusicDemoActivity";
+ protected static final boolean DBG = true;
+ private static final boolean TEST_ON_DEMAND_GEOMETRY = false; // for testing.. geometry per card
+ protected static final int SET_TEXTURE_N = 1000;
+ private CarouselView mView;
+ private int imageResources[] = {
+ R.drawable.emo_im_angel,
+ R.drawable.emo_im_cool,
+ R.drawable.emo_im_crying,
+ R.drawable.emo_im_foot_in_mouth,
+ R.drawable.emo_im_happy,
+ R.drawable.emo_im_kissing,
+ R.drawable.emo_im_laughing,
+ R.drawable.emo_im_lips_are_sealed,
+ R.drawable.emo_im_money_mouth,
+ R.drawable.emo_im_sad,
+ R.drawable.emo_im_surprised,
+ R.drawable.emo_im_tongue_sticking_out,
+ R.drawable.emo_im_undecided,
+ R.drawable.emo_im_winking,
+ R.drawable.emo_im_wtf,
+ R.drawable.emo_im_yelling
+ };
+ private Bitmap mSpecularMap;
+ private Mesh mGeometry;
+
+ private HandlerThread mTextureThread;
+ private HandlerThread mGeometryThread;
+ private Handler mTextureHandler;
+ private Handler mGeometryHandler;
+ private Handler mSetTextureHandler;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mTextureThread = new HandlerThread(TAG);
+ mGeometryThread = new HandlerThread(TAG);
+ mTextureThread.start();
+ mGeometryThread.start();
+
+ mTextureHandler = new Handler(mTextureThread.getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what < TOTAL_CARDS) {
+ final int resId = (Integer) msg.arg1;
+ final int n = msg.what;
+ final Bitmap bitmap = compositeBitmap(resId);
+ mSetTextureHandler.obtainMessage(SET_TEXTURE_N + n, bitmap).sendToTarget();
+ }
+ }
+ };
+
+ mGeometryHandler = new Handler(mGeometryThread.getLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ // TODO
+ }
+ };
+
+ mSetTextureHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (msg.what >= SET_TEXTURE_N) {
+ final Bitmap bitmap = (Bitmap) msg.obj;
+ mView.setTextureForItem(msg.what - SET_TEXTURE_N, bitmap);
+ }
+ }
+ };
+
+ final Resources res = getResources();
+ setContentView(R.layout.music_demo);
+ // mView = new MyCarouselView(this);
+ mView = (CarouselView) findViewById(R.id.carousel);
+ mView.setCallback(mCallback);
+ mView.setSlotCount(CARD_SLOTS);
+ mView.createCards(TOTAL_CARDS);
+ mView.setVisibleSlots(VISIBLE_SLOTS);
+ mView.setStartAngle((float) -(2.0f*Math.PI * 5 / CARD_SLOTS));
+ mView.setDefaultBitmap(BitmapFactory.decodeResource(res, R.drawable.wait));
+ mView.setLoadingBitmap(BitmapFactory.decodeResource(res, R.drawable.blank_album));
+ mView.setDefaultGeometry(mView.loadGeometry(CD_GEOMETRY));
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mView.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mView.onPause();
+ }
+
+ void writeBitmapToFile(Bitmap bitmap, String fname) {
+ File path = Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_PICTURES);
+ File file = new File(path, fname);
+
+ try {
+ path.mkdirs();
+ OutputStream os = new FileOutputStream(file);
+ MediaScannerConnection.scanFile(this, new String[] { file.toString() }, null,
+ new MediaScannerConnection.OnScanCompletedListener() {
+
+ public void onScanCompleted(String path, Uri uri) {
+
+ }
+ });
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
+ } catch (IOException e) {
+ Log.w("ExternalStorage", "Error writing " + file, e);
+ }
+ }
+
+ private Bitmap compositeBitmap(int id) {
+ final Resources res = getResources();
+ if (mSpecularMap == null) {
+ mSpecularMap = BitmapFactory.decodeResource(res, R.drawable.specularmap);
+ }
+
+ final int width = mSpecularMap.getWidth();
+ final int height = mSpecularMap.getHeight();
+
+ Log.v(TAG, "Width = " + width + ", height = " + height);
+
+ final Bitmap artwork = BitmapFactory.decodeResource(res, id);
+
+ /*
+ final Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
+ //result.setHasAlpha(false);
+ final Canvas canvas = new Canvas(result);
+ canvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
+ Paint.FILTER_BITMAP_FLAG));
+
+ Paint paint = new Paint();
+ paint.setFilterBitmap(false);
+
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
+ canvas.drawBitmap(artwork, 0, 0, paint);
+ paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
+ canvas.drawBitmap(mSpecularMap, 0, 0, paint);
+
+ writeBitmapToFile(result, "test" + id + ".png");
+ */
+
+ return artwork;
+ }
+
+ private CarouselCallback mCallback = new CarouselCallback() {
+
+ public void onRequestTexture(int n) {
+ if (DBG) Log.v(TAG, "onRequestTexture(" + n + ")" );
+ mTextureHandler.removeMessages(n);
+ int resId = imageResources[n%imageResources.length];
+ Message message = mTextureHandler.obtainMessage(n, resId, 0);
+ mTextureHandler.sendMessageDelayed(message, HOLDOFF_DELAY);
+ }
+
+ public void onInvalidateTexture(final int n) {
+ if (DBG) Log.v(TAG, "onInvalidateTexture(" + n + ")");
+ mTextureHandler.removeMessages(n);
+ }
+
+ public void onRequestGeometry(int n) {
+ if (DBG) Log.v(TAG, "onRequestGeometry(" + n + ")");
+ mGeometryHandler.removeMessages(n);
+ mGeometryHandler.sendMessage(mGeometryHandler.obtainMessage(n));
+ }
+
+ public void onInvalidateGeometry(int n) {
+ if (DBG) Log.v(TAG, "onInvalidateGeometry(" + n + ")");
+ mGeometryHandler.removeMessages(n);
+ }
+
+ public void onCardSelected(int n) {
+ if (DBG) Log.v(TAG, "onCardSelected(" + n + ")");
+ }
+
+ public void onAnimationStarted() {
+
+ }
+
+ public void onAnimationFinished() {
+
+ }
+
+ };
+
+}
diff --git a/carousel/test/src/com/android/carouseltest/MyCarouselView.java b/carousel/test/src/com/android/carouseltest/MyCarouselView.java
new file mode 100644
index 0000000..b9ee6b1
--- /dev/null
+++ b/carousel/test/src/com/android/carouseltest/MyCarouselView.java
@@ -0,0 +1,39 @@
+/*
+ * 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.carouseltest;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import com.android.ex.carousel.CarouselView;
+import com.android.ex.carousel.CarouselView.Info;
+
+public class MyCarouselView extends CarouselView {
+
+ public MyCarouselView(Context context) {
+ this(context, null);
+ }
+
+ public MyCarouselView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public Info getRenderScriptInfo() {
+ return new Info(R.raw.carousel);
+ }
+
+}
diff --git a/carousel/test/src/com/android/carouseltest/TaskSwitcherActivity.java b/carousel/test/src/com/android/carouseltest/TaskSwitcherActivity.java
new file mode 100644
index 0000000..7a2f047
--- /dev/null
+++ b/carousel/test/src/com/android/carouseltest/TaskSwitcherActivity.java
@@ -0,0 +1,315 @@
+/*
+ * 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.carouseltest;
+
+import java.util.ArrayList;
+import java.util.List;
+import com.android.carouseltest.R;
+
+import com.android.ex.carousel.CarouselRS.CarouselCallback;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.IThumbnailReceiver;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.graphics.Bitmap.Config;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.View;
+
+public class TaskSwitcherActivity extends Activity {
+ private static final String TAG = "TaskSwitcherActivity";
+ private static final int CARD_SLOTS = 56;
+ private static final int MAX_TASKS = 20;
+ private static final int VISIBLE_SLOTS = 7;
+ private ActivityManager mActivityManager;
+ private List<RunningTaskInfo> mRunningTaskList;
+ private boolean mPortraitMode = true;
+ private ArrayList<ActivityDescription> mActivityDescriptions
+ = new ArrayList<ActivityDescription>();
+ private MyCarouselView mView;
+ private Bitmap mBlankBitmap = Bitmap.createBitmap(128, 128, Config.RGB_565);
+
+ static class ActivityDescription {
+ int id;
+ Bitmap thumbnail;
+ Drawable icon;
+ String label;
+ String description;
+ Intent intent;
+ Matrix matrix;
+
+ public ActivityDescription(Bitmap _thumbnail,
+ Drawable _icon, String _label, String _desc, int _id)
+ {
+ thumbnail = _thumbnail;
+ icon = _icon;
+ label = _label;
+ description = _desc;
+ id = _id;
+ }
+
+ public void clear() {
+ icon = null;
+ thumbnail = null;
+ label = null;
+ description = null;
+ intent = null;
+ matrix = null;
+ id = -1;
+ }
+ };
+
+ private ActivityDescription findActivityDescription(int id) {
+ for (int i = 0; i < mActivityDescriptions.size(); i++) {
+ ActivityDescription item = mActivityDescriptions.get(i);
+ if (item != null && item.id == id) {
+ return item;
+ }
+ }
+ return null;
+ }
+
+ final CarouselCallback mCarouselCallback = new CarouselCallback() {
+
+ public void onAnimationFinished() {
+
+ }
+
+ public void onAnimationStarted() {
+
+ }
+
+ public void onCardSelected(int n) {
+ if (n < mActivityDescriptions.size()) {
+ ActivityDescription item = mActivityDescriptions.get(n);
+ // prepare a launch intent and send it
+ if (item.intent != null) {
+ item.intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY);
+ try {
+ Log.v(TAG, "Starting intent " + item.intent);
+ startActivity(item.intent);
+ overridePendingTransition(R.anim.zoom_enter, R.anim.zoom_exit);
+ } catch (ActivityNotFoundException e) {
+ Log.w("Recent", "Unable to launch recent task", e);
+ }
+ finish();
+ }
+ }
+ }
+
+ public void onInvalidateTexture(int n) {
+
+ }
+
+ public void onRequestGeometry(int n) {
+
+ }
+
+ public void onInvalidateGeometry(int n) {
+
+ }
+
+ public void onRequestTexture(final int n) {
+ Log.v(TAG, "onRequestTexture(" + n + ")");
+ if (n < mActivityDescriptions.size()) {
+ mView.post(new Runnable() {
+ public void run() {
+ ActivityDescription desc = mActivityDescriptions.get(n);
+ if (desc != null) {
+ Log.v(TAG, "FOUND ACTIVITY THUMBNAIL " + desc.thumbnail);
+ Bitmap bitmap = desc.thumbnail == null ? mBlankBitmap : desc.thumbnail;
+ mView.setTextureForItem(n, bitmap);
+ } else {
+ Log.v(TAG, "FAILED TO GET ACTIVITY THUMBNAIL FOR ITEM " + n);
+ }
+ }
+ });
+ }
+ }
+ };
+
+ private final IThumbnailReceiver mThumbnailReceiver = new IThumbnailReceiver.Stub() {
+
+ public void finished() throws RemoteException {
+
+ }
+
+ public void newThumbnail(final int id, final Bitmap bitmap, CharSequence description)
+ throws RemoteException {
+ int w = bitmap.getWidth();
+ int h = bitmap.getHeight();
+ Log.v(TAG, "New thumbnail for id=" + id + ", dimensions=" + w + "x" + h
+ + " description '" + description + "'");
+ ActivityDescription info = findActivityDescription(id);
+ if (info != null) {
+ info.thumbnail = bitmap;
+ final int thumbWidth = bitmap.getWidth();
+ final int thumbHeight = bitmap.getHeight();
+ if ((mPortraitMode && thumbWidth > thumbHeight)
+ || (!mPortraitMode && thumbWidth < thumbHeight)) {
+ Matrix matrix = new Matrix();
+ matrix.setRotate(90.0f, (float) thumbWidth / 2, (float) thumbHeight / 2);
+ info.matrix = matrix;
+ } else {
+ info.matrix = null;
+ }
+ } else {
+ Log.v(TAG, "Can't find view for id " + id);
+ }
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Resources res = getResources();
+ final View decorView = getWindow().getDecorView();
+
+ mView = new MyCarouselView(this);
+ mView.setSlotCount(CARD_SLOTS);
+ mView.setVisibleSlots(VISIBLE_SLOTS);
+ mView.createCards(1);
+ mView.setStartAngle((float) -(2.0f*Math.PI * 5 / CARD_SLOTS));
+ mView.setDefaultBitmap(BitmapFactory.decodeResource(res, R.drawable.wait));
+ mView.setLoadingBitmap(BitmapFactory.decodeResource(res, R.drawable.wait));
+ mView.setCallback(mCarouselCallback);
+
+ mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
+ mPortraitMode = decorView.getHeight() > decorView.getWidth();
+
+ refresh();
+
+ setContentView(mView);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ refresh();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mPortraitMode = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT;
+ Log.v(TAG, "CONFIG CHANGE, mPortraitMode = " + mPortraitMode);
+ refresh();
+ }
+
+ void updateRunningTasks() {
+ mRunningTaskList = mActivityManager.getRunningTasks(MAX_TASKS + 2, 0, mThumbnailReceiver);
+ Log.v(TAG, "Portrait: " + mPortraitMode);
+ for (RunningTaskInfo r : mRunningTaskList) {
+ if (r.thumbnail != null) {
+ int thumbWidth = r.thumbnail.getWidth();
+ int thumbHeight = r.thumbnail.getHeight();
+ Log.v(TAG, "Got thumbnail " + thumbWidth + "x" + thumbHeight);
+ ActivityDescription desc = findActivityDescription(r.id);
+ if (desc != null) {
+ desc.thumbnail = r.thumbnail;
+ desc.label = r.topActivity.flattenToShortString();
+ if ((mPortraitMode && thumbWidth > thumbHeight)
+ || (!mPortraitMode && thumbWidth < thumbHeight)) {
+ Matrix matrix = new Matrix();
+ matrix.setRotate(90.0f, (float) thumbWidth / 2, (float) thumbHeight / 2);
+ desc.matrix = matrix;
+ }
+ } else {
+ Log.v(TAG, "Couldn't find ActivityDesc for id=" + r.id);
+ }
+ } else {
+ Log.v(TAG, "*** RUNNING THUMBNAIL WAS NULL ***");
+ }
+ }
+ // HACK refresh carousel
+ mView.createCards(mActivityDescriptions.size());
+ }
+
+ private void updateRecentTasks() {
+ final PackageManager pm = getPackageManager();
+ final ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
+
+ final List<ActivityManager.RecentTaskInfo> recentTasks =
+ am.getRecentTasks(MAX_TASKS + 2, ActivityManager.RECENT_IGNORE_UNAVAILABLE);
+
+ ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
+ .resolveActivityInfo(pm, 0);
+
+ //IconUtilities iconUtilities = new IconUtilities(this);
+
+ int numTasks = recentTasks.size();
+ mActivityDescriptions.clear();
+ for (int i = 1, index = 0; i < numTasks && (index < MAX_TASKS + 2); ++i) {
+ final ActivityManager.RecentTaskInfo recentInfo = recentTasks.get(i);
+
+ Intent intent = new Intent(recentInfo.baseIntent);
+ if (recentInfo.origActivity != null) {
+ intent.setComponent(recentInfo.origActivity);
+ }
+
+ // Skip the current home activity.
+ if (homeInfo != null
+ && homeInfo.packageName.equals(intent.getComponent().getPackageName())
+ && homeInfo.name.equals(intent.getComponent().getClassName())) {
+ continue;
+ }
+
+ intent.setFlags((intent.getFlags()&~Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
+ | Intent.FLAG_ACTIVITY_NEW_TASK);
+ final ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
+ if (resolveInfo != null) {
+ final ActivityInfo info = resolveInfo.activityInfo;
+ final String title = info.loadLabel(pm).toString();
+ Drawable icon = info.loadIcon(pm);
+
+ int id = recentTasks.get(i).id;
+ if (id != -1 && title != null && title.length() > 0 && icon != null) {
+ //icon = iconUtilities.createIconDrawable(icon);
+ ActivityDescription item = new ActivityDescription(null, icon, title, null, id);
+ item.intent = intent;
+ mActivityDescriptions.add(item);
+ Log.v(TAG, "Added item[" + index + "], id=" + item.id);
+ ++index;
+ } else {
+ Log.v(TAG, "SKIPPING item " + id);
+ }
+ }
+ }
+ }
+
+ private void refresh() {
+ updateRecentTasks();
+ updateRunningTasks();
+ mView.createCards(mActivityDescriptions.size());
+ }
+}