Modified focus state

Change-Id: If5e5723ea3b16e7e9805eba9208a85557565b360
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 47e24e8..2b8ca89 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -10,6 +10,16 @@
   public void setGlowScale(float);
 }
 
+-keep class com.android.systemui.recents.views.TaskView {
+  public void setHighlightProgress(float);
+  public float getHighlightProgress();
+}
+
+-keep class com.android.systemui.recents.views.TaskViewHeader {
+  public void setFocusProgress(float);
+  public float getFocusProgress();
+}
+
 -keep class com.android.systemui.statusbar.phone.PhoneStatusBar
 -keep class com.android.systemui.statusbar.tv.TvStatusBar
 -keep class com.android.systemui.recents.*
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 0dcbe88..3438502 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -26,6 +26,7 @@
     <color name="notification_panel_solid_background">#ff000000</color>
     <drawable name="status_bar_recents_app_thumbnail_background">#88000000</drawable>
     <color name="status_bar_recents_app_label_color">#ffffffff</color>
+    <color name="status_bar_recents_highlight_color">#ff009688</color>
     <drawable name="status_bar_notification_row_background_color">#ff090909</drawable>
     <color name="notification_list_shadow_top">#80000000</color>
     <drawable name="recents_callout_line">#99ffffff</drawable>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 5d06768..e00b1f4 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -167,7 +167,7 @@
     <integer name="recents_enter_from_app_transition_duration">325</integer>
 
     <!-- The duration for animating the task decorations in after transitioning from an app. -->
-    <integer name="recents_task_enter_from_app_duration">200</integer>
+    <integer name="recents_task_enter_from_app_duration">350</integer>
 
     <!-- The duration for animating the task decorations out before transitioning to an app. -->
     <integer name="recents_task_exit_to_app_duration">125</integer>
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 3c7d48c..4214558 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -409,7 +409,7 @@
         // Get the first stack view
         List<TaskStackView> stackViews = getTaskStackViews();
         if (!stackViews.isEmpty()) {
-            stackViews.get(0).focusNextTask(forward, true);
+            stackViews.get(0).focusNextTask(forward);
         }
     }
 
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 a8a18fc..571a9ef 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -218,6 +218,8 @@
             mUIDozeTrigger.resetTrigger();
         }
         mStackScroller.reset();
+
+        mStartEnterAnimationCompleted = false;
     }
 
     /** Requests that the views be synchronized with the model */
@@ -500,26 +502,34 @@
     }
 
     /** Focuses the task at the specified index in the stack */
