Initial changes to support lock-to-app on the foremost task. (Bug 16221876)	
diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
index e375433..a9a606f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java
@@ -68,6 +68,7 @@
     // Recents service binding
     Handler mHandler;
     boolean mBootCompleted = false;
+    boolean mStartAnimationTriggered = false;
 
     // Task launching
     RecentsConfiguration mConfig;
@@ -252,6 +253,7 @@
      * Creates the activity options for a unknown state->recents transition.
      */
     ActivityOptions getUnknownTransitionActivityOptions() {
+        mStartAnimationTriggered = false;
         return ActivityOptions.makeCustomAnimation(mContext,
                 R.anim.recents_from_unknown_enter,
                 R.anim.recents_from_unknown_exit, mHandler, this);
@@ -261,6 +263,7 @@
      * Creates the activity options for a home->recents transition.
      */
     ActivityOptions getHomeTransitionActivityOptions() {
+        mStartAnimationTriggered = false;
         return ActivityOptions.makeCustomAnimation(mContext,
                 R.anim.recents_from_launcher_enter,
                 R.anim.recents_from_launcher_exit, mHandler, this);
@@ -279,6 +282,7 @@
             // Take the full screenshot
             sLastScreenshot = mSystemServicesProxy.takeAppScreenshot();
             if (sLastScreenshot != null) {
+                mStartAnimationTriggered = false;
                 return ActivityOptions.makeCustomAnimation(mContext,
                         R.anim.recents_from_app_enter,
                         R.anim.recents_from_app_exit, mHandler, this);
@@ -302,6 +306,7 @@
                 c.setBitmap(null);
                 // Recycle the old thumbnail
                 firstThumbnail.recycle();
+                mStartAnimationTriggered = false;
                 return ActivityOptions.makeThumbnailScaleDownAnimation(mStatusBarView,
                         thumbnail, toTaskRect.left, toTaskRect.top, this);
             }
@@ -449,9 +454,12 @@
     @Override
     public void onAnimationStarted() {
         // Notify recents to start the enter animation
-        Intent intent = new Intent(RecentsActivity.ACTION_START_ENTER_ANIMATION);
-        intent.setPackage(mContext.getPackageName());
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        mContext.sendBroadcast(intent);
+        if (!mStartAnimationTriggered) {
+            Intent intent = new Intent(RecentsActivity.ACTION_START_ENTER_ANIMATION);
+            intent.setPackage(mContext.getPackageName());
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+            mContext.sendBroadcast(intent);
+            mStartAnimationTriggered = true;
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index b039485..e62d989 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -92,6 +92,11 @@
     public int taskBarExitAnimDuration;
     public int taskBarDismissDozeDelaySeconds;
 
+    /** Lock to app */
+    public int taskViewLockToAppButtonHeight;
+    public int taskViewLockToAppShortAnimDuration;
+    public int taskViewLockToAppLongAnimDuration;
+
     /** Nav bar scrim */
     public int navBarScrimEnterDuration;
 
@@ -226,6 +231,14 @@
         taskBarDismissDozeDelaySeconds =
                 res.getInteger(R.integer.recents_task_bar_dismiss_delay_seconds);
 
+        // Lock to app
+        taskViewLockToAppButtonHeight =
+                res.getDimensionPixelSize(R.dimen.recents_task_view_lock_to_app_button_height);
+        taskViewLockToAppShortAnimDuration =
+                res.getInteger(R.integer.recents_animate_lock_to_app_button_short_duration);
+        taskViewLockToAppLongAnimDuration =
+                res.getInteger(R.integer.recents_animate_lock_to_app_button_long_duration);
+
         // Nav bar scrim
         navBarScrimEnterDuration =
                 res.getInteger(R.integer.recents_nav_bar_scrim_enter_duration);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index 05c0f58..b8beda6f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -17,8 +17,10 @@
 package com.android.systemui.recents.misc;
 
 import android.app.ActivityManager;
+import android.app.ActivityManagerNative;
 import android.app.ActivityOptions;
 import android.app.AppGlobals;
+import android.app.IActivityManager;
 import android.app.SearchManager;
 import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetManager;
@@ -66,6 +68,7 @@
     final static String TAG = "SystemServicesProxy";
 
     ActivityManager mAm;
+    IActivityManager mIam;
     AppWidgetManager mAwm;
     PackageManager mPm;
     IPackageManager mIpm;
@@ -83,6 +86,7 @@
     /** Private constructor */
     public SystemServicesProxy(Context context) {
         mAm = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+        mIam = ActivityManagerNative.getDefault();
         mAwm = AppWidgetManager.getInstance(context);
         mPm = context.getPackageManager();
         mUm = (UserManager) context.getSystemService(Context.USER_SERVICE);
@@ -407,6 +411,17 @@
     }
 
     /**
+     * Locks the current task.
+     */
+    public void lockCurrentTask() {
+        if (mIam == null) return;
+
+        try {
+            mIam.startLockTaskModeOnCurrent();
+        } catch (RemoteException e) {}
+    }
+
+    /**
      * Takes a screenshot of the current surface.
      */
     public Bitmap takeScreenshot() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
index 94474e9..86e8981 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoader.java
@@ -429,7 +429,8 @@
 
             // Create a new task
             Task task = new Task(t.persistentId, (t.id > -1), t.baseIntent, 0, activityLabel,
-                    activityIcon, activityColor, t.userId, t.firstActiveTime, t.lastActiveTime);
+                    activityIcon, activityColor, t.userId, t.firstActiveTime, t.lastActiveTime,
+                    (i == (taskCount - 1)));
 
             // Preload the specified number of apps
             if (i >= (taskCount - preloadCount)) {
@@ -523,7 +524,7 @@
             if (info == null) continue;
 
             stack.addTask(new Task(t.persistentId, true, t.baseIntent, 0, null, null, 0, 0,
-                    t.firstActiveTime, t.lastActiveTime));
+                    t.firstActiveTime, t.lastActiveTime, (i == (taskCount - 1))));
         }
         stack.createSimulatedAffiliatedGroupings();
         return stack;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 0667e4c..88e9f40 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -87,6 +87,7 @@
     public int colorPrimaryGreyscale;
     public Bitmap thumbnail;
     public boolean isActive;
+    public boolean canLockToTask;
     public int userId;
 
     TaskCallbacks mCb;
@@ -97,7 +98,7 @@
 
     public Task(int id, boolean isActive, Intent intent, int taskAffiliation, String activityTitle,
                 Drawable activityIcon, int colorPrimary, int userId,
-                long firstActiveTime, long lastActiveTime) {
+                long firstActiveTime, long lastActiveTime, boolean canLockToTask) {
         this.key = new TaskKey(id, intent, userId, firstActiveTime, lastActiveTime);
         this.taskAffiliation = taskAffiliation;
         this.activityLabel = activityTitle;
@@ -105,6 +106,7 @@
         this.colorPrimary = colorPrimary;
         this.colorPrimaryGreyscale = Utilities.colorToGreyscale(colorPrimary);
         this.isActive = isActive;
+        this.canLockToTask = canLockToTask;
         this.userId = userId;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
index 1ed0edd..7dd15a6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java
@@ -150,7 +150,7 @@
         /* Notifies when a task has been added to the stack */
         public void onStackTaskAdded(TaskStack stack, Task t);
         /* Notifies when a task has been removed from the stack */
-        public void onStackTaskRemoved(TaskStack stack, Task t);
+        public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask);
         /** Notifies when the stack was filtered */
         public void onStackFiltered(TaskStack newStack, ArrayList<Task> curTasks, Task t);
         /** Notifies when the stack was un-filtered */
@@ -203,9 +203,15 @@
             if (group.getTaskCount() == 0) {
                 removeGroup(group);
             }
+            // Update the lock-to-app state
+            Task newFrontMostTask = getFrontMostTask();
+            t.canLockToTask = false;
+            if (newFrontMostTask != null) {
+                newFrontMostTask.canLockToTask = true;
+            }
             if (mCb != null) {
                 // Notify that a task has been removed
-                mCb.onStackTaskRemoved(this, t);
+                mCb.onStackTaskRemoved(this, t, newFrontMostTask);
             }
         }
     }
