Merge "Introduce AmbientState for StackScroller."
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index e5168c4..4369741 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -18,11 +18,13 @@
 <resources>
     <item type="id" name="translation_y_animator_tag"/>
     <item type="id" name="translation_z_animator_tag"/>
+    <item type="id" name="scale_animator_tag"/>
     <item type="id" name="alpha_animator_tag"/>
     <item type="id" name="top_inset_animator_tag"/>
     <item type="id" name="height_animator_tag"/>
     <item type="id" name="translation_y_animator_end_value_tag"/>
     <item type="id" name="translation_z_animator_end_value_tag"/>
+    <item type="id" name="scale_animator_end_value_tag"/>
     <item type="id" name="alpha_animator_end_value_tag"/>
     <item type="id" name="top_inset_animator_end_value_tag"/>
     <item type="id" name="height_animator_end_value_tag"/>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index 1c88ea7..c81a3c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -38,6 +38,7 @@
 public abstract class ActivatableNotificationView extends ExpandableOutlineView {
 
     private static final long DOUBLETAP_TIMEOUT_MS = 1000;
+    private static final int BACKGROUND_ANIMATION_LENGTH_MS = 220;
 
     private boolean mDimmed;
 
@@ -179,7 +180,7 @@
             mActivated = false;
         }
         if (mOnActivatedListener != null) {
-            mOnActivatedListener.onReset(this);
+            mOnActivatedListener.onActivationReset(this);
         }
         removeCallbacks(mTapTimeoutRunnable);
     }
@@ -189,12 +190,6 @@
                 && Math.abs(event.getY() - mDownY) < mTouchSlop;
     }
 
-    /**
-     * Sets the notification as dimmed, meaning that it will appear in a more gray variant.
-     *
-     * @param dimmed Whether the notification should be dimmed.
-     * @param fade Whether an animation should be played to change the state.
-     */
     public void setDimmed(boolean dimmed, boolean fade) {
         if (mDimmed != dimmed) {
             mDimmed = dimmed;
@@ -226,7 +221,7 @@
         }
         int startAlpha = mDimmed ? 255 : 0;
         int endAlpha = mDimmed ? 0 : 255;
-        int duration = NotificationActivator.ANIMATION_LENGTH_MS;
+        int duration = BACKGROUND_ANIMATION_LENGTH_MS;
         // Check whether there is already a background animation running.
         if (mBackgroundAnimator != null) {
             startAlpha = (Integer) mBackgroundAnimator.getAnimatedValue();
@@ -313,8 +308,8 @@
     }
 
     @Override
-    public void setActualHeight(int actualHeight) {
-        super.setActualHeight(actualHeight);
+    public void setActualHeight(int actualHeight, boolean notifyListeners) {
+        super.setActualHeight(actualHeight, notifyListeners);
         invalidate();
         setPivotY(actualHeight / 2);
     }
@@ -331,6 +326,6 @@
 
     public interface OnActivatedListener {
         void onActivated(View view);
-        void onReset(View view);
+        void onActivationReset(View view);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index a325186..3b53667 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -1067,7 +1067,6 @@
                     entry.row.setSystemExpanded(top);
                 }
             }
-            entry.row.setDimmed(onKeyguard, false /* fade */);
             boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification);
             if (onKeyguard && (visibleNotifications >= maxKeyguardNotifications
                     || !showOnKeyguard)) {
@@ -1087,48 +1086,11 @@
 
         if (onKeyguard && mKeyguardIconOverflowContainer.getIconsView().getChildCount() > 0) {
             mKeyguardIconOverflowContainer.setVisibility(View.VISIBLE);
-            mKeyguardIconOverflowContainer.setDimmed(true /* dimmed */, false /* fade */);
         } else {
             mKeyguardIconOverflowContainer.setVisibility(View.GONE);
         }
     }
 
-    @Override
-    public void onActivated(View view) {
-        int n = mNotificationData.size();
-        for (int i = 0; i < n; i++) {
-            NotificationData.Entry entry = mNotificationData.get(i);
-            if (entry.row.getVisibility() != View.GONE) {
-                if (view == entry.row) {
-                    entry.row.getActivator().activate();
-                } else {
-                    entry.row.getActivator().activateInverse();
-                }
-            }
-        }
-        if (mKeyguardIconOverflowContainer.getVisibility() != View.GONE) {
-            if (view == mKeyguardIconOverflowContainer) {
-                mKeyguardIconOverflowContainer.getActivator().activate();
-            } else {
-                mKeyguardIconOverflowContainer.getActivator().activateInverse();
-            }
-        }
-    }
-
-    @Override
-    public void onReset(View view) {
-        int n = mNotificationData.size();
-        for (int i = 0; i < n; i++) {
-            NotificationData.Entry entry = mNotificationData.get(i);
-            if (entry.row.getVisibility() != View.GONE) {
-                entry.row.getActivator().reset();
-            }
-        }
-        if (mKeyguardIconOverflowContainer.getVisibility() != View.GONE) {
-            mKeyguardIconOverflowContainer.getActivator().reset();
-        }
-    }
-
     private boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
         return sbn.getNotification().priority >= Notification.PRIORITY_LOW;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
index e471754..5b2ea0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DragDownHelper.java
@@ -117,7 +117,7 @@
                 } else {
                     if (mDraggedFarEnough) {
                         mDraggedFarEnough = false;
-                        mOnDragDownListener.onReset();
+                        mOnDragDownListener.onDragDownReset();
                     }
                 }
                 return true;
@@ -188,7 +188,7 @@
             cancelExpansion(mStartingChild);
         }
         mDraggingDown = false;