-    void focusTask(int taskIndex, boolean scrollToNewPosition, final boolean animateFocusedState) {
+    void focusTask(int taskIndex, boolean scrollToNewPosition) {
         // Return early if the task is already focused
         if (taskIndex == mFocusedTaskIndex) return;
 
         if (0 <= taskIndex && taskIndex < mStack.getTaskCount()) {
+            resetFocusedTask();
             mFocusedTaskIndex = taskIndex;
             mPrevAccessibilityFocusedIndex = taskIndex;
 
             // Focus the view if possible, otherwise, focus the view after we scroll into position
+            Runnable postScrollRunnable = null;
             final Task t = mStack.getTasks().get(mFocusedTaskIndex);
-            Runnable postScrollRunnable = new Runnable() {
-                @Override
-                public void run() {
-                    TaskView tv = getChildViewForTask(t);
-                    if (tv != null) {
-                        tv.setFocusedTask(animateFocusedState);
-                        tv.requestAccessibilityFocus();
+            final TaskView tv = getChildViewForTask(t);
+            if (tv != null) {
+                tv.setFocusedTask();
+                tv.requestAccessibilityFocus();
+            } else {
+                postScrollRunnable = new Runnable() {
+                    @Override
+                    public void run() {
+                        TaskView tv = getChildViewForTask(t);
+                        if (tv != null) {
+                            tv.setFocusedTask();
+                            tv.requestAccessibilityFocus();
+                        }
                     }
-                }
-            };
+                };
+            }
 
             // Scroll the view into position (just center it in the curve)
             if (scrollToNewPosition) {
@@ -574,7 +584,7 @@
      *                            the change in focus, as well as whether to scroll to fit the
      *                            task into view.
      */
-    public void focusNextTask(boolean forward, boolean animateFocusedState) {
+    public void focusNextTask(boolean forward) {
         // Find the next index to focus
         int numTasks = mStack.getTaskCount();
         if (numTasks == 0) return;
@@ -587,7 +597,7 @@
             newIndex = 0;
         }
 
-        focusTask(newIndex, true, animateFocusedState);
+        focusTask(newIndex, true);
     }
 
     /** Dismisses the focused task. */
@@ -663,14 +673,14 @@
             switch (action) {
                 case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
                     if (mPrevAccessibilityFocusedIndex > 0) {
-                        focusNextTask(true, false);
+                        focusNextTask(true);
                         return true;
                     }
                 }
                 break;
                 case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
                     if (mPrevAccessibilityFocusedIndex < mStack.getTaskCount() - 1) {
-                        focusNextTask(false, false);
+                        focusNextTask(false);
                         return true;
                     }
                 }
@@ -869,11 +879,9 @@
         // enter animation).
         if (mConfig.launchedWithAltTab) {
             if (mConfig.launchedFromAppWithThumbnail) {
-                focusTask(Math.max(0, mStack.getTaskCount() - 2), false,
-                        mConfig.launchedHasConfigurationChanged);
+                focusTask(Math.max(0, mStack.getTaskCount() - 2), false);
             } else {
-                focusTask(Math.max(0, mStack.getTaskCount() - 1), false,
-                        mConfig.launchedHasConfigurationChanged);
+                focusTask(Math.max(0, mStack.getTaskCount() - 1), false);
             }
         }
 
@@ -948,7 +956,7 @@
                             0 <= mFocusedTaskIndex && mFocusedTaskIndex < tasks.size()) {
                         TaskView tv = getChildViewForTask(tasks.get(mFocusedTaskIndex));
                         if (tv != null) {
-                            tv.setFocusedTask(true);
+                            tv.setFocusedTask();
                         }
                     }
 
@@ -1404,7 +1412,7 @@
                 if (nextTv != null) {
                     // Focus the next task, and only animate the visible state if we are launched
                     // from Alt-Tab
-                    nextTv.setFocusedTask(mConfig.launchedWithAltTab);
+                    nextTv.setFocusedTask();
                 }
             }
         }
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 be618e2..25fb71e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -420,11 +420,11 @@
                     float vScroll = ev.getAxisValue(MotionEvent.AXIS_VSCROLL);
                     if (vScroll > 0) {
                         if (mSv.ensureFocusedTask(true)) {
-                            mSv.focusNextTask(true, false);
+                            mSv.focusNextTask(true);
                         }
                     } else {
                         if (mSv.ensureFocusedTask(true)) {
-                            mSv.focusNextTask(false, false);
+                            mSv.focusNextTask(false);
                         }
                     }
                     return true;
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 5ad8e69..55aba75 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -5,7 +5,7 @@
  * 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
+ * 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,
@@ -26,6 +26,9 @@
 import android.view.View;
 import android.view.ViewOutlineProvider;
 import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.OvershootInterpolator;
 import android.widget.FrameLayout;
 
 import com.android.internal.logging.MetricsLogger;
@@ -48,7 +51,6 @@
         public void onTaskViewDismissed(TaskView tv);
         public void onTaskViewClipStateChanged(TaskView tv);
         public void onTaskViewFocusChanged(TaskView tv, boolean focused);
-
         public void onTaskResize(TaskView tv);
     }
 
@@ -76,6 +78,22 @@
     View mActionButtonView;
     TaskViewCallbacks mCb;
 