@@ -226,7 +232,7 @@
             }
             if (mCb != null) {
                 // Notify that a task has been removed
-                mCb.onStackTaskRemoved(this, t);
+                mCb.onStackTaskRemoved(this, t, null);
             }
         }
         mTaskList.set(tasks);
@@ -239,6 +245,7 @@
 
     /** Gets the front task */
     public Task getFrontMostTask() {
+        if (mTaskList.size() == 0) return null;
         return mTaskList.getTasks().get(mTaskList.size() - 1);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index 85afb32..99b012e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -34,9 +34,10 @@
 import android.view.View;
 import android.view.WindowInsets;
 import android.widget.FrameLayout;
-import com.android.systemui.recents.misc.Console;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
+import com.android.systemui.recents.misc.Console;
+import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.model.RecentsPackageMonitor;
 import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.SpaceNode;
@@ -144,7 +145,7 @@
                             Console.log(Constants.Log.UI.Focus, "[RecentsView|launchFocusedTask]",
                                     "Found focused Task");
                         }
-                        onTaskViewClicked(stackView, tv, stack, task);
+                        onTaskViewClicked(stackView, tv, stack, task, false);
                         return true;
                     }
                 }
@@ -180,7 +181,7 @@
                             tv = stv;
                         }
                     }
-                    onTaskViewClicked(stackView, tv, stack, task);
+                    onTaskViewClicked(stackView, tv, stack, task, false);
                     return true;
                 }
             }
