diff options
author | Jim Miller <jaggies@google.com> | 2010-09-02 13:17:24 -0700 |
---|---|---|
committer | Jim Miller <jaggies@google.com> | 2010-09-02 17:06:39 -0700 |
commit | 5ce730797a8a7278dfe19dac8a9460b25675fed0 (patch) | |
tree | f7b506eeef5ccc496e206ea36609e201e189aedc | |
parent | 1fa3a8f74d46a616e27c23ed1512f4b7de2ad66d (diff) | |
download | android_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
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 Binary files differnew file mode 100644 index 0000000..76331d5 --- /dev/null +++ b/carousel/test/res/drawable/blank_album.png diff --git a/carousel/test/res/drawable/emo_im_angel.png b/carousel/test/res/drawable/emo_im_angel.png Binary files differnew file mode 100644 index 0000000..10742a6 --- /dev/null +++ b/carousel/test/res/drawable/emo_im_angel.png diff --git a/carousel/test/res/drawable/emo_im_cool.png b/carousel/test/res/drawable/emo_im_cool.png Binary files differnew file mode 100644 index 0000000..e3c8654 --- /dev/null +++ b/carousel/test/res/drawable/emo_im_cool.png diff --git a/carousel/test/res/drawable/emo_im_crying.png b/carousel/test/res/drawable/emo_im_crying.png Binary files differnew file mode 100644 index 0000000..b23791c --- /dev/null +++ b/carousel/test/res/drawable/emo_im_crying.png diff --git a/carousel/test/res/drawable/emo_im_foot_in_mouth.png b/carousel/test/res/drawable/emo_im_foot_in_mouth.png Binary files differnew file mode 100644 index 0000000..050b7be --- /dev/null +++ b/carousel/test/res/drawable/emo_im_foot_in_mouth.png diff --git a/carousel/test/res/drawable/emo_im_happy.png b/carousel/test/res/drawable/emo_im_happy.png Binary files differnew file mode 100644 index 0000000..69e3bed --- /dev/null +++ b/carousel/test/res/drawable/emo_im_happy.png diff --git a/carousel/test/res/drawable/emo_im_kissing.png b/carousel/test/res/drawable/emo_im_kissing.png Binary files differnew file mode 100644 index 0000000..0cca68e --- /dev/null +++ b/carousel/test/res/drawable/emo_im_kissing.png diff --git a/carousel/test/res/drawable/emo_im_laughing.png b/carousel/test/res/drawable/emo_im_laughing.png Binary files differnew file mode 100644 index 0000000..8406ad0 --- /dev/null +++ b/carousel/test/res/drawable/emo_im_laughing.png diff --git a/carousel/test/res/drawable/emo_im_lips_are_sealed.png b/carousel/test/res/drawable/emo_im_lips_are_sealed.png Binary files differnew file mode 100644 index 0000000..222f175 --- /dev/null +++ b/carousel/test/res/drawable/emo_im_lips_are_sealed.png diff --git a/carousel/test/res/drawable/emo_im_money_mouth.png b/carousel/test/res/drawable/emo_im_money_mouth.png Binary files differnew file mode 100644 index 0000000..d711bfb --- /dev/null +++ b/carousel/test/res/drawable/emo_im_money_mouth.png diff --git a/carousel/test/res/drawable/emo_im_sad.png b/carousel/test/res/drawable/emo_im_sad.png Binary files differnew file mode 100644 index 0000000..40017f1 --- /dev/null +++ b/carousel/test/res/drawable/emo_im_sad.png diff --git a/carousel/test/res/drawable/emo_im_surprised.png b/carousel/test/res/drawable/emo_im_surprised.png Binary files differnew file mode 100644 index 0000000..4b2af7a --- /dev/null +++ b/carousel/test/res/drawable/emo_im_surprised.png diff --git a/carousel/test/res/drawable/emo_im_tongue_sticking_out.png b/carousel/test/res/drawable/emo_im_tongue_sticking_out.png Binary files differnew file mode 100644 index 0000000..42ac80d --- /dev/null +++ b/carousel/test/res/drawable/emo_im_tongue_sticking_out.png diff --git a/carousel/test/res/drawable/emo_im_undecided.png b/carousel/test/res/drawable/emo_im_undecided.png Binary files differnew file mode 100644 index 0000000..2cf5bd2 --- /dev/null +++ b/carousel/test/res/drawable/emo_im_undecided.png diff --git a/carousel/test/res/drawable/emo_im_winking.png b/carousel/test/res/drawable/emo_im_winking.png Binary files differnew file mode 100644 index 0000000..a3a0876 --- /dev/null +++ b/carousel/test/res/drawable/emo_im_winking.png diff --git a/carousel/test/res/drawable/emo_im_wtf.png b/carousel/test/res/drawable/emo_im_wtf.png Binary files differnew file mode 100644 index 0000000..86c4bda --- /dev/null +++ b/carousel/test/res/drawable/emo_im_wtf.png diff --git a/carousel/test/res/drawable/emo_im_yelling.png b/carousel/test/res/drawable/emo_im_yelling.png Binary files differnew file mode 100644 index 0000000..cfd991a --- /dev/null +++ b/carousel/test/res/drawable/emo_im_yelling.png diff --git a/carousel/test/res/drawable/specularmap.png b/carousel/test/res/drawable/specularmap.png Binary files differnew file mode 100644 index 0000000..6b1c0d9 --- /dev/null +++ b/carousel/test/res/drawable/specularmap.png diff --git a/carousel/test/res/drawable/unknown.png b/carousel/test/res/drawable/unknown.png Binary files differnew file mode 100644 index 0000000..74bead5 --- /dev/null +++ b/carousel/test/res/drawable/unknown.png diff --git a/carousel/test/res/drawable/wait.png b/carousel/test/res/drawable/wait.png Binary files differnew file mode 100644 index 0000000..e3f892f --- /dev/null +++ b/carousel/test/res/drawable/wait.png 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 Binary files differnew file mode 100644 index 0000000..c38cc44 --- /dev/null +++ b/carousel/test/res/raw/book.a3d 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()); + } +} |