Merge "Fix 2797185: Integrate 3D RecentApps View into system."
diff --git a/core/java/com/android/internal/widget/CarouselView.java b/core/java/com/android/internal/widget/CarouselView.java
index e0c65dc..217805b 100644
--- a/core/java/com/android/internal/widget/CarouselView.java
+++ b/core/java/com/android/internal/widget/CarouselView.java
@@ -16,21 +16,26 @@
 
 package com.android.internal.widget;
 
+import com.android.internal.R;
 import com.android.internal.widget.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 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";
@@ -46,20 +51,28 @@
     private int mVisibleSlots = 0;
     private float mStartAngle;
     private int mSlotCount = DEFAULT_SLOT_COUNT;
-    
+
     public CarouselView(Context context) {
-        super(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;
-        mRS = createRenderScript(useDepthBuffer);
+        mRS = createRenderScript(USE_DEPTH_BUFFER);
         mRenderScript = new CarouselRS();
         mRenderScript.init(mRS, getResources());
+        // TODO: add parameters to layout
     }
-    
+
     @Override
     public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
         super.surfaceChanged(holder, format, w, h);
-        mRS.contextSetSurface(w, h, holder.getSurface());
+        //mRS.contextSetSurface(w, h, holder.getSurface());
         mRenderScript.init(mRS, getResources());
         setSlotCount(mSlotCount);
         createCards(mCardCount);
@@ -172,11 +185,20 @@
     
     @Override
     protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
         if(mRS != null) {
             mRS = null;
             destroyRenderScript();
         }
     }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        if (mRS == null) {
+            mRS = createRenderScript(USE_DEPTH_BUFFER);
+        }
+    }
     
     @Override
     public boolean onTouchEvent(MotionEvent event) {
diff --git a/core/java/com/android/internal/widget/carousel.rs b/core/java/com/android/internal/widget/carousel.rs
index 4cfcbf4..87e24c0 100644
--- a/core/java/com/android/internal/widget/carousel.rs
+++ b/core/java/com/android/internal/widget/carousel.rs
@@ -53,19 +53,22 @@
 };
 
 // Client messages *** THIS LIST MUST MATCH THOSE IN CarouselRS.java. ***
-const int CMD_CARD_SELECTED = 100;
-const int CMD_REQUEST_TEXTURE = 200;
-const int CMD_INVALIDATE_TEXTURE = 210;
-const int CMD_REQUEST_GEOMETRY = 300;
-const int CMD_INVALIDATE_GEOMETRY = 310;
-const int CMD_ANIMATION_STARTED = 400;
-const int CMD_ANIMATION_FINISHED = 500;
-const int CMD_PING = 600;
+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;
-bool debugPicking = false;
+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
@@ -96,8 +99,11 @@
 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.5f, 0.5f, 0.5f };
+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] = {
@@ -121,10 +127,12 @@
 // 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;
@@ -245,12 +253,24 @@
         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
 }
 
@@ -353,27 +373,30 @@
     }
     velocityTracker = 0.0f;
     velocityTrackerCount = 0;