@@ -431,7 +432,7 @@
 
     @Override
     public void onTaskViewClicked(final TaskStackView stackView, final TaskView tv,
-                                  final TaskStack stack, final Task task) {
+                                  final TaskStack stack, final Task task, final boolean lockToTask) {
         // Notify any callbacks of the launching of a new task
         if (mCb != null) {
             mCb.onTaskViewClicked();
@@ -456,6 +457,8 @@
         }
 
         // Compute the thumbnail to scale up from
+        final SystemServicesProxy ssp =
+                RecentsTaskLoader.getInstance().getSystemServicesProxy();
         ActivityOptions opts = null;
         int thumbnailWidth = transform.rect.width();
         int thumbnailHeight = transform.rect.height();
@@ -469,8 +472,26 @@
                     new Rect(0, 0, task.thumbnail.getWidth(), task.thumbnail.getHeight()),
                     new Rect(0, 0, thumbnailWidth, thumbnailHeight), null);
             c.setBitmap(null);
+            ActivityOptions.OnAnimationStartedListener animStartedListener = null;
+            if (lockToTask) {
+                animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
+                    boolean mTriggered = false;
+                    @Override
+                    public void onAnimationStarted() {
+                        if (!mTriggered) {
+                            postDelayed(new Runnable() {
+                                @Override
+                                public void run() {
+                                    ssp.lockCurrentTask();
+                                }
+                            }, 350);
+                            mTriggered = true;
+                        }
+                    }
+                };
+            }
             opts = ActivityOptions.makeThumbnailScaleUpAnimation(sourceView,
-                    b, offsetX, offsetY);
+                    b, offsetX, offsetY, animStartedListener);
         }
 
         final ActivityOptions launchOpts = opts;