+    // Focus animation
+    float mHighlightProgress;
+    Paint mHighlightPaint = new Paint();
+    int mHighlightColor = 0xff009688;
+    int mHighlightAlpha = 0x50;
+    static Interpolator sHighlightInInterpolator = new OvershootInterpolator(2);
+    static Interpolator sHighlightInRadiusInterpolator = new DecelerateInterpolator();
+    static Interpolator sHighlightOutInterpolator = new DecelerateInterpolator();
+    ObjectAnimator mHighlightAnimator;
+    static long sHighlightInDurationMs = 350;
+    static long sHighlightOutDurationMs = 200;
+    float mHighlightInCircleRadiusProgress;
+    int mHighlightInFillAlpha;
+    int mHighlightInCircleAlpha;
+    int mHighlightOutFillAlpha;
+
     // Optimizations
     ValueAnimator.AnimatorUpdateListener mUpdateDimListener =
             new ValueAnimator.AnimatorUpdateListener() {
@@ -85,7 +103,6 @@
                 }
             };
 
-
     public TaskView(Context context) {
         this(context, null);
     }
@@ -110,6 +127,11 @@
             setBackground(new FakeShadowDrawable(context.getResources(), mConfig));
         }
         setOutlineProvider(mViewBounds);
+
+        mHighlightAnimator = ObjectAnimator.ofFloat(this, "highlightProgress", 1f);
+        mHighlightColor =
+                context.getResources().getColor(R.color.status_bar_recents_highlight_color);
+        mHighlightPaint.setColor(mHighlightColor);
     }
 
     /** Set callback */
@@ -185,7 +207,7 @@
     }
 
     void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration,
-                                             ValueAnimator.AnimatorUpdateListener updateCallback) {
+            ValueAnimator.AnimatorUpdateListener updateCallback) {
         // Apply the transform
         toTransform.applyToTaskView(this, duration, mConfig.fastOutSlowInInterpolator, false,
                 !mConfig.fakeShadows, updateCallback);
@@ -235,10 +257,12 @@
         fromTransform.alpha = 0f;
     }
 
-    /** Prepares this task view for the enter-recents animations.  This is called earlier in the
-     * first layout because the actual animation into recents may take a long time. */
+    /**
+     * Prepares this task view for the enter-recents animations. This is called earlier in the
+     * first layout because the actual animation into recents may take a long time.
+     */
     void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask,
-                                             boolean occludesLaunchTarget, int offscreenY) {
+            boolean occludesLaunchTarget, int offscreenY) {
         int initialDim = getDim();
         if (mConfig.launchedHasConfigurationChanged) {
             // Just load the views as-is
@@ -299,7 +323,8 @@
             } else {
                 // Animate the task up if it was occluding the launch target
                 if (ctx.currentTaskOccludesLaunchTarget) {
-                    setTranslationY(transform.translationY + mConfig.taskViewAffiliateGroupEnterOffsetPx);
+                    setTranslationY(transform.translationY
+                            + mConfig.taskViewAffiliateGroupEnterOffsetPx);
                     setAlpha(0f);
                     animate().alpha(1f)
                             .translationY(transform.translationY)
@@ -424,13 +449,15 @@
             // If this is another view in the task grouping and is in front of the launch task,
             // animate it away first
             if (occludesLaunchTarget) {
-                animate().alpha(0f)
-                    .translationY(getTranslationY() + mConfig.taskViewAffiliateGroupEnterOffsetPx)
-                    .setStartDelay(0)
-                    .setUpdateListener(null)
-                    .setInterpolator(mConfig.fastOutLinearInInterpolator)
-                    .setDuration(mConfig.taskViewExitToAppDuration)
-                    .start();
+                animate()
+                        .alpha(0f)
+                        .translationY(
+                                getTranslationY() + mConfig.taskViewAffiliateGroupEnterOffsetPx)
+                        .setStartDelay(0)
+                        .setUpdateListener(null)
+                        .setInterpolator(mConfig.fastOutLinearInInterpolator)
+                        .setDuration(mConfig.taskViewExitToAppDuration)
+                        .start();
             }
         }
     }
@@ -441,23 +468,23 @@
         setClipViewInStack(false);
 
         animate().translationX(mConfig.taskViewRemoveAnimTranslationXPx)
-            .alpha(0f)
-            .setStartDelay(delay)
-            .setUpdateListener(null)
-            .setInterpolator(mConfig.fastOutSlowInInterpolator)
-            .setDuration(mConfig.taskViewRemoveAnimDuration)
-            .withEndAction(new Runnable() {
-                @Override
-                public void run() {
-                    if (r != null) {
-                        r.run();
-                    }
+                .alpha(0f)
+                .setStartDelay(delay)
+                .setUpdateListener(null)
+                .setInterpolator(mConfig.fastOutSlowInInterpolator)
+                .setDuration(mConfig.taskViewRemoveAnimDuration)
+                .withEndAction(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (r != null) {
+                            r.run();
+                        }
 
-                    // Re-enable clipping with the stack (we will reuse this view)
-                    setClipViewInStack(true);
-                }
-            })
-            .start();
+                        // Re-enable clipping with the stack (we will reuse this view)
+                        setClipViewInStack(true);
+                    }
+                })
+                .start();
     }
 
     /** Enables/disables handling touch on this task view. */
@@ -470,12 +497,18 @@
         mHeaderView.startNoUserInteractionAnimation();
     }
 
-    /** Mark this task view that the user does has not interacted with the stack after a certain time. */
+    /**
+     * Mark this task view that the user does has not interacted with the stack after a certain
+     * time.
+     */
     void setNoUserInteractionState() {
         mHeaderView.setNoUserInteractionState();
     }
 
-    /** Resets the state tracking that the user has not interacted with the stack after a certain time. */
+    /**
+     * Resets the state tracking that the user has not interacted with the stack after a certain
+     * time.
+     */
     void resetNoUserInteractionState() {
         mHeaderView.resetNoUserInteractionState();
     }
@@ -512,6 +545,47 @@
         }
     }
 