+    touchTime = rsUptimeMillis();
+    touchBias = bias;
+    currentSelection = doSelection(x, y);
 }
 
 
 void doStop(float x, float y)
 {
+    int64_t currentTime = rsUptimeMillis();
     updateAllocationVars();
-
-    velocity = velocityTrackerCount > 0 ?
-            (velocityTracker / velocityTrackerCount) : 0.0f;  // avg velocity
-    if (fabs(velocity) > velocityThreshold) {
-        animating = true;
-        rsSendToClient(CMD_ANIMATION_STARTED);
+    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 {
-        const int selection = doSelection(x, y);  // velocity too small; treat as a tap
-        if (selection != -1) {
-            rsDebug("HIT!", selection);
-            int data[1];
-            data[0] = selection;
-            rsSendToClient(CMD_CARD_SELECTED, data, sizeof(data));
+        velocity = velocityTrackerCount > 0 ?
+                    (velocityTracker / velocityTrackerCount) : 0.0f;  // avg velocity
+        if (fabs(velocity) > velocityThreshold) {
+            animating = true;
+            rsSendToClient(CMD_ANIMATION_STARTED);
         }
     }
+    currentSelection = -1;
     lastTime = rsUptimeMillis();
 }
 
@@ -395,6 +418,14 @@
         //    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;
 }
 
@@ -431,6 +462,8 @@
     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) {
@@ -444,7 +477,7 @@
     // TODO: pre-compute lowerLeftRay, du, dv to eliminate most of this math.
     if (true) {
         const float u = x / rsgGetWidth();
-        const float v = (y / rsgGetHeight());
+        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);
@@ -537,9 +570,8 @@
 // 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()
+static bool updateNextPosition(int64_t currentTime)
 {
-    int64_t currentTime = rsUptimeMillis();
     if (animating) {
         float dt = deltaTimeInSeconds(currentTime);
         if (dt <= 0.0f)
@@ -658,8 +690,7 @@
             // ask the host to remove the texture
             if (cards[i].textureState == STATE_LOADED) {
                 data[0] = i;
-                bool enqueued = true;
-                rsSendToClientBlocking(CMD_INVALIDATE_TEXTURE, data, sizeof(data));
+                bool enqueued = rsSendToClient(CMD_INVALIDATE_TEXTURE, data, sizeof(data));
                 if (enqueued) {
                     cards[i].textureState = STATE_INVALID;
                 } else {
@@ -669,8 +700,7 @@
             // ask the host to remove the geometry
             if (cards[i].geometryState == STATE_LOADED) {
                 data[0] = i;
-                bool enqueued = true;
-                rsSendToClientBlocking(CMD_INVALIDATE_GEOMETRY, data, sizeof(data));
+                bool enqueued = rsSendToClient(CMD_INVALIDATE_GEOMETRY, data, sizeof(data));
                 if (enqueued) {
                     cards[i].geometryState = STATE_INVALID;
                 } else {
@@ -699,7 +729,7 @@
             if (makeRayForPixelAt(&ray, posX, posY)) {
                 float bestTime = FLT_MAX;
                 if (intersectGeometry(&ray, &bestTime) != -1) {
-                    rsgDrawSpriteScreenspace(posX, posY, 0.0f, 2.0f, 2.0f);
+                    rsgDrawSpriteScreenspace(posX, h - posY - 1, 0.0f, 2.0f, 2.0f);
                 }
             }
         }
@@ -707,8 +737,9 @@
 }
 
 int root() {
-    rsgClearDepth(1.0f);
+    int64_t currentTime = rsUptimeMillis();
 
+    rsgClearDepth(1.0f);
     rsgBindProgramVertex(vertexProgram);
     rsgBindProgramFragment(fragmentProgram);
     rsgBindProgramStore(programStore);
@@ -735,7 +766,11 @@
 
     updateCameraMatrix(rsgGetWidth(), rsgGetHeight());
 
-    bool stillAnimating = updateNextPosition();
+    const bool timeExpired = (currentTime - touchTime) > ANIMATION_SCALE_TIME;
+    if (timeExpired) {
+        //currentSelection = -1;
+    }
+    bool stillAnimating = updateNextPosition(currentTime) || !timeExpired;
 
     cullCards();
 
diff --git a/core/res/res/layout/recent_apps_activity.xml b/core/res/res/layout/recent_apps_activity.xml
new file mode 100644
index 0000000..d962339
--- /dev/null
+++ b/core/res/res/layout/recent_apps_activity.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="@android: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="@android:string/no_recent_tasks"
+        android:visibility="gone"/>
+
+    <com.android.internal.widget.CarouselView
+        android:id="@+id/carousel"
+        android:layout_width="match_parent"
+        android:layout_height="0dip"
+        android:layout_weight="1">
+    </com.android.internal.widget.CarouselView>
+
+</LinearLayout>
diff --git a/core/res/res/values-xlarge/config.xml b/core/res/res/values-xlarge/config.xml
index e92ed11..7e5a27b 100644
--- a/core/res/res/values-xlarge/config.xml
+++ b/core/res/res/values-xlarge/config.xml
@@ -29,5 +29,9 @@
     <bool name="config_enableSlidingTabFirst">false</bool>
     <!-- Enable lockscreen rotation -->
     <bool name="config_enableLockScreenRotation">true</bool>
+
+    <!-- Enables 3d task switcher on xlarge device -->
+    <bool name="config_enableRecentApps3D">true</bool>
+
 </resources>
 
diff --git a/core/res/res/values-xlarge/dimens.xml b/core/res/res/values-xlarge/dimens.xml
index 1a16da7..516fb5f 100644
--- a/core/res/res/values-xlarge/dimens.xml
+++ b/core/res/res/values-xlarge/dimens.xml
@@ -29,5 +29,9 @@
     <dimen name="password_keyboard_key_height_alpha">0.35in</dimen>
     <!-- Default height of a key in the password keyboard for numeric -->
     <dimen name="password_keyboard_key_height_numeric">0.47in</dimen>
-</resources>
 
+    <!-- The width that is used when creating thumbnails of applications. -->
+    <dimen name="thumbnail_width">256dp</dimen>
+    <!-- The height that is used when creating thumbnails of applications. -->
+    <dimen name="thumbnail_height">255dp</dimen>
+</resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 3e3f47c..a071cac 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -299,6 +299,9 @@
     <!-- Diable lockscreen rotation by default -->
     <bool name="config_enableLockScreenRotation">false</bool>
 
+    <!-- Enable 3D RecentApplications view -->
+    <bool name="config_enableRecentApps3D">false</bool>
+
     <!-- Array of light sensor LUX values to define our levels for auto backlight brightness support.
          The N entries of this array define N + 1 zones as follows:
 
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 7bc2e7d..18e2f479 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -5,6 +5,7 @@
         >
 
     <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
+    <uses-permission android:name="android.permission.GET_TASKS" />
 
     <application
         android:persistent="true"
@@ -26,5 +27,11 @@
                 android:excludeFromRecents="true">
         </activity>
 
+        <activity android:name=".statusbar.RecentApplicationsActivity"
+            android:theme="@android:style/Theme.NoTitleBar"
+            android:excludeFromRecents="true"
+            android:exported="true">
+        </activity>
+
     </application>
 </manifest>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RecentApplicationsActivity.java b/packages/SystemUI/src/com/android/systemui/statusbar/RecentApplicationsActivity.java
new file mode 100644
index 0000000..a5ba7e2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RecentApplicationsActivity.java
@@ -0,0 +1,340 @@
+/*
+ * 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.systemui.statusbar;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.android.internal.widget.CarouselView;
+import com.android.internal.widget.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.Matrix;
+import android.graphics.Bitmap.Config;
+import android.graphics.drawable.Drawable;
+import android.graphics.PixelFormat;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.View;
+
+public class RecentApplicationsActivity extends Activity {
+    private static final String TAG = "RecentApplicationsActivity";
+    private static boolean DBG = true;
+    private static final int CARD_SLOTS = 56;
+    private static final int VISIBLE_SLOTS = 7;
+    private static final int MAX_TASKS = VISIBLE_SLOTS * 2;
+    private ActivityManager mActivityManager;
+    private List<RunningTaskInfo> mRunningTaskList;
+    private boolean mPortraitMode = true;
+    private ArrayList<ActivityDescription> mActivityDescriptions
+            = new ArrayList<ActivityDescription>();
+    private CarouselView mCarouselView;
+    private View mNoRecentsView;
+    private Bitmap mBlankBitmap = Bitmap.createBitmap(
+            new int[] {0xff808080, 0xffffffff, 0xff808080, 0xffffffff}, 2, 2, Config.RGB_565);
+
+    static class ActivityDescription {
+        int id;
+        Bitmap thumbnail; // generated by Activity.onCreateThumbnail()
+        Drawable icon; // application package icon
+        String label; // application package label
+        String description; // generated by Activity.onCreateDescription()
+        Intent intent; // launch intent for application
+        Matrix matrix; // arbitrary rotation matrix to correct orientation
+        int position; // position in list
+
+        public ActivityDescription(Bitmap _thumbnail,
+                Drawable _icon, String _label, String _desc, int _id, int _pos)
+        {
+            thumbnail = _thumbnail;
+            icon = _icon;
+            label = _label;
+            description = _desc;
+            id = _id;
+            position = _pos;
+        }
+
+        public void clear() {
+            icon = null;
+            thumbnail = null;
+            label = null;
+            description = null;
+            intent = null;
+            matrix = null;
+            id = -1;
+            position = -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 {
+                        if (DBG) Log.v(TAG, "Starting intent " + item.intent);
+                        startActivity(item.intent);
+                        //overridePendingTransition(R.anim.zoom_enter, R.anim.zoom_exit);
+                    } catch (ActivityNotFoundException e) {
+                        if (DBG) 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) {
+            if (DBG) Log.v(TAG, "onRequestTexture(" + n + ")");
+            if (n < mActivityDescriptions.size()) {
+                mCarouselView.post(new Runnable() {
+                    public void run() {
+                        ActivityDescription info = mActivityDescriptions.get(n);
+                        if (info != null) {
+                            if (DBG) Log.v(TAG, "FOUND ACTIVITY THUMBNAIL " + info.thumbnail);
+                            Bitmap bitmap = info.thumbnail == null ? mBlankBitmap : info.thumbnail;
+                            mCarouselView.setTextureForItem(n, bitmap);
+                        } else {
+                            if (DBG) 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();
+            if (DBG) 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;
+                }
+                mCarouselView.setTextureForItem(info.position, info.thumbnail);
+            } else {
+                if (DBG) 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();
+
+        getWindow().getDecorView().setBackgroundColor(0x80000000);
+        setContentView(R.layout.recent_apps_activity);
+        mCarouselView = (CarouselView)findViewById(R.id.carousel);
+        mNoRecentsView = (View) findViewById(R.id.no_applications_message);
+        //mCarouselView = new CarouselView(this);
+        //setContentView(mCarouselView);
+        mCarouselView.setSlotCount(CARD_SLOTS);
+        mCarouselView.setVisibleSlots(VISIBLE_SLOTS);
+        mCarouselView.createCards(1);
+        mCarouselView.setStartAngle((float) -(2.0f*Math.PI * 5 / CARD_SLOTS));
+        mCarouselView.setDefaultBitmap(mBlankBitmap);
+        mCarouselView.setLoadingBitmap(mBlankBitmap);
+        mCarouselView.setCallback(mCarouselCallback);
+        mCarouselView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
+
+        mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
+        mPortraitMode = decorView.getHeight() > decorView.getWidth();
+
+        refresh();
+
+
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        refresh();
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        mPortraitMode = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT;
+        if (DBG) Log.v(TAG, "CONFIG CHANGE, mPortraitMode = " + mPortraitMode);
+        refresh();
+    }
+
+    void updateRunningTasks() {
+        mRunningTaskList = mActivityManager.getRunningTasks(MAX_TASKS, 0, mThumbnailReceiver);
+        if (DBG) Log.v(TAG, "Portrait: " + mPortraitMode);
+        for (RunningTaskInfo r : mRunningTaskList) {
+            if (r.thumbnail != null) {
+                int thumbWidth = r.thumbnail.getWidth();
+                int thumbHeight = r.thumbnail.getHeight();
+                if (DBG) 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 {
+                    if (DBG) Log.v(TAG, "Couldn't find ActivityDesc for id=" + r.id);
+                }
+            } else {
+                if (DBG) Log.v(TAG, "*** RUNNING THUMBNAIL WAS NULL ***");
+            }
+        }
+        mCarouselView.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, ActivityManager.RECENT_IGNORE_UNAVAILABLE);
+
+        ActivityInfo homeInfo = new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME)
+                    .resolveActivityInfo(pm, 0);
+
+        // IconUtilities iconUtilities = new IconUtilities(this); // FIXME
+
+        int numTasks = recentTasks.size();
+        mActivityDescriptions.clear();
+        for (int i = 0, index = 0; i < numTasks && (index < MAX_TASKS); ++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 = null; FIXME: iconUtilities.createIconDrawable(icon);
+                    ActivityDescription item = new ActivityDescription(
+                            null, icon, title, null, id, index);
+                    item.intent = intent;
+                    mActivityDescriptions.add(item);
+                    if (DBG) Log.v(TAG, "Added item[" + index
+                            + "], id=" + item.id
+                            + ", title=" + item.label);
+                    ++index;
+                } else {
+                    if (DBG) Log.v(TAG, "SKIPPING item " + id);
+                }
+            }
+        }
+    }
+
+    private void refresh() {
+        updateRecentTasks();
+        updateRunningTasks();
+        if (mActivityDescriptions.size() == 0) {
+            // show "No Recent Takss"
+            mNoRecentsView.setVisibility(View.VISIBLE);
+            mCarouselView.setVisibility(View.GONE);
+        } else {
+            mNoRecentsView.setVisibility(View.GONE);
+            mCarouselView.setVisibility(View.VISIBLE);
+            mCarouselView.createCards(mActivityDescriptions.size());
+        }
+    }
+}
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index ecba1fe..4d4c799 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -46,6 +46,7 @@
 import android.os.Vibrator;
 import android.provider.Settings;
 
+import com.android.internal.R;
 import com.android.internal.policy.PolicyManager;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.telephony.ITelephony;
@@ -269,6 +270,7 @@
     Intent mHomeIntent;
     Intent mCarDockIntent;
     Intent mDeskDockIntent;
+    Intent mRecentAppsIntent;
     boolean mSearchKeyPressed;
     boolean mConsumeSearchKeyUp;
 
@@ -491,6 +493,16 @@
      * Create (if necessary) and launch the recent apps dialog
      */
     void showRecentAppsDialog() {
+        if (mRecentAppsIntent != null) {
+            try {
+                mContext.startActivity(mRecentAppsIntent);
+                return;
+            } catch (ActivityNotFoundException e) {
+                Log.e(TAG, "Failed to launch RecentAppsIntent", e);
+            }
+        }
+
+        // Fallback to dialog if we fail to launch the above.
         if (mRecentAppsDialog == null) {
             mRecentAppsDialog = new RecentApplicationsDialog(mContext);
         }
@@ -522,6 +534,18 @@
         mDeskDockIntent.addCategory(Intent.CATEGORY_DESK_DOCK);
         mDeskDockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+
+        boolean use3dRecents = mContext.getResources().getBoolean(R.bool.config_enableRecentApps3D);
+        if (use3dRecents) {
+            mRecentAppsIntent = new Intent();
+            mRecentAppsIntent.setClassName("com.android.systemui", 
+                    "com.android.systemui.statusbar.RecentApplicationsActivity");
+            mRecentAppsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 
+                    | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+        } else {
+            mRecentAppsIntent = null;
+        }
+
         PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
         mBroadcastWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                 "PhoneWindowManager.mBroadcastWakeLock");