-        mOnDragDownListener.onReset();
+        mOnDragDownListener.onDragDownReset();
     }
 
     private ExpandableView findView(float x, float y) {
@@ -200,7 +200,7 @@
 
     public interface OnDragDownListener {
         void onDraggedDown(View startingChild);
-        void onReset();
+        void onDragDownReset();
         void onThresholdReached();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index e5512a3..39f2bb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -52,7 +52,6 @@
     private NotificationContentView mPublicLayout;
     private NotificationContentView mPrivateLayout;
     private int mMaxExpandHeight;
-    private NotificationActivator mActivator;
 
     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -63,8 +62,6 @@
         super.onFinishInflate();
         mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
         mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
-
-        mActivator = new NotificationActivator(this, this);
     }
 
     @Override
@@ -208,23 +205,10 @@
         mPrivateLayout.setVisibility(show ? View.GONE : View.VISIBLE);
     }
 
-    /**
-     * Sets the notification as dimmed, meaning that it will appear in a more gray variant.
-     */
-    @Override
-    public void setDimmed(boolean dimmed, boolean fade) {
-        super.setDimmed(dimmed, fade);
-        mActivator.setDimmed(dimmed, fade);
-    }
-
     public int getMaxExpandHeight() {
         return mMaxExpandHeight;
     }
 
-    public NotificationActivator getActivator() {
-        return mActivator;
-    }
-
     /**
      * @return the potential height this view could expand in addition.
      */
@@ -238,10 +222,10 @@
     }
 
     @Override