@@ -496,8 +517,12 @@
                         UserHandle taskUser = new UserHandle(task.userId);
                         if (launchOpts != null) {
                             getContext().startActivityAsUser(i, launchOpts.toBundle(), taskUser);
+
                         } else {
                             getContext().startActivityAsUser(i, taskUser);
+                            if (lockToTask) {
+                                ssp.lockCurrentTask();
+                            }
                         }
                     } catch (ActivityNotFoundException anfe) {
                         Console.logError(getContext(), "Could not start Activity");
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 35cf8ab..599c590 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -51,11 +51,12 @@
 /* The visual representation of a task stack view */
 public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
         TaskView.TaskViewCallbacks, ViewPool.ViewPoolConsumer<TaskView, Task>,
-        View.OnClickListener, RecentsPackageMonitor.PackageCallbacks {
+        RecentsPackageMonitor.PackageCallbacks {
 
     /** The TaskView callbacks */
     interface TaskStackViewCallbacks {
-        public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t);
+        public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t,
+                                      boolean lockToTask);
         public void onTaskViewAppInfoClicked(Task t);
         public void onTaskViewDismissed(Task t);
         public void onAllTaskViewsDismissed();
@@ -734,7 +735,8 @@
         for (int i = 0; i < childCount; i++) {
             TaskView t = (TaskView) getChildAt(i);
             t.measure(MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.width(), MeasureSpec.EXACTLY),
-                    MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.height(), MeasureSpec.EXACTLY));
+                    MeasureSpec.makeMeasureSpec(mStackAlgorithm.mTaskRect.height() +
+                            mConfig.taskViewLockToAppButtonHeight, MeasureSpec.EXACTLY));
         }
 
         setMeasuredDimension(width, height);
@@ -766,7 +768,7 @@
             TaskView t = (TaskView) getChildAt(i);
             t.layout(mStackAlgorithm.mTaskRect.left, mStackAlgorithm.mStackRectSansPeek.top,
                     mStackAlgorithm.mTaskRect.right, mStackAlgorithm.mStackRectSansPeek.top +
-                    mStackAlgorithm.mTaskRect.height());
+                    mStackAlgorithm.mTaskRect.height() + mConfig.taskViewLockToAppButtonHeight);
         }
 
         if (mAwaitingFirstLayout) {
@@ -903,11 +905,6 @@
         mUIDozeTrigger.poke();
     }
 
-    /** Disables handling touch on this task view. */
-    void setTouchOnTaskView(TaskView tv, boolean enabled) {
-        tv.setOnClickListener(enabled ? this : null);
-    }
-
     /**** TaskStackCallbacks Implementation ****/
 
     @Override
@@ -919,25 +916,33 @@
     }
 
     @Override