+    @Override
+    public void dispatchDraw(Canvas canvas) {
+        super.dispatchDraw(canvas);
+        if (mIsFocused) {
+            final int x = getWidth() / 2;
+            final int y = getHeight() / 2;
+            final float hypot = (float) Math.hypot(x, y);
+            final float radius = hypot * mHighlightInCircleRadiusProgress;
+
+            mHighlightPaint.setAlpha(mHighlightInFillAlpha);
+            canvas.drawRect(0, 0, getWidth(), getHeight(), mHighlightPaint);
+
+            mHighlightPaint.setAlpha(mHighlightInCircleAlpha);
+            canvas.drawCircle(x, y, radius, mHighlightPaint);
+        } else {
+            mHighlightPaint.setAlpha(mHighlightOutFillAlpha);
+            canvas.drawRect(0, 0, getWidth(), getHeight(), mHighlightPaint);
+        }
+    }
+
+    public void setHighlightProgress(float progress) {
+        mHighlightProgress = progress;
+        if (mIsFocused) {
+            final float interpolatedProgress = sHighlightInInterpolator.getInterpolation(progress);
+            mHighlightInCircleRadiusProgress =
+                    0.5f + sHighlightInRadiusInterpolator.getInterpolation(progress);
+            mHighlightInCircleAlpha = (int) (mHighlightAlpha * interpolatedProgress);
+            mHighlightInFillAlpha =
+                    (mHighlightAlpha / 4) + (int) ((mHighlightAlpha / 2) * interpolatedProgress);
+        } else {
+            final float interpolatedProgress = sHighlightOutInterpolator.getInterpolation(progress);
+            mHighlightOutFillAlpha = (int) (mHighlightAlpha * (1 - interpolatedProgress));
+        }
+
+        invalidate();
+    }
+
+    public float getHighlightProgress() {
+        return mHighlightProgress;
+    }
+
     /** Sets the current task progress. */
     public void setTaskProgress(float p) {
         mTaskProgress = p;
@@ -583,12 +657,13 @@
      * if the view is not currently visible, or we are in touch state (where we still want to keep
      * track of focus).
      */
-    public void setFocusedTask(boolean animateFocusedState) {
-        mIsFocused = true;
-        if (mFocusAnimationsEnabled) {
-            // Focus the header bar
-            mHeaderView.onTaskViewFocusChanged(true, animateFocusedState);
+    public void setFocusedTask() {
+        if (mIsFocused) {
+            return;
         }
+        mIsFocused = true;
+
+        performFocusAnimation();
         // Update the thumbnail alpha with the focus
         mThumbnailView.onFocusChanged(true);
         // Call the callback
@@ -604,14 +679,31 @@
         invalidate();
     }
 
+    private void performFocusAnimation() {
+        if (mFocusAnimationsEnabled) {
+            // Focus the header bar
+            mHeaderView.onTaskViewFocusChanged(true);
+
+            mHighlightAnimator.setDuration(sHighlightInDurationMs);
+            mHighlightAnimator.start();
+        }
+    }
+
     /**
      * Unsets the focused task explicitly.
      */
     void unsetFocusedTask() {
+        if (!mIsFocused) {
+            return;
+        }
+
         mIsFocused = false;
         if (mFocusAnimationsEnabled) {
             // Un-focus the header bar
-            mHeaderView.onTaskViewFocusChanged(false, true);
+            mHeaderView.onTaskViewFocusChanged(false);
+
+            mHighlightAnimator.setDuration(sHighlightOutDurationMs);
+            mHighlightAnimator.start();
         }
 
         // Update the thumbnail alpha with the focus
@@ -645,9 +737,9 @@
     void enableFocusAnimations() {
         boolean wasFocusAnimationsEnabled = mFocusAnimationsEnabled;
         mFocusAnimationsEnabled = true;
-        if (mIsFocused && !wasFocusAnimationsEnabled) {
+        if (mIsFocused) {
             // Re-notify the header if we were focused and animations were not previously enabled
-            mHeaderView.onTaskViewFocusChanged(true, true);
+            performFocusAnimation();
         }
     }
 
@@ -719,7 +811,7 @@
     /**** View.OnClickListener Implementation ****/
 
     @Override
-     public void onClick(final View v) {
+    public void onClick(final View v) {
         final TaskView tv = this;
         final boolean delayViewClick = (v != this) && (v != mActionButtonView);
         if (delayViewClick) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
index 29aef69..cfe5a4e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java
@@ -5,7 +5,7 @@
  * 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
+ * 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,
@@ -42,6 +42,7 @@
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.TextView;
+
 import com.android.systemui.R;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.RecentsConfiguration;
@@ -50,13 +51,16 @@
 import com.android.systemui.recents.model.RecentsTaskLoader;
 import com.android.systemui.recents.model.Task;
 
-
 /* The task bar view */
 public class TaskViewHeader extends FrameLayout {
 
     RecentsConfiguration mConfig;
     private SystemServicesProxy mSsp;
 
+    Task mTask;
+    boolean mIsFocused;
+    float mFocusProgress;
+
     // Header views
     ImageView mMoveTaskButton;
     ImageView mDismissButton;
@@ -71,7 +75,7 @@
     Drawable mDarkDismissDrawable;
     RippleDrawable mBackground;
     GradientDrawable mBackgroundColorDrawable;
-    AnimatorSet mFocusAnimator;
+    ValueAnimator mFocusAnimator;
     String mDismissContentDescription;
 
     // Static highlight that we draw at the top of each view
@@ -123,6 +127,8 @@
             sHighlightPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
             sHighlightPaint.setAntiAlias(true);
         }
+
+        mFocusAnimator = ObjectAnimator.ofFloat(this, "focusProgress", 1f);
     }
 
     @Override
@@ -180,14 +186,9 @@
         }
     }
 
-    /** Returns the secondary color for a primary color. */
-    int getSecondaryColor(int primaryColor, boolean useLightOverlayColor) {
-        int overlayColor = useLightOverlayColor ? Color.WHITE : Color.BLACK;
-        return Utilities.getColorWithOverlay(primaryColor, overlayColor, 0.8f);
-    }
-
     /** Binds the bar view to the task */
     public void rebindToTask(Task t) {
+        mTask = t;
         // If an activity icon is defined, then we use that as the primary icon to show in the bar,
         // otherwise, we fall back to the application icon
         if (t.activityIcon != null) {
@@ -292,7 +293,10 @@
         }
     }
 
-    /** Mark this task view that the user does has not interacted with the stack after a certain time. */
+    /**
+     * Mark this task view that the user does has not interacted with the stack after a certain
+     * time.
+     */
     void setNoUserInteractionState() {
         if (mDismissButton.getVisibility() != View.VISIBLE) {
             mDismissButton.animate().cancel();
@@ -301,7 +305,10 @@
         }
     }
 
-    /** Resets the state tracking that the user has not interacted with the stack after a certain time. */
+    /**
+     * Resets the state tracking that the user has not interacted with the stack after a certain
+     * time.
+     */
     void resetNoUserInteractionState() {
         mDismissButton.setVisibility(View.INVISIBLE);
     }
@@ -336,92 +343,43 @@
         setLayerType(LAYER_TYPE_NONE, null);
     }
 
+    public void setFocusProgress(float progress) {
+        mFocusProgress = progress;
+        final ArgbEvaluator colorEvaluator = new ArgbEvaluator();
+        int desaturatedColor = calculateDesaurateColor(mBackgroundColor);
+        int color = (Integer) colorEvaluator.evaluate(progress, mBackgroundColor,
+                mIsFocused ? desaturatedColor : mTask.colorPrimary);
+        mBackgroundColorDrawable.setColor(color);
+        mBackgroundColor = color;
+
+        TaskViewHeader.this.setTranslationZ((mIsFocused ? progress : (1 - progress)) * 4);
+    }
+
+    private int calculateDesaurateColor(int saturatedColor) {
+        final float red = 0.2126f * Color.red(saturatedColor);
+        final float green = 0.7152f * Color.green(saturatedColor);
+        final float blue = 0.0722f * Color.blue(saturatedColor);
+        final int sum = (int) (red + green + blue);
+
+        return Color.argb(255, sum, sum, sum);
+    }
+
+    public float getFocusProgress() {
+        return mFocusProgress;
+    }
+
     /** Notifies the associated TaskView has been focused. */
-    void onTaskViewFocusChanged(boolean focused, boolean animateFocusedState) {
-        // If we are not animating the visible state, just return
-        if (!animateFocusedState) return;
+    void onTaskViewFocusChanged(final boolean focused) {
+        mIsFocused = focused;
 
-        boolean isRunning = false;
-        if (mFocusAnimator != null) {
-            isRunning = mFocusAnimator.isRunning();
-            Utilities.cancelAnimationWithoutCallbacks(mFocusAnimator);
-        }
-
-        if (focused) {
-            int currentColor = mBackgroundColor;
-            int secondaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
-            int[][] states = new int[][] {
-                    new int[] {},
-                    new int[] { android.R.attr.state_enabled },
-                    new int[] { android.R.attr.state_pressed }
-            };
-            int[] newStates = new int[]{
-                    0,
-                    android.R.attr.state_enabled,
-                    android.R.attr.state_pressed
-            };
-            int[] colors = new int[] {
-                    currentColor,
-                    secondaryColor,
-                    secondaryColor
-            };
-            mBackground.setColor(new ColorStateList(states, colors));
-            mBackground.setState(newStates);
-            // Pulse the background color
-            int lightPrimaryColor = getSecondaryColor(mCurrentPrimaryColor, mCurrentPrimaryColorIsDark);
-            ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
-                    currentColor, lightPrimaryColor);
-            backgroundColor.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationStart(Animator animation) {
-                    mBackground.setState(new int[]{});
-                }
-            });
-            backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                @Override
-                public void onAnimationUpdate(ValueAnimator animation) {
-                    int color = (int) animation.getAnimatedValue();
-                    mBackgroundColorDrawable.setColor(color);
-                    mBackgroundColor = color;
-                }
-            });
-            backgroundColor.setRepeatCount(ValueAnimator.INFINITE);
-            backgroundColor.setRepeatMode(ValueAnimator.REVERSE);
-            // Pulse the translation
-            ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 15f);
-            translation.setRepeatCount(ValueAnimator.INFINITE);
-            translation.setRepeatMode(ValueAnimator.REVERSE);
-
-            mFocusAnimator = new AnimatorSet();
-            mFocusAnimator.playTogether(backgroundColor, translation);
-            mFocusAnimator.setStartDelay(150);
-            mFocusAnimator.setDuration(750);
-            mFocusAnimator.start();
-        } else {
-            if (isRunning) {
-                // Restore the background color
-                int currentColor = mBackgroundColor;
-                ValueAnimator backgroundColor = ValueAnimator.ofObject(new ArgbEvaluator(),
-                        currentColor, mCurrentPrimaryColor);
-                backgroundColor.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-                    @Override
-                    public void onAnimationUpdate(ValueAnimator animation) {
-                        int color = (int) animation.getAnimatedValue();
-                        mBackgroundColorDrawable.setColor(color);
-                        mBackgroundColor = color;
-                    }
-                });
-                // Restore the translation
-                ObjectAnimator translation = ObjectAnimator.ofFloat(this, "translationZ", 0f);
-
-                mFocusAnimator = new AnimatorSet();
-                mFocusAnimator.playTogether(backgroundColor, translation);
-                mFocusAnimator.setDuration(150);
-                mFocusAnimator.start();
-            } else {
-                mBackground.setState(new int[] {});
-                setTranslationZ(0f);
-            }
-        }
+        mFocusAnimator.setDuration(
+                mIsFocused ?
+                        TaskView.sHighlightInDurationMs :
+                        TaskView.sHighlightOutDurationMs);
+        mFocusAnimator.setInterpolator(
+                mIsFocused ?
+                        TaskView.sHighlightInRadiusInterpolator :
+                        TaskView.sHighlightOutInterpolator);
+        mFocusAnimator.start();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index 117a7d3..d5862bc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -18,7 +18,9 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.BitmapShader;
@@ -32,6 +34,11 @@
 import android.graphics.Shader;
 import android.util.AttributeSet;
 import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.view.animation.OvershootInterpolator;
+
+import com.android.systemui.R;
 import com.android.systemui.recents.RecentsConfiguration;
 import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.model.Task;
@@ -57,8 +64,8 @@
     // Thumbnail alpha
     float mThumbnailAlpha;
     ValueAnimator mThumbnailAlphaAnimator;
-    ValueAnimator.AnimatorUpdateListener mThumbnailAlphaUpdateListener
-            = new ValueAnimator.AnimatorUpdateListener() {
+    ValueAnimator.AnimatorUpdateListener mThumbnailAlphaUpdateListener =
+            new ValueAnimator.AnimatorUpdateListener() {
         @Override
         public void onAnimationUpdate(ValueAnimator animation) {
             mThumbnailAlpha = (float) animation.getAnimatedValue();