-    public void setActualHeight(int height) {
-        mPrivateLayout.setActualHeight(height);
+    public void setActualHeight(int height, boolean notifyListeners) {
+        mPrivateLayout.setActualHeight(height, notifyListeners);
         invalidate();
-        super.setActualHeight(height);
+        super.setActualHeight(height, notifyListeners);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
index 43eb5b5..a42c194 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
@@ -33,8 +33,8 @@
     }
 
     @Override
-    public void setActualHeight(int actualHeight) {
-        super.setActualHeight(actualHeight);
+    public void setActualHeight(int actualHeight, boolean notifyListeners) {
+        super.setActualHeight(actualHeight, notifyListeners);
         updateOutline();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index 169521d..281bd2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -61,10 +61,19 @@
     /**
      * Sets the actual height of this notification. This is different than the laid out
      * {@link View#getHeight()}, as we want to avoid layouting during scrolling and expanding.
+     *
+     * @param actualHeight The height of this notification.
+     * @param notifyListeners Whether the listener should be informed about the change.
      */
-    public void setActualHeight(int actualHeight) {
+    public void setActualHeight(int actualHeight, boolean notifyListeners) {
         mActualHeight = actualHeight;
-        notifyHeightChanged();
+        if (notifyListeners) {
+            notifyHeightChanged();
+        }
+    }
+
+    public void setActualHeight(int actualHeight) {
+        setActualHeight(actualHeight, true);
     }
 
     /**
@@ -91,6 +100,15 @@
     }
 
     /**
+     * Sets the notification as dimmed. The default implementation does nothing.
+     *
+     * @param dimmed Whether the notification should be dimmed.
+     * @param fade Whether an animation should be played to change the state.
+     */
+    public void setDimmed(boolean dimmed, boolean fade) {
+    }
+
+    /**
      * @return The desired notification height.
      */
     public int getIntrinsicHeight() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationActivator.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationActivator.java
deleted file mode 100644
index a03aeec..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationActivator.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2014 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 android.content.Context;
-import android.view.View;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-
-import com.android.systemui.R;
-
-/**
- * A helper class used by both {@link com.android.systemui.statusbar.ExpandableNotificationRow} and
- * {@link com.android.systemui.statusbar.NotificationOverflowIconsView} to make a notification look
- * active after tapping it once on the Keyguard.
- */
-public class NotificationActivator {
-
-    public static final int ANIMATION_LENGTH_MS = 220;
-    private static final float INVERSE_ALPHA = 0.9f;
-    private static final float DIMMED_SCALE = 0.95f;
-
-    /**
-     * Normal state. Notification is fully interactable.
-     */
-    private static final int STATE_NORMAL = 0;
-
-    /**
-     * Dimmed state. Neutral state when on the lockscreen, with slight transparency and scaled down
-     * a bit.
-     */
-    private static final int STATE_DIMMED = 1;
-
-    /**
-     * Activated state. Used after tapping a notification on the lockscreen. Normal transparency and
-     * normal scale.
-     */
-    private static final int STATE_ACTIVATED = 2;
-
-    /**
-     * Inverse activated state. Used for the other notifications on the lockscreen when tapping on
-     * one.
-     */
-    private static final int STATE_ACTIVATED_INVERSE = 3;
-
-    private final View mTargetView;
-    private final View mHotspotView;
-    private final Interpolator mFastOutSlowInInterpolator;
-    private final Interpolator mLinearOutSlowInInterpolator;
-    private final int mTranslationZ;
-
-    private int mState;
-
-    public NotificationActivator(View targetView, View hotspotView) {
-        mTargetView = targetView;
-        mHotspotView = hotspotView;
-        Context ctx = targetView.getContext();
-        mFastOutSlowInInterpolator =
-                AnimationUtils.loadInterpolator(ctx, android.R.interpolator.fast_out_slow_in);
-        mLinearOutSlowInInterpolator =
-                AnimationUtils.loadInterpolator(ctx, android.R.interpolator.linear_out_slow_in);
-        mTranslationZ =
-                ctx.getResources().getDimensionPixelSize(R.dimen.z_distance_between_notifications);
-        mTargetView.animate().setDuration(ANIMATION_LENGTH_MS);
-    }
-
-    public void activateInverse() {
-        if (mState == STATE_ACTIVATED_INVERSE) {
-            return;
-        }
-        mTargetView.animate().cancel();
-        mTargetView.animate().withLayer().alpha(INVERSE_ALPHA);
-        mState = STATE_ACTIVATED_INVERSE;
-    }
-
-    public void addHotspot() {
-        mHotspotView.getBackground().setHotspot(
-                0, mHotspotView.getWidth()/2, mHotspotView.getHeight()/2);
-    }
-
-    public void activate() {
-        if (mState == STATE_ACTIVATED) {
-            return;
-        }
-        mTargetView.animate().cancel();
-        mTargetView.animate()
-                .setInterpolator(mLinearOutSlowInInterpolator)
-                .scaleX(1)
-                .scaleY(1)
-                .translationZBy(mTranslationZ);
-        mState = STATE_ACTIVATED;
-    }
-
-    public void reset() {
-        if (mState == STATE_DIMMED) {
-            return;
-        }
-        mTargetView.animate().cancel();
-        mTargetView.animate()
-                .setInterpolator(mFastOutSlowInInterpolator)
-                .scaleX(DIMMED_SCALE)
-                .scaleY(DIMMED_SCALE)
-                .translationZBy(-mTranslationZ);
-        if (mTargetView.getAlpha() != 1.0f) {
-            mTargetView.animate().withLayer().alpha(1);
-        }
-        mState = STATE_DIMMED;
-    }
-
-    public void setDimmed(boolean dimmed, boolean fade) {
-        if (dimmed) {
-            mTargetView.animate().cancel();
-            if (fade) {
-                mTargetView.animate()
-                        .setInterpolator(mFastOutSlowInInterpolator)
-                        .scaleX(DIMMED_SCALE)
-                        .scaleY(DIMMED_SCALE);
-            } else {
-                mTargetView.setScaleX(DIMMED_SCALE);
-                mTargetView.setScaleY(DIMMED_SCALE);
-            }
-            mState = STATE_DIMMED;
-        } else {
-            mTargetView.animate().cancel();
-            if (fade) {
-                mTargetView.animate()
-                        .setInterpolator(mFastOutSlowInInterpolator)
-                        .scaleX(1)
-                        .scaleY(1);
-            } else {
-                mTargetView.animate().cancel();
-                mTargetView.setScaleX(1);
-                mTargetView.setScaleY(1);
-            }
-            mState = STATE_NORMAL;
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 379bd05..9df2701 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -70,8 +70,8 @@
     }
 
     @Override
-    public void setActualHeight(int actualHeight) {
-        super.setActualHeight(actualHeight);
+    public void setActualHeight(int actualHeight, boolean notifyListeners) {
+        super.setActualHeight(actualHeight, notifyListeners);
         selectLayout();
         updateClipping();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java
index e6b5600..864c597 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java
@@ -28,14 +28,13 @@
 public class NotificationOverflowContainer extends ActivatableNotificationView {
 
     private NotificationOverflowIconsView mIconsView;
-    private NotificationActivator mActivator;
 
     public NotificationOverflowContainer(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     @Override
-    public void setActualHeight(int currentHeight) {
+    public void setActualHeight(int currentHeight, boolean notifyListeners) {
         // noop
     }
 
@@ -54,22 +53,9 @@
         super.onFinishInflate();
         mIconsView = (NotificationOverflowIconsView) findViewById(R.id.overflow_icons_view);
         mIconsView.setMoreText((TextView) findViewById(R.id.more_text));
-
-        mActivator = new NotificationActivator(this, this);
-        setDimmed(true, false);
-    }
-
-    @Override
-    public void setDimmed(boolean dimmed, boolean fade) {
-        super.setDimmed(dimmed, fade);
-        mActivator.setDimmed(dimmed, fade);
     }
 
     public NotificationOverflowIconsView getIconsView() {
         return mIconsView;
     }
-
-    public NotificationActivator getActivator() {
-        return mActivator;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index c4ee6ff..304430a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -2790,6 +2790,7 @@
             mSettingsContainer.setKeyguardShowing(false);
         }
 
+        updateStackScrollerState();
         updatePublicMode();
         updateRowStates();
         checkBarModes();
@@ -2797,6 +2798,10 @@
         updateCarrierLabelVisibility(false);
     }
 
+    public void updateStackScrollerState() {
+        mStackScroller.setDimmed(mState == StatusBarState.KEYGUARD, false /* animate */);
+    }
+
     public void userActivity() {
         if (mState == StatusBarState.KEYGUARD) {
             mKeyguardViewMediatorCallback.userActivity();
@@ -2850,7 +2855,7 @@
     public void onActivated(View view) {
         userActivity();
         mKeyguardIndicationTextView.switchIndication(R.string.notification_tap_again);
-        super.onActivated(view);
+        mStackScroller.setActivatedChild(view);
     }
 
     /**
@@ -2862,9 +2867,11 @@
     }
 
     @Override
-    public void onReset(View view) {
-        super.onReset(view);
-        mKeyguardIndicationTextView.switchIndication(mKeyguardHotwordPhrase);
+    public void onActivationReset(View view) {
+        if (view == mStackScroller.getActivatedChild()) {
+            mKeyguardIndicationTextView.switchIndication(mKeyguardHotwordPhrase);
+            mStackScroller.setActivatedChild(null);
+        }
     }
 
     public void onTrackingStarted() {
@@ -2896,30 +2903,12 @@
     }
 
     @Override
-    public void onReset() {
-        int n = mNotificationData.size();
-        for (int i = 0; i < n; i++) {
-            NotificationData.Entry entry = mNotificationData.get(i);
-            if (entry.row.getVisibility() != View.GONE) {
-                entry.row.setDimmed(true /* dimmed */, true /* fade */);
-            }
-        }
-        if (mKeyguardIconOverflowContainer.getVisibility() != View.GONE) {
-            mKeyguardIconOverflowContainer.setDimmed(true /* dimmed */, true /* fade */);
-        }
+    public void onDragDownReset() {
+        mStackScroller.setDimmed(true /* dimmed */, true /* animated */);
     }
 
     public void onThresholdReached() {
-        int n = mNotificationData.size();
-        for (int i = 0; i < n; i++) {
-            NotificationData.Entry entry = mNotificationData.get(i);
-            if (entry.row.getVisibility() != View.GONE) {
-                entry.row.setDimmed(false /* dimmed */, true /* fade */);
-            }
-        }
-        if (mKeyguardIconOverflowContainer.getVisibility() != View.GONE) {
-            mKeyguardIconOverflowContainer.setDimmed(false /* dimmed */, true /* fade */);
-        }
+        mStackScroller.setDimmed(false /* dimmed */, true /* animate */);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
new file mode 100644
index 0000000..4121a40
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2014 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.stack;
+
+import android.view.View;
+
+import java.util.ArrayList;
+
+/**
+ * A global state to track all input states for the algorithm.
+ */
+public class AmbientState {
+    private ArrayList<View> mDraggedViews = new ArrayList<View>();
+    private int mScrollY;
+    private boolean mDimmed;
+    private View mActivatedChild;
+
+    public int getScrollY() {
+        return mScrollY;
+    }
+
+    public void setScrollY(int scrollY) {
+        this.mScrollY = scrollY;
+    }
+
+    public void onBeginDrag(View view) {
+        mDraggedViews.add(view);
+    }
+
+    public void onDragFinished(View view) {
+        mDraggedViews.remove(view);
+    }
+
+    public ArrayList<View> getDraggedViews() {
+        return mDraggedViews;
+    }
+
+    /**
+     * @param dimmed Whether we are in a dimmed state (on the lockscreen), where the backgrounds are
+     *               translucent and everything is scaled back a bit.
+     */
+    public void setDimmed(boolean dimmed) {
+        mDimmed = dimmed;
+    }
+
+    /**
+     * In dimmed mode, a child can be activated, which happens on the first tap of the double-tap
+     * interaction. This child is then scaled normally and its background is fully opaque.
+     */
+    public void setActivatedChild(View activatedChild) {
+        mActivatedChild = activatedChild;
+    }
+
+    public boolean isDimmed() {
+        return mDimmed;
+    }
+
+    public View getActivatedChild() {
+        return mActivatedChild;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
new file mode 100644
index 0000000..41914ed
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2014 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.stack;
+
+import java.util.ArrayList;
+
+/**
+ * Filters the animations for only a certain type of properties.
+ */
+public class AnimationFilter {
+    boolean animateAlpha;
+    boolean animateY;
+    boolean animateZ;
+    boolean animateScale;
+    boolean animateHeight;
+    boolean animateDimmed;
+
+    public AnimationFilter animateAlpha() {
+        animateAlpha = true;
+        return this;
+    }
+
+    public AnimationFilter animateY() {
+        animateY = true;
+        return this;
+    }
+
+    public AnimationFilter animateZ() {
+        animateZ = true;
+        return this;
+    }
+
+    public AnimationFilter animateScale() {
+        animateScale = true;
+        return this;
+    }
+
+    public AnimationFilter animateHeight() {
+        animateHeight = true;
+        return this;
+    }
+
+    public AnimationFilter animateDimmed() {
+        animateDimmed = true;
+        return this;
+    }
+
+    /**
+     * Combines multiple filters into {@code this} filter, using or as the operand .
+     *
+     * @param events The animation events from the filters to combine.
+     */
+    public void applyCombination(ArrayList<NotificationStackScrollLayout.AnimationEvent> events) {
+        reset();
+        int size = events.size();
+        for (int i = 0; i < size; i++) {
+            combineFilter(events.get(i).filter);
+        }
+    }
+
+    private void combineFilter(AnimationFilter filter) {
+        animateAlpha |= filter.animateAlpha;
+        animateY |= filter.animateY;
+        animateZ |= filter.animateZ;
+        animateScale |= filter.animateScale;
+        animateHeight |= filter.animateHeight;
+        animateDimmed |= filter.animateDimmed;
+    }
+
+    private void reset() {
+        animateAlpha = false;
+        animateY = false;
+        animateZ = false;
+        animateScale = false;
+        animateHeight = false;
+        animateDimmed = false;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 59d717c..7398035 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -84,7 +84,6 @@
     private int mEmptyMarginBottom;
     private int mPaddingBetweenElements;
     private int mTopPadding;
-    private boolean mListenForHeightChanges = true;
 
     /**
      * The algorithm which calculates the properties for our children
@@ -95,6 +94,7 @@
      * The current State this Layout is in
      */
     private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
+    private AmbientState mAmbientState = new AmbientState();
     private ArrayList<View> mChildrenToAddAnimated = new ArrayList<View>();
     private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<View>();
     private ArrayList<View> mSnappedBackChildren = new ArrayList<View>();
@@ -108,6 +108,8 @@
     private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
     private boolean mNeedsAnimation;
     private boolean mTopPaddingNeedsAnimation;
+    private boolean mDimmedNeedsAnimation;
+    private boolean mActivateNeedsAnimation;
     private boolean mIsExpanded = true;
     private boolean mChildrenUpdateRequested;
     private ViewTreeObserver.OnPreDrawListener mChildrenUpdater
@@ -267,8 +269,8 @@
      * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
      */
     private void updateChildren() {
-        mCurrentStackScrollState.setScrollY(mOwnScrollY);
-        mStackScrollAlgorithm.getStackScrollState(mCurrentStackScrollState);
+        mAmbientState.setScrollY(mOwnScrollY);
+        mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
         if (!isCurrentlyAnimating() && !mNeedsAnimation) {
             applyCurrentState();
         } else {
@@ -385,12 +387,12 @@
             mDragAnimPendingChildren.remove(v);
         }
         mSwipedOutViews.add(v);
-        mStackScrollAlgorithm.onDragFinished(v);
+        mAmbientState.onDragFinished(v);
     }
 
     @Override
     public void onChildSnappedBack(View animView) {
-        mStackScrollAlgorithm.onDragFinished(animView);
+        mAmbientState.onDragFinished(animView);
         if (!mDragAnimPendingChildren.contains(animView)) {
             mSnappedBackChildren.add(animView);
             requestChildrenUpdate();
@@ -404,7 +406,7 @@
     public void onBeginDrag(View v) {
         setSwipingInProgress(true);
         mDragAnimPendingChildren.add(v);
-        mStackScrollAlgorithm.onBeginDrag(v);
+        mAmbientState.onBeginDrag(v);
         requestChildrenUpdate();
         mNeedsAnimation = true;
     }
@@ -965,6 +967,8 @@
         generateSnapBackEvents();
         generateDragEvents();
         generateTopPaddingEvent();
+        generateActivateEvent();
+        generateDimmedEvent();
         mNeedsAnimation = false;
     }
 
@@ -1012,6 +1016,22 @@
         mTopPaddingNeedsAnimation = false;
     }
 
+    private void generateActivateEvent() {
+        if (mActivateNeedsAnimation) {
+            mAnimationEvents.add(
+                    new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_ACTIVATED_CHILD));
+        }
+        mActivateNeedsAnimation = false;
+    }
+
+    private void generateDimmedEvent() {
+        if (mDimmedNeedsAnimation) {
+            mAnimationEvents.add(
+                    new AnimationEvent(null, AnimationEvent.ANIMATION_TYPE_DIMMED));
+        }
+        mDimmedNeedsAnimation = false;
+    }
+
     private boolean onInterceptTouchEventScroll(MotionEvent ev) {
         /*
          * This method JUST determines whether we want to intercept the motion.
@@ -1177,14 +1197,12 @@
 
     @Override
     public void onHeightChanged(ExpandableView view) {
-        if (mListenForHeightChanges && !isCurrentlyAnimating()) {
-            updateContentHeight();
-            updateScrollPositionIfNecessary();
-            if (mOnHeightChangedListener != null) {
-                mOnHeightChangedListener.onHeightChanged(view);
-            }
-            requestChildrenUpdate();
+        updateContentHeight();
+        updateScrollPositionIfNecessary();
+        if (mOnHeightChangedListener != null) {
+            mOnHeightChangedListener.onHeightChanged(view);
         }
+        requestChildrenUpdate();
     }
 
     public void setOnHeightChangedListener(
@@ -1197,10 +1215,34 @@
         mAnimationEvents.clear();
     }
 
+    /**
+     * See {@link AmbientState#setDimmed}.
+     */
+    public void setDimmed(boolean dimmed, boolean animate) {
+        mAmbientState.setDimmed(dimmed);
+        if (animate) {
+            mDimmedNeedsAnimation = true;
+            mNeedsAnimation =  true;
+        }
+        requestChildrenUpdate();
+    }
+
+    /**
+     * See {@link AmbientState#setActivatedChild}.
+     */
+    public void setActivatedChild(View activatedChild) {
+        mAmbientState.setActivatedChild(activatedChild);
+        mActivateNeedsAnimation = true;
+        mNeedsAnimation =  true;
+        requestChildrenUpdate();
+    }
+
+    public View getActivatedChild() {
+        return mAmbientState.getActivatedChild();
+    }
+
     private void applyCurrentState() {
-        mListenForHeightChanges = false;
         mCurrentStackScrollState.apply();
-        mListenForHeightChanges = true;
         if (mListener != null) {
             mListener.onChildLocationsChanged(this);
         }
@@ -1215,21 +1257,76 @@
 
     static class AnimationEvent {
 
-        static int ANIMATION_TYPE_ADD = 1;
-        static int ANIMATION_TYPE_REMOVE = 2;
-        static int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 3;
-        static int ANIMATION_TYPE_TOP_PADDING_CHANGED = 4;
-        static int ANIMATION_TYPE_START_DRAG = 5;
-        static int ANIMATION_TYPE_SNAP_BACK = 6;
+        static AnimationFilter[] FILTERS = new AnimationFilter[] {
+
+                // ANIMATION_TYPE_ADD
+                new AnimationFilter()
+                        .animateAlpha()
+                        .animateHeight()
+                        .animateY()
+                        .animateZ(),
+
+                // ANIMATION_TYPE_REMOVE
+                new AnimationFilter()
+                        .animateAlpha()
+                        .animateHeight()
+                        .animateY()
+                        .animateZ(),
+
+                // ANIMATION_TYPE_REMOVE_SWIPED_OUT
+                new AnimationFilter()
+                        .animateAlpha()
+                        .animateHeight()
+                        .animateY()
+                        .animateZ(),
+
+                // ANIMATION_TYPE_TOP_PADDING_CHANGED
+                new AnimationFilter()
+                        .animateAlpha()
+                        .animateHeight()
+                        .animateY()
+                        .animateDimmed()
+                        .animateScale()
+                        .animateZ(),
+
+                // ANIMATION_TYPE_START_DRAG
+                new AnimationFilter()
+                        .animateAlpha(),
+
+                // ANIMATION_TYPE_SNAP_BACK
+                new AnimationFilter()
+                        .animateAlpha(),
+
+                // ANIMATION_TYPE_ACTIVATED_CHILD
+                new AnimationFilter()
+                        .animateScale()
+                        .animateAlpha(),
+
+                // ANIMATION_TYPE_DIMMED
+                new AnimationFilter()
+                        .animateScale()
+                        .animateDimmed()
+        };
+
+        static int ANIMATION_TYPE_ADD = 0;
+        static int ANIMATION_TYPE_REMOVE = 1;
+        static int ANIMATION_TYPE_REMOVE_SWIPED_OUT = 2;
+        static int ANIMATION_TYPE_TOP_PADDING_CHANGED = 3;
+        static int ANIMATION_TYPE_START_DRAG = 4;
+        static int ANIMATION_TYPE_SNAP_BACK = 5;
+        static int ANIMATION_TYPE_ACTIVATED_CHILD = 6;
+        static int ANIMATION_TYPE_DIMMED = 7;
 
         final long eventStartTime;
         final View changingView;
         final int animationType;
+        final AnimationFilter filter;
 
         AnimationEvent(View view, int type) {
             eventStartTime = AnimationUtils.currentAnimationTimeMillis();
             changingView = view;
             animationType = type;
+            filter = FILTERS[type];
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index f7818c0..5e4d496 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -39,6 +39,10 @@
     private static final int MAX_ITEMS_IN_BOTTOM_STACK = 3;
     private static final int MAX_ITEMS_IN_TOP_STACK = 3;
 
+    /** When a child is activated, the other cards' alpha fade to this value. */
+    private static final float ACTIVATED_INVERSE_ALPHA = 0.9f;
+    private static final float DIMMED_SCALE = 0.95f;
+
     private int mPaddingBetweenElements;
     private int mCollapsedSize;
     private int mTopStackPeekSize;
@@ -61,7 +65,6 @@
     private ExpandableView mFirstChildWhileExpanding;
     private boolean mExpandedOnStart;
     private int mTopStackTotalSize;
-    private ArrayList<View> mDraggedViews = new ArrayList<View>();
 
     public StackScrollAlgorithm(Context context) {
         initConstants(context);
@@ -93,7 +96,7 @@
     }
 
 
-    public void getStackScrollState(StackScrollState resultState) {
+    public void getStackScrollState(AmbientState ambientState, StackScrollState resultState) {
         // The state of the local variables are saved in an algorithmState to easily subdivide it
         // into multiple phases.
         StackScrollAlgorithmState algorithmState = mTempAlgorithmState;
@@ -107,7 +110,7 @@
         algorithmState.scrolledPixelsTop = 0;
         algorithmState.itemsInBottomStack = 0.0f;
         algorithmState.partialInBottom = 0.0f;
-        algorithmState.scrollY = resultState.getScrollY() + mCollapsedSize;
+        algorithmState.scrollY = ambientState.getScrollY() + mCollapsedSize;
 
         updateVisibleChildren(resultState, algorithmState);
 
@@ -120,19 +123,42 @@
         // Phase 3:
         updateZValuesForState(resultState, algorithmState);
 
-        handleDraggedViews(resultState, algorithmState);
+        handleDraggedViews(ambientState, resultState, algorithmState);
+        updateDimmedActivated(ambientState, resultState, algorithmState);
+    }
+
+    /**
+     * Updates the dimmed and activated states of the children.
+     */
+    private void updateDimmedActivated(AmbientState ambientState, StackScrollState resultState,
+            StackScrollAlgorithmState algorithmState) {
+        boolean dimmed = ambientState.isDimmed();
+        View activatedChild = ambientState.getActivatedChild();
+        int childCount = algorithmState.visibleChildren.size();
+        for (int i = 0; i < childCount; i++) {
+            View child = algorithmState.visibleChildren.get(i);
+            StackScrollState.ViewState childViewState = resultState.getViewStateForView(child);
+            childViewState.dimmed = dimmed;
+            childViewState.scale = !dimmed || activatedChild == child
+                    ? 1.0f
+                    : DIMMED_SCALE;
+            if (dimmed && activatedChild != null && child != activatedChild) {
+                childViewState.alpha *= ACTIVATED_INVERSE_ALPHA;
+            }
+        }
     }
 
     /**
      * Handle the special state when views are being dragged
      */
-    private void handleDraggedViews(StackScrollState resultState,
+    private void handleDraggedViews(AmbientState ambientState, StackScrollState resultState,
             StackScrollAlgorithmState algorithmState) {
-        for (View draggedView : mDraggedViews) {
+        ArrayList<View> draggedViews = ambientState.getDraggedViews();
+        for (View draggedView : draggedViews) {
             int childIndex = algorithmState.visibleChildren.indexOf(draggedView);
             if (childIndex >= 0 && childIndex < algorithmState.visibleChildren.size() - 1) {
                 View nextChild = algorithmState.visibleChildren.get(childIndex + 1);
-                if (!mDraggedViews.contains(nextChild)) {
+                if (!draggedViews.contains(nextChild)) {
                     // only if the view is not dragged itself we modify its state to be fully
                     // visible
                     StackScrollState.ViewState viewState = resultState.getViewStateForView(
@@ -595,14 +621,6 @@
         }
     }
 
-    public void onBeginDrag(View view) {
-        mDraggedViews.add(view);
-    }
-
-    public void onDragFinished(View view) {
-        mDraggedViews.remove(view);
-    }
-
     class StackScrollAlgorithmState {
 
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
index 70126f5..8fc26d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
@@ -37,19 +37,10 @@
 
     private final ViewGroup mHostView;
     private Map<ExpandableView, ViewState> mStateMap;
-    private int mScrollY;
     private final Rect mClipRect = new Rect();
     private int mBackgroundRoundedRectCornerRadius;
     private final Outline mChildOutline = new Outline();
 
-    public int getScrollY() {
-        return mScrollY;
-    }
-
-    public void setScrollY(int scrollY) {
-        this.mScrollY = scrollY;
-    }
-
     public StackScrollState(ViewGroup hostView) {
         mHostView = hostView;
         mStateMap = new HashMap<ExpandableView, ViewState>();
@@ -106,10 +97,12 @@
                 float alpha = child.getAlpha();
                 float yTranslation = child.getTranslationY();
                 float zTranslation = child.getTranslationZ();
+                float scale = child.getScaleX();
                 int height = child.getActualHeight();
                 float newAlpha = state.alpha;
                 float newYTranslation = state.yTranslation;
                 float newZTranslation = state.zTranslation;
+                float newScale = state.scale;
                 int newHeight = state.height;
                 boolean becomesInvisible = newAlpha == 0.0f;
                 if (alpha != newAlpha) {
@@ -147,11 +140,20 @@
                     child.setTranslationZ(newZTranslation);
                 }
 
+                // apply scale
+                if (scale != newScale) {
+                    child.setScaleX(newScale);
+                    child.setScaleY(newScale);
+                }
+
                 // apply height
                 if (height != newHeight) {
-                    child.setActualHeight(newHeight);
+                    child.setActualHeight(newHeight, false /* notifyListeners */);
                 }
 
+                // apply dimming
+                child.setDimmed(state.dimmed, false /* animate */);
+
                 // apply clipping and shadow
                 float newNotificationEnd = newYTranslation + newHeight;
 
@@ -228,6 +230,8 @@
         float zTranslation;
         int height;
         boolean gone;
+        float scale;
+        boolean dimmed;
 
         /**
          * The location this view is currently rendered at.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
index 2e700aa..695a0db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -18,7 +18,9 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
 import android.view.View;
 import android.view.animation.AnimationUtils;
@@ -40,11 +42,13 @@
     private static final int ANIMATION_DURATION = 360;
     private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag;
     private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag;
+    private static final int TAG_ANIMATOR_SCALE = R.id.scale_animator_tag;
     private static final int TAG_ANIMATOR_ALPHA = R.id.alpha_animator_tag;
     private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag;
     private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag;
     private static final int TAG_END_TRANSLATION_Y = R.id.translation_y_animator_end_value_tag;
     private static final int TAG_END_TRANSLATION_Z = R.id.translation_z_animator_end_value_tag;
+    private static final int TAG_END_SCALE = R.id.scale_animator_end_value_tag;
     private static final int TAG_END_ALPHA = R.id.alpha_animator_end_value_tag;
     private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag;
     private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag;
@@ -58,6 +62,7 @@
     private Set<Animator> mAnimatorSet = new HashSet<Animator>();
     private Stack<AnimatorListenerAdapter> mAnimationListenerPool
             = new Stack<AnimatorListenerAdapter>();
+    private AnimationFilter mAnimationFilter = new AnimationFilter();
 
     public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
         mHostLayout = hostLayout;
@@ -75,8 +80,8 @@
 
         processAnimationEvents(mAnimationEvents, finalState);
 
-        boolean hasNewEvents = !mNewEvents.isEmpty();
         int childCount = mHostLayout.getChildCount();
+        mAnimationFilter.applyCombination(mNewEvents);
         for (int i = 0; i < childCount; i++) {
             final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
             StackScrollState.ViewState viewState = finalState.getViewStateForView(child);
@@ -84,7 +89,7 @@
                 continue;
             }
 
-            startAnimations(child, viewState, hasNewEvents);
+            startAnimations(child, viewState);
 
             child.setClipBounds(null);
         }
@@ -97,8 +102,7 @@
     /**
      * Start an animation to the given viewState
      */
-    private void startAnimations(final ExpandableView child, StackScrollState.ViewState viewState,
-            boolean hasNewEvents) {
+    private void startAnimations(final ExpandableView child, StackScrollState.ViewState viewState) {
         int childVisibility = child.getVisibility();
         boolean wasVisible = childVisibility == View.VISIBLE;
         final float alpha = viewState.alpha;
@@ -107,33 +111,40 @@
         }
         // start translationY animation
         if (child.getTranslationY() != viewState.yTranslation) {
-            startYTranslationAnimation(child, viewState, hasNewEvents);
+            startYTranslationAnimation(child, viewState);
         }
         // start translationZ animation
         if (child.getTranslationZ() != viewState.zTranslation) {
-            startZTranslationAnimation(child, viewState, hasNewEvents);
+            startZTranslationAnimation(child, viewState);
+        }
+        // start scale animation
+        if (child.getScaleX() != viewState.scale) {
+            startScaleAnimation(child, viewState);
         }
         // start alpha animation
         if (alpha != child.getAlpha()) {
-            startAlphaAnimation(child, viewState, hasNewEvents);
+            startAlphaAnimation(child, viewState);
         }
         // start height animation
         if (viewState.height != child.getActualHeight()) {
-            startHeightAnimation(child, viewState, hasNewEvents);
+            startHeightAnimation(child, viewState);
         }
+        // start dimmed animation
+        child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed);
     }
 
     private void startHeightAnimation(final ExpandableView child,
-            StackScrollState.ViewState viewState, boolean hasNewEvents) {
-        Integer previousEndValue = getChildTag(child,TAG_END_HEIGHT);
+            StackScrollState.ViewState viewState) {
+        Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT);
         if (previousEndValue != null && previousEndValue == viewState.height) {
             return;
         }
         ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT);
-        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, hasNewEvents);
+        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator,
+                mAnimationFilter.animateHeight);
         if (newDuration <= 0) {
             // no new animation needed, let's just apply the value
-            child.setActualHeight(viewState.height);
+            child.setActualHeight(viewState.height, false /* notifyListeners */);
             if (previousAnimator != null && !isRunning()) {
                 onAnimationFinished();
             }
@@ -144,7 +155,8 @@
         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
             @Override
             public void onAnimationUpdate(ValueAnimator animation) {
-                child.setActualHeight((int) animation.getAnimatedValue());
+                child.setActualHeight((int) animation.getAnimatedValue(),
+                        false /* notifyListeners */);
             }
         });
         animator.setInterpolator(mFastOutSlowInInterpolator);
@@ -164,14 +176,15 @@
     }
 
     private void startAlphaAnimation(final ExpandableView child,
-            final StackScrollState.ViewState viewState, boolean hasNewEvents) {
+            final StackScrollState.ViewState viewState) {
         final float endAlpha = viewState.alpha;
         Float previousEndValue = getChildTag(child,TAG_END_ALPHA);
         if (previousEndValue != null && previousEndValue == endAlpha) {
             return;
         }
         ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA);
-        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, hasNewEvents);
+        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator,
+                mAnimationFilter.animateAlpha);
         if (newDuration <= 0) {
             // no new animation needed, let's just apply the value
             child.setAlpha(endAlpha);
@@ -228,13 +241,14 @@
     }
 
     private void startZTranslationAnimation(final ExpandableView child,
-            final StackScrollState.ViewState viewState, boolean hasNewEvents) {
+            final StackScrollState.ViewState viewState) {
         Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
         if (previousEndValue != null && previousEndValue == viewState.zTranslation) {
             return;
         }
         ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z);
-        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, hasNewEvents);
+        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator,
+                mAnimationFilter.animateZ);
         if (newDuration <= 0) {
             // no new animation needed, let's just apply the value
             child.setTranslationZ(viewState.zTranslation);
@@ -264,13 +278,14 @@
     }
 
     private void startYTranslationAnimation(final ExpandableView child,
-            StackScrollState.ViewState viewState, boolean hasNewEvents) {
+            StackScrollState.ViewState viewState) {
         Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y);
         if (previousEndValue != null && previousEndValue == viewState.yTranslation) {
             return;
         }
         ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y);
-        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator, hasNewEvents);
+        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator,
+                mAnimationFilter.animateY);
         if (newDuration <= 0) {
             // no new animation needed, let's just apply the value
             child.setTranslationY(viewState.yTranslation);
@@ -298,6 +313,46 @@
         child.setTag(TAG_END_TRANSLATION_Y, viewState.yTranslation);
     }
 
+    private void startScaleAnimation(final ExpandableView child,
+            StackScrollState.ViewState viewState) {
+        Float previousEndValue = getChildTag(child, TAG_END_SCALE);
+        if (previousEndValue != null && previousEndValue == viewState.scale) {
+            return;
+        }
+        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SCALE);
+        long newDuration = cancelAnimatorAndGetNewDuration(previousAnimator,
+                mAnimationFilter.animateScale);
+        if (newDuration <= 0) {
+            // no new animation needed, let's just apply the value
+            child.setScaleX(viewState.scale);
+            child.setScaleY(viewState.scale);
+            if (previousAnimator != null && !isRunning()) {
+                onAnimationFinished();
+            }
+            return;
+        }
+
+        PropertyValuesHolder holderX =
+                PropertyValuesHolder.ofFloat(View.SCALE_X, child.getScaleX(), viewState.scale);
+        PropertyValuesHolder holderY =
+                PropertyValuesHolder.ofFloat(View.SCALE_Y, child.getScaleY(), viewState.scale);
+        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(child, holderX, holderY);
+        animator.setInterpolator(mFastOutSlowInInterpolator);
+        animator.setDuration(newDuration);
+        animator.addListener(getGlobalAnimationFinishedListener());
+        // remove the tag when the animation is finished
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                child.setTag(TAG_ANIMATOR_SCALE, null);
+                child.setTag(TAG_END_SCALE, null);
+            }
+        });
+        startInstantly(animator);
+        child.setTag(TAG_ANIMATOR_SCALE, animator);
+        child.setTag(TAG_END_SCALE, viewState.scale);
+    }
+
     /**
      * Start an animator instantly instead of waiting on the next synchronization frame
      */
@@ -349,21 +404,22 @@
      * Cancel the previous animator and get the duration of the new animation.
      *
      * @param previousAnimator the animator which was running before
-     * @param hasNewEvents indicating whether new events came in in this animation
+     * @param newAnimationNeeded indicating whether a new animation should be started for this
+     *                           property
      * @return the new duration
      */
     private long cancelAnimatorAndGetNewDuration(ValueAnimator previousAnimator,
-            boolean hasNewEvents) {
+            boolean newAnimationNeeded) {
         long newDuration = ANIMATION_DURATION;
         if (previousAnimator != null) {
-            if (!hasNewEvents) {
+            if (!newAnimationNeeded) {
                 // This is only an update, no new event came in. lets just take the remaining
                 // duration as the new duration
                 newDuration = previousAnimator.getDuration()
                         - previousAnimator.getCurrentPlayTime();
             }
             previousAnimator.cancel();
-        } else if (!hasNewEvents){
+        } else if (!newAnimationNeeded){
             newDuration = 0;
         }
         return newDuration;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
index d6a8885..9006c9a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java
@@ -150,4 +150,11 @@
     protected void refreshLayout(int layoutDirection) {
     }
 
+    @Override
+    public void onActivated(View view) {
+    }
+
+    @Override
+    public void onActivationReset(View view) {
+    }
 }