-    public void onStackTaskRemoved(TaskStack stack, Task t) {
+    public void onStackTaskRemoved(TaskStack stack, Task removedTask, Task newFrontMostTask) {
         // Update the task offsets
         mStackAlgorithm.updateTaskOffsets(mStack.getTasks());
 
         // Remove the view associated with this task, we can't rely on updateTransforms
         // to work here because the task is no longer in the list
-        TaskView tv = getChildViewForTask(t);
+        TaskView tv = getChildViewForTask(removedTask);
         if (tv != null) {
             mViewPool.returnViewToPool(tv);
         }
 
         // Notify the callback that we've removed the task and it can clean up after it
-        mCb.onTaskViewDismissed(t);
+        mCb.onTaskViewDismissed(removedTask);
 
         // Update the min/max scroll and animate other task views into their new positions
         updateMinMaxScroll(true);
         int movement = (int) mStackAlgorithm.getTaskOverlapHeight();
         requestSynchronizeStackViewsWithModel(Utilities.calculateTranslationAnimationDuration(movement));
 
+        // Update the new front most task
+        if (newFrontMostTask != null) {
+            TaskView frontTv = getChildViewForTask(newFrontMostTask);
+            if (frontTv != null) {
+                frontTv.onTaskBound(newFrontMostTask);
+            }
+        }
+
         // If there are no remaining tasks, then either unfilter the current stack, or just close
         // the activity if there are no filtered stacks
         if (mStack.getTaskCount() == 0) {
@@ -1086,7 +1091,7 @@
             addView(tv, insertIndex);
 
             // Set the callbacks and listeners for this new view
-            setTouchOnTaskView(tv, true);
+            tv.setTouchEnabled(true);
             tv.setCallbacks(this);
         } else {
             attachViewToParent(tv, insertIndex, tv.getLayoutParams());
@@ -1129,18 +1134,7 @@
     }
 
     @Override
-    public void onTaskViewDismissed(TaskView tv) {
-        Task task = tv.getTask();
-        // Remove the task from the view
-        mStack.removeTask(task);
-    }
-
-    /**** View.OnClickListener Implementation ****/
-
-    @Override
-    public void onClick(View v) {
-        TaskView tv = (TaskView) v;
-        Task task = tv.getTask();
+    public void onTaskViewClicked(TaskView tv, Task task, boolean lockToTask) {
         if (Console.Enabled) {
             Console.log(Constants.Log.UI.ClickEvents, "[TaskStack|Clicked|Thumbnail]",
                     task + " cb: " + mCb);
@@ -1150,10 +1144,17 @@
         mUIDozeTrigger.stopDozing();
 
         if (mCb != null) {
-            mCb.onTaskViewClicked(this, tv, mStack, task);
+            mCb.onTaskViewClicked(this, tv, mStack, task, lockToTask);
         }
     }
 
+    @Override
+    public void onTaskViewDismissed(TaskView tv) {
+        Task task = tv.getTask();
+        // Remove the task from the view
+        mStack.removeTask(task);
+    }
+
     /**** RecentsPackageMonitor.PackageCallbacks Implementation ****/
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
index 789b4f7..e1e682b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewLayoutAlgorithm.java
@@ -95,9 +95,11 @@
 
         if (numTasks <= 1) {
             // If there is only one task, then center the task in the stack rect (sans peek)
-            mMinScroll = mMaxScroll = -(stackHeight - taskHeight) / 2;
+            mMinScroll = mMaxScroll = -(stackHeight -
+                    (taskHeight + mConfig.taskViewLockToAppButtonHeight)) / 2;
         } else {
-            int maxScrollHeight = taskHeight + getStackScrollForTaskIndex(tasks.get(tasks.size() - 1));
+            int maxScrollHeight = getStackScrollForTaskIndex(tasks.get(tasks.size() - 1))
+                    + taskHeight + mConfig.taskViewLockToAppButtonHeight;
             mMinScroll = Math.min(stackHeight, maxScrollHeight) - stackHeight;
             mMaxScroll = maxScrollHeight - stackHeight;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index e186e2e..15ace13 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -377,7 +377,9 @@
         // Enable HW layers on that task
         tv.enableHwLayers();
         // Disallow touch events from this task view
-        mSv.setTouchOnTaskView(tv, false);
+        tv.setTouchEnabled(false);
+        // Hide the footer
+        tv.animateFooterVisibility(false, mSv.mConfig.taskViewLockToAppShortAnimDuration, 0);
         // Disallow parents from intercepting touch events
         final ViewParent parent = mSv.getParent();
         if (parent != null) {
@@ -413,7 +415,9 @@
         // Re-enable clipping with the stack
         tv.setClipViewInStack(true);
         // Re-enable touch events from this task view
-        mSv.setTouchOnTaskView(tv, true);
+        tv.setTouchEnabled(true);
+        // Restore the footer
+        tv.animateFooterVisibility(true, mSv.mConfig.taskViewLockToAppShortAnimDuration, 0);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index 33e3f58..125b018 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -35,6 +35,7 @@
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskStack;
 
 
 /* A task view */
@@ -44,11 +45,16 @@
     interface TaskViewCallbacks {
         public void onTaskViewAppIconClicked(TaskView tv);
         public void onTaskViewAppInfoClicked(TaskView tv);
+        public void onTaskViewClicked(TaskView tv, Task t, boolean lockToTask);
         public void onTaskViewDismissed(TaskView tv);
     }
 
     RecentsConfiguration mConfig;
 
+    int mFooterHeight;
+    int mMaxFooterHeight;
+    ObjectAnimator mFooterAnimator;
+
     int mDim;
     int mMaxDim;
     AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator();
@@ -60,9 +66,11 @@
     boolean mClipViewInStack;
     Rect mTmpRect = new Rect();
     Paint mLayerPaint = new Paint();
+    Outline mOutline = new Outline();
 
     TaskThumbnailView mThumbnailView;
     TaskBarView mBarView;
+    View mLockToAppButtonView;
     TaskViewCallbacks mCb;
 
     // Optimizations
@@ -102,9 +110,11 @@
     public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
         mConfig = RecentsConfiguration.getInstance();
+        mMaxFooterHeight = mConfig.taskViewLockToAppButtonHeight;
         setWillNotDraw(false);
         setClipToOutline(true);
         setDim(getDim());
+        setFooterHeight(getFooterHeight());
     }
 
     @Override
@@ -117,6 +127,7 @@
         // Bind the views
         mBarView = (TaskBarView) findViewById(R.id.task_view_bar);
         mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail);
+        mLockToAppButtonView = findViewById(R.id.lock_to_app);
 
         if (mTaskDataLoaded) {
             onTaskDataLoaded();
@@ -125,13 +136,33 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        int width = MeasureSpec.getSize(widthMeasureSpec);
+        int height = MeasureSpec.getSize(heightMeasureSpec);
 
-        // Update the outline
-        Outline o = new Outline();
-        o.setRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight(),
+        // Measure the bar view, thumbnail, and lock-to-app buttons
+        mBarView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(mConfig.taskBarHeight, MeasureSpec.EXACTLY));
+        mLockToAppButtonView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(mConfig.taskViewLockToAppButtonHeight,
+                        MeasureSpec.EXACTLY));
+        // Measure the thumbnail height to be the same as the width
+        mThumbnailView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+                MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY));
+        setMeasuredDimension(width, height);
+        updateOutline();
+    }
+
+    /** Updates the outline to match whether the lock-to-app button is visible or not. */
+    void updateOutline() {
+        int height = getMeasuredHeight();
+        if (height == 0) return;
+
+        // Account for the current footer height
+        height = height - mMaxFooterHeight + mFooterHeight;
+
+        mOutline.setRoundRect(0, 0, getMeasuredWidth(), height,
                 mConfig.taskViewRoundedCornerRadiusPx);
-        setOutline(o);
+        setOutline(mOutline);
     }
 
     /** Set callback */
@@ -289,6 +320,8 @@
                         // Animate the task bar of the first task view
                         mBarView.startEnterRecentsAnimation(0, mEnableThumbnailClip);
                         setVisibility(View.VISIBLE);
+                        // Animate the footer into view
+                        animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration, 0);
                         // Decrement the post animation trigger
                         ctx.postAnimationTrigger.decrement();
                     }
@@ -335,6 +368,10 @@
                 });
                 anim.start();
                 ctx.postAnimationTrigger.increment();
+
+                // Animate the footer into view
+                animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration,
+                        mConfig.taskBarEnterAnimDelay);
             } else {
                 mEnableThumbnailClip.run();
             }
@@ -366,9 +403,16 @@
                     })
                     .start();
             ctx.postAnimationTrigger.increment();
+
+            // Animate the footer into view
+            animateFooterVisibility(true, mConfig.taskViewEnterFromHomeDuration,
+                    mConfig.taskBarEnterAnimDelay);
         } else {
             // Otherwise, just enable the thumbnail clip
             mEnableThumbnailClip.run();
+
+            // Animate the footer into view
+            animateFooterVisibility(true, 0, 0);
         }
     }
 
@@ -457,12 +501,14 @@
     void enableHwLayers() {
         mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
         mBarView.enableHwLayers();
+        mLockToAppButtonView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
     }
 
     /** Disable the hw layers on this task view */
     void disableHwLayers() {
         mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
         mBarView.disableHwLayers();
+        mLockToAppButtonView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
     }
 
     /** Sets the stubbed state of this task view. */
@@ -499,6 +545,57 @@
         }
     }
 
+    /** Sets the footer height. */
+    public void setFooterHeight(int height) {
+        mFooterHeight = height;
+        updateOutline();
+        invalidate(0, getMeasuredHeight() - mMaxFooterHeight, getMeasuredWidth(),
+                getMeasuredHeight());
+    }
+
+    /** Gets the footer height. */
+    public int getFooterHeight() {
+        return mFooterHeight;
+    }
+
+    /** Animates the footer into and out of view. */
+    public void animateFooterVisibility(boolean visible, int duration, int delay) {
+        if (!mTask.canLockToTask) return;
+        if (mMaxFooterHeight <= 0) return;
+
+        if (mFooterAnimator != null) {
+            mFooterAnimator.removeAllListeners();
+            mFooterAnimator.cancel();
+        }
+        int height = visible ? mMaxFooterHeight : 0;
+        if (visible && mLockToAppButtonView.getVisibility() != View.VISIBLE) {
+            if (duration > 0) {
+                setFooterHeight(0);
+            } else {
+                setFooterHeight(mMaxFooterHeight);
+            }
+            mLockToAppButtonView.setVisibility(View.VISIBLE);
+        }
+        if (duration > 0) {
+            mFooterAnimator = ObjectAnimator.ofInt(this, "footerHeight", height);
+            mFooterAnimator.setDuration(duration);
+            mFooterAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
+            if (!visible) {
+                mFooterAnimator.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mLockToAppButtonView.setVisibility(View.INVISIBLE);
+                    }
+                });
+            }
+            mFooterAnimator.start();
+        } else {
+            if (!visible) {
+                mLockToAppButtonView.setVisibility(View.INVISIBLE);
+            }
+        }
+    }
+
     /** Returns the current dim. */
     public void setDim(int dim) {
         mDim = dim;
@@ -584,6 +681,11 @@
     public void onTaskBound(Task t) {
         mTask = t;
         mTask.setCallbacks(this);
+        if (getMeasuredWidth() == 0) {
+            animateFooterVisibility(t.canLockToTask, 0, 0);
+        } else {
+            animateFooterVisibility(t.canLockToTask, mConfig.taskViewLockToAppLongAnimDuration, 0);
+        }
     }
 
     @Override
@@ -597,6 +699,7 @@
                 mBarView.mApplicationIcon.setOnClickListener(this);
             }
             mBarView.mDismissButton.setOnClickListener(this);
+            mLockToAppButtonView.setOnClickListener(this);
             if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
                 if (mConfig.developerOptionsEnabled) {
                     mBarView.mApplicationIcon.setOnLongClickListener(this);
@@ -618,6 +721,7 @@
                 mBarView.mApplicationIcon.setOnClickListener(null);
             }
             mBarView.mDismissButton.setOnClickListener(null);
+            mLockToAppButtonView.setOnClickListener(null);
             if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
                 mBarView.mApplicationIcon.setOnLongClickListener(null);
             }
@@ -625,6 +729,11 @@
         mTaskDataLoaded = false;
     }
 
+    /** Enables/disables handling touch on this task view. */
+    void setTouchEnabled(boolean enabled) {
+        setOnClickListener(enabled ? this : null);
+    }
+
     @Override
     public void onClick(final View v) {
         // We purposely post the handler delayed to allow for the touch feedback to draw
@@ -642,6 +751,10 @@
                             mCb.onTaskViewDismissed(tv);
                         }
                     });
+                    // Hide the footer
+                    tv.animateFooterVisibility(false, mConfig.taskViewRemoveAnimDuration, 0);
+                } else if (v == tv || v == mLockToAppButtonView) {
+                    mCb.onTaskViewClicked(tv, tv.getTask(), (v == mLockToAppButtonView));
                 }
             }
         }, 125);