Added possibility to use canned animation for icons

Icons now move in a canned animation from the shelf position
to the icon.

Test: adb shell setprop debug.icon_opening_animations true && adb shell killall com.android.systemui
Bug: 32437839
Change-Id: I82b6de37ac7a179aeb5d16bd663d566c2f338b1a
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 661cc3c..e4654e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -193,7 +193,7 @@
     private View mChildAfterViewWhenDismissed;
     private View mGroupParentWhenDismissed;
     private boolean mRefocusOnDismiss;
-    private float mIconTransformationAmount;
+    private float mContentTransformationAmount;
     private boolean mIconsVisible = true;
     private boolean mAboveShelf;
     private boolean mIsLastChild;
@@ -837,23 +837,29 @@
     /**
      * Set how much this notification is transformed into an icon.
      *
-     * @param iconTransformationAmount A value from 0 to 1 indicating how much we are transformed
-     *                                 to an icon
+     * @param contentTransformationAmount A value from 0 to 1 indicating how much we are transformed
+     *                                 to the content away
      * @param isLastChild is this the last child in the list. If true, then the transformation is
      *                    different since it's content fades out.
      */
-    public void setIconTransformationAmount(float iconTransformationAmount, boolean isLastChild) {
+    public void setContentTransformationAmount(float contentTransformationAmount,
+            boolean isLastChild) {
         boolean changeTransformation = isLastChild != mIsLastChild;
-        changeTransformation |= mIconTransformationAmount != iconTransformationAmount;
+        changeTransformation |= mContentTransformationAmount != contentTransformationAmount;
         mIsLastChild = isLastChild;
-        mIconTransformationAmount = iconTransformationAmount;
+        mContentTransformationAmount = contentTransformationAmount;
         if (changeTransformation) {
             updateContentTransformation();
-            boolean iconsVisible = mIconTransformationAmount == 0.0f;
-            if (iconsVisible != mIconsVisible) {
-                mIconsVisible = iconsVisible;
-                updateIconVisibilities();
-            }
+        }
+    }
+
+    /**
+     * Set the icons to be visible of this notification.
+     */
+    public void setIconsVisible(boolean iconsVisible) {
+        if (iconsVisible != mIconsVisible) {
+            mIconsVisible = iconsVisible;
+            updateIconVisibilities();
         }
     }
 
@@ -864,9 +870,9 @@
 
     private void updateContentTransformation() {
         float contentAlpha;
-        float translationY = - mIconTransformationAmount * mIconTransformContentShift;
+        float translationY = -mContentTransformationAmount * mIconTransformContentShift;
         if (mIsLastChild) {
-            contentAlpha = 1.0f - mIconTransformationAmount;
+            contentAlpha = 1.0f - mContentTransformationAmount;
             contentAlpha = Math.min(contentAlpha / 0.5f, 1.0f);
             contentAlpha = Interpolators.ALPHA_OUT.getInterpolation(contentAlpha);
             translationY *= 0.4f;
@@ -1871,8 +1877,8 @@
         }
 
         @Override
-        protected void onYTranslationAnimationFinished() {
-            super.onYTranslationAnimationFinished();
+        protected void onYTranslationAnimationFinished(View view) {
+            super.onYTranslationAnimationFinished(view);
             if (mHeadsupDisappearRunning) {
                 setHeadsUpAnimatingAway(false);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 3687f6d..32f8ebb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -208,6 +208,12 @@
                 expandedIcon = null;
                 throw new IconException("Couldn't create icon: " + ic);
             }
+            expandedIcon.setOnVisibilityChangedListener(
+                    newVisibility -> {
+                        if (row != null) {
+                            row.setIconsVisible(newVisibility != View.VISIBLE);
+                        }
+                    });
         }
 
         public void setIconTag(int key, Object tag) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 680562a..3423e3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -18,7 +18,10 @@
 
 import android.content.Context;
 import android.content.res.Configuration;
+import android.os.SystemProperties;
 import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.util.Property;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -34,7 +37,6 @@
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.stack.StackScrollState;
 
-import java.util.ArrayList;
 import java.util.WeakHashMap;
 
 /**
@@ -43,10 +45,11 @@
  */
 public class NotificationShelf extends ActivatableNotificationView {
 
+    private static final boolean USE_ANIMATIONS_WHEN_OPENING =
+            SystemProperties.getBoolean("debug.icon_opening_animations", true);
     private ViewInvertHelper mViewInvertHelper;
     private boolean mDark;
     private NotificationIconContainer mShelfIcons;
-    private ArrayList<StatusBarIconView> mIcons = new ArrayList<>();
     private ShelfState mShelfState;
     private int[] mTmp = new int[2];
     private boolean mHideBackground;
@@ -204,8 +207,6 @@
                 continue;
             }
             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-            StatusBarIconView icon = row.getEntry().expandedIcon;
-            NotificationIconContainer.IconState iconState = iconStates.get(icon);
             float notificationClipEnd;
             float shelfStart = getTranslationY();
             boolean aboveShelf = row.getTranslationZ() > mAmbientState.getBaseZHeight();
@@ -223,8 +224,7 @@
                 }
             }
             updateNotificationClipHeight(row, notificationClipEnd);
-            float inShelfAmount = updateIconAppearance(row, iconState, icon, expandAmount,
-                    isLastChild);
+            float inShelfAmount = updateIconAppearance(row, expandAmount, isLastChild);
             numViewsInShelf += inShelfAmount;
             int ownColorUntinted = row.getBackgroundColorWithoutTint();
             if (row.getTranslationY() >= getTranslationY() && mNotGoneIndex == -1) {
@@ -250,7 +250,7 @@
         }
         mShelfIcons.calculateIconTranslations();
         mShelfIcons.applyIconStates();
-        setVisibility(numViewsInShelf != 0.0f && mAmbientState.isShadeExpanded()
+        setVisibility(mAmbientState.isShadeExpanded()
                 ? VISIBLE
                 : INVISIBLE);
         boolean hideBackground = numViewsInShelf < 1.0f;
@@ -275,41 +275,103 @@
     /**
      * @return the icon amount how much this notification is in the shelf;
      */
-    private float updateIconAppearance(ExpandableNotificationRow row,
-            NotificationIconContainer.IconState iconState, StatusBarIconView icon,
-            float expandAmount, boolean isLastChild) {
+    private float updateIconAppearance(ExpandableNotificationRow row, float expandAmount,
+            boolean isLastChild) {
         // Let calculate how much the view is in the shelf
         float viewStart = row.getTranslationY();
-        int transformHeight = row.getActualHeight() + mPaddingBetweenElements;
+        int fullHeight = row.getActualHeight() + mPaddingBetweenElements;
+        float iconTransformDistance = getIntrinsicHeight() * 1.5f;
         if (isLastChild) {
-            transformHeight =
-                    Math.min(transformHeight, row.getMinHeight() - getIntrinsicHeight());
+            fullHeight = Math.min(fullHeight, row.getMinHeight() - getIntrinsicHeight());
+            iconTransformDistance = Math.min(iconTransformDistance, row.getMinHeight()
+                    - getIntrinsicHeight());
         }
-        float viewEnd = viewStart + transformHeight;
-        float iconAppearAmount;
-        float yTranslation;
-        float alpha = 1.0f;
+        float viewEnd = viewStart + fullHeight;
+        float fullTransitionAmount;
+        float iconTransitonAmount;
         if (viewEnd >= getTranslationY() && (mAmbientState.isShadeExpanded()
                 || (!row.isPinned() && !row.isHeadsUpAnimatingAway()))) {
             if (viewStart < getTranslationY()) {
-                float linearAmount = (getTranslationY() - viewStart) / transformHeight;
+
+                float fullAmount = (getTranslationY() - viewStart) / fullHeight;
                 float interpolatedAmount =  Interpolators.ACCELERATE_DECELERATE.getInterpolation(
-                        linearAmount);
+                        fullAmount);
                 interpolatedAmount = NotificationUtils.interpolate(
-                        interpolatedAmount, linearAmount, expandAmount);
-                iconAppearAmount = 1.0f - interpolatedAmount;
+                        interpolatedAmount, fullAmount, expandAmount);
+                fullTransitionAmount = 1.0f - interpolatedAmount;
+
+                iconTransitonAmount = (getTranslationY() - viewStart) / iconTransformDistance;
+                iconTransitonAmount = Math.min(1.0f, iconTransitonAmount);
+                iconTransitonAmount = 1.0f - iconTransitonAmount;
+
             } else {
-                iconAppearAmount = 1.0f;
+                fullTransitionAmount = 1.0f;
+                iconTransitonAmount = 1.0f;
             }
         } else {
-            iconAppearAmount = 0.0f;
+            fullTransitionAmount = 0.0f;
+            iconTransitonAmount = 0.0f;
         }
+        row.setContentTransformationAmount(iconTransitonAmount, isLastChild);
+        updateIconPositioning(row, iconTransitonAmount, fullTransitionAmount);
+        return fullTransitionAmount;
+    }
 
-        // Lets now calculate how much of the transformation has already happened. This is different
-        // from the above, since we only start transforming when the view is already quite a bit
-        // pushed in.
+    private void updateIconPositioning(ExpandableNotificationRow row, float iconTransitionAmount,
+            float fullTransitionAmount) {
+        StatusBarIconView icon = row.getEntry().expandedIcon;
+        NotificationIconContainer.IconState iconState = getIconState(icon);
+        if (iconState == null) {
+            return;
+        }
+        float clampedAmount = iconTransitionAmount > 0.5f ? 1.0f : 0.0f;
+        boolean isLastChild = isLastChild(row);
+        if (clampedAmount == iconTransitionAmount) {
+            iconState.keepClampedPosition = false;
+        }
+        if (clampedAmount == fullTransitionAmount) {
+            iconState.useFullTransitionAmount = fullTransitionAmount == 0.0f;
+        }
+        float transitionAmount;
+        boolean needCannedAnimation = iconState.clampedAppearAmount == 1.0f
+                && clampedAmount == 0.0f;
+        if (isLastChild || !USE_ANIMATIONS_WHEN_OPENING || iconState.useFullTransitionAmount) {
+            transitionAmount = iconTransitionAmount;
+        } else if (iconState.keepClampedPosition
+                && iconState.clampedAppearAmount != clampedAmount) {
+            // We animated to the clamped amount but then decided to go the other way. Let's
+            // animate it to the new position
+            transitionAmount = iconTransitionAmount;
+            iconState.needsCannedAnimation = true;
+            iconState.keepClampedPosition = false;
+        } else if (needCannedAnimation || iconState.keepClampedPosition
+                || iconState.iconAppearAmount == 1.0f) {
+            // We need to perform a canned animation since we crossed the treshhold
+            transitionAmount = clampedAmount;
+            iconState.keepClampedPosition = iconState.keepClampedPosition || needCannedAnimation;
+            iconState.needsCannedAnimation = needCannedAnimation;
+        } else {
+            transitionAmount = iconTransitionAmount;
+        }
+        iconState.iconAppearAmount = !USE_ANIMATIONS_WHEN_OPENING
+                    || iconState.useFullTransitionAmount
+                ? fullTransitionAmount
+                : transitionAmount;
+        iconState.clampedAppearAmount = clampedAmount;
+        setIconTransformationAmount(row, transitionAmount);
+    }
+
+    private boolean isLastChild(ExpandableNotificationRow row) {
+        return row == mAmbientState.getLastVisibleBackgroundChild();
+    }
+
+    private void setIconTransformationAmount(ExpandableNotificationRow row,
+            float transitionAmount) {
+        StatusBarIconView icon = row.getEntry().expandedIcon;
+        NotificationIconContainer.IconState iconState = getIconState(icon);
+
         View rowIcon = row.getNotificationIcon();
-        float notificationIconPosition = viewStart;
+        float notificationIconPosition = row.getTranslationY();
         float notificationIconSize = 0.0f;
         int iconTopPadding;
         if (rowIcon != null) {
@@ -322,28 +384,18 @@
         float shelfIconPosition = getTranslationY() + icon.getTop();
         shelfIconPosition += ((1.0f - icon.getIconScale()) * icon.getHeight()) / 2.0f;
         float transitionDistance = getIntrinsicHeight() * 1.5f;
-        if (isLastChild) {
+        if (row == mAmbientState.getLastVisibleBackgroundChild()) {
             transitionDistance = Math.min(transitionDistance, row.getMinHeight()
                     - getIntrinsicHeight());
         }
         float transformationStartPosition = getTranslationY() - transitionDistance;
-        float transitionAmount = 0.0f;
-        if (viewStart < transformationStartPosition
-                || (!mAmbientState.isShadeExpanded()
-                        && (row.isPinned() || row.isHeadsUpAnimatingAway()))) {
-            // We simply place it on the icon of the notification
-            yTranslation = notificationIconPosition - shelfIconPosition;
-        } else {
-            transitionAmount = (viewStart - transformationStartPosition)
-                    / transitionDistance;
-            float startPosition = transformationStartPosition + iconTopPadding;
-            yTranslation = NotificationUtils.interpolate(
-                    startPosition - shelfIconPosition, 0, transitionAmount);
-            // If we are merging into the shelf, lets make sure the shelf is at least on our height,
-            // otherwise the icons won't be visible.
-            setTranslationZ(Math.max(getTranslationZ(), row.getTranslationZ()));
-        }
+        float iconYTranslation = NotificationUtils.interpolate(
+                Math.min(notificationIconPosition, transformationStartPosition + iconTopPadding)
+                        - shelfIconPosition,
+                0,
+                transitionAmount);
         float shelfIconSize = icon.getHeight() * icon.getIconScale();
+        float alpha = 1.0f;
         if (!row.isShowingIcon()) {
             // The view currently doesn't have an icon, lets transform it in!
             alpha = transitionAmount;
@@ -352,15 +404,12 @@
         // The notification size is different from the size in the shelf / statusbar
         float newSize = NotificationUtils.interpolate(notificationIconSize, shelfIconSize,
                 transitionAmount);
-        row.setIconTransformationAmount(transitionAmount, isLastChild);
         if (iconState != null) {
             iconState.scaleX = newSize / icon.getHeight() / icon.getIconScale();
             iconState.scaleY = iconState.scaleX;
             iconState.hidden = transitionAmount == 0.0f;
-            iconState.iconAppearAmount = iconAppearAmount;
             iconState.alpha = alpha;
-            iconState.yTranslation = yTranslation;
-            icon.setVisibility(transitionAmount == 0.0f ? INVISIBLE : VISIBLE);
+            iconState.yTranslation = iconYTranslation;
             if (row.isInShelf() && !row.isTransformingIntoShelf()) {
                 iconState.iconAppearAmount = 1.0f;
                 iconState.alpha = 1.0f;
@@ -369,7 +418,10 @@
                 iconState.hidden = false;
             }
         }
-        return iconAppearAmount;
+    }
+
+    private NotificationIconContainer.IconState getIconState(StatusBarIconView icon) {
+        return mShelfIcons.getIconState(icon);
     }
 
     private float getFullyClosedTranslation() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index d635bb0..2621e4a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -37,6 +37,7 @@
 import android.util.Log;
 import android.util.Property;
 import android.util.TypedValue;
+import android.view.View;
 import android.view.ViewDebug;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.Interpolator;
@@ -102,6 +103,7 @@
     private ObjectAnimator mIconAppearAnimator;
     private ObjectAnimator mDotAnimator;
     private float mDotAppearAmount;
+    private OnVisibilityChangedListener mOnVisibilityChangedListener;
 
     public StatusBarIconView(Context context, String slot, Notification notification) {
         this(context, slot, notification, false);
@@ -525,7 +527,23 @@
         invalidate();
     }
 
+    @Override
+    public void setVisibility(int visibility) {
+        super.setVisibility(visibility);
+        if (mOnVisibilityChangedListener != null) {
+            mOnVisibilityChangedListener.onVisibilityChanged(visibility);
+        }
+    }
+
     public float getDotAppearAmount() {
         return mDotAppearAmount;
     }
+
+    public void setOnVisibilityChangedListener(OnVisibilityChangedListener listener) {
+        mOnVisibilityChangedListener = listener;
+    }
+
+    public interface OnVisibilityChangedListener {
+        void onVisibilityChanged(int newVisibility);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 03697b8..160b233 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -40,6 +40,7 @@
 public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
     private static final String TAG = "NotificationIconContainer";
     private static final boolean DEBUG = false;
+    private static final int CANNED_ANIMATION_DURATION = 100;
     private static final AnimationProperties DOT_ANIMATION_PROPERTIES = new AnimationProperties() {
         private AnimationFilter mAnimationFilter = new AnimationFilter().animateX();
 
@@ -49,6 +50,26 @@
         }
     }.setDuration(200);
 
+    private static final AnimationProperties ICON_ANIMATION_PROPERTIES = new AnimationProperties() {
+        private AnimationFilter mAnimationFilter = new AnimationFilter().animateY().animateAlpha();
+        // TODO: add scale
+
+        @Override
+        public AnimationFilter getAnimationFilter() {
+            return mAnimationFilter;
+        }
+    }.setDuration(CANNED_ANIMATION_DURATION);
+
+    private static final AnimationProperties mTempProperties = new AnimationProperties() {
+        private AnimationFilter mAnimationFilter = new AnimationFilter();
+        // TODO: add scale
+
+        @Override
+        public AnimationFilter getAnimationFilter() {
+            return mAnimationFilter;
+        }
+    }.setDuration(CANNED_ANIMATION_DURATION);
+
     private static final AnimationProperties ADD_ICON_PROPERTIES = new AnimationProperties() {
         private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
 
@@ -66,7 +87,8 @@
     private float mActualPaddingEnd = -1;
     private float mActualPaddingStart = -1;
     private boolean mChangingViewPositions;
-    private int mAnimationStartIndex = -1;
+    private int mAddAnimationStartIndex = -1;
+    private int mCannedAnimationStartIndex = -1;
 
     public NotificationIconContainer(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -121,7 +143,8 @@
                 childState.applyToView(child);
             }
         }
-        mAnimationStartIndex = -1;
+        mAddAnimationStartIndex = -1;
+        mCannedAnimationStartIndex = -1;
     }
 
     @Override
@@ -133,10 +156,10 @@
         int childIndex = indexOfChild(child);
         if (childIndex < getChildCount() - 1
             && mIconStates.get(getChildAt(childIndex + 1)).iconAppearAmount > 0.0f) {
-            if (mAnimationStartIndex < 0) {
-                mAnimationStartIndex = childIndex;
+            if (mAddAnimationStartIndex < 0) {
+                mAddAnimationStartIndex = childIndex;
             } else {
-                mAnimationStartIndex = Math.min(mAnimationStartIndex, childIndex);
+                mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, childIndex);
             }
         }
     }
@@ -149,10 +172,10 @@
             if (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN
                     && child.getVisibility() == VISIBLE) {
                 int animationStartIndex = findFirstViewIndexAfter(icon.getTranslationX());
-                if (mAnimationStartIndex < 0) {
-                    mAnimationStartIndex = animationStartIndex;
+                if (mAddAnimationStartIndex < 0) {
+                    mAddAnimationStartIndex = animationStartIndex;
                 } else {
-                    mAnimationStartIndex = Math.min(mAnimationStartIndex, animationStartIndex);
+                    mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, animationStartIndex);
                 }
             }
             if (!mChangingViewPositions) {
@@ -305,28 +328,64 @@
         mChangingViewPositions = changingViewPositions;
     }
 
+    public IconState getIconState(StatusBarIconView icon) {
+        return mIconStates.get(icon);
+    }
+
     public class IconState extends ViewState {
         public float iconAppearAmount = 1.0f;
+        public float clampedAppearAmount = 1.0f;
         public int visibleState;
         public boolean justAdded = true;
+        public boolean needsCannedAnimation;
+        public boolean keepClampedPosition;
+        public boolean useFullTransitionAmount;
 
         @Override
         public void applyToView(View view) {
             if (view instanceof StatusBarIconView) {
                 StatusBarIconView icon = (StatusBarIconView) view;
-                AnimationProperties animationProperties = DOT_ANIMATION_PROPERTIES;
+                boolean animate = false;
+                AnimationProperties animationProperties = null;
                 if (justAdded) {
                     super.applyToView(icon);
                     icon.setAlpha(0.0f);
                     icon.setVisibleState(StatusBarIconView.STATE_HIDDEN, false /* animate */);
                     animationProperties = ADD_ICON_PROPERTIES;
+                    animate = true;
+                } else if (visibleState != icon.getVisibleState()) {
+                    animationProperties = DOT_ANIMATION_PROPERTIES;
+                    animate = true;
                 }
-                boolean animate = visibleState != icon.getVisibleState() || justAdded;
-                if (!animate && mAnimationStartIndex >= 0
+                if (!animate && mAddAnimationStartIndex >= 0
+                        && indexOfChild(view) >= mAddAnimationStartIndex
                         && (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN
                             || visibleState != StatusBarIconView.STATE_HIDDEN)) {
-                    int viewIndex = indexOfChild(view);
-                    animate = viewIndex >= mAnimationStartIndex;
+                    animationProperties = DOT_ANIMATION_PROPERTIES;
+                    animate = true;
+                }
+                if (needsCannedAnimation) {
+                    AnimationFilter animationFilter = mTempProperties.getAnimationFilter();
+                    animationFilter.reset();
+                    animationFilter.combineFilter(ICON_ANIMATION_PROPERTIES.getAnimationFilter());
+                    if (animationProperties != null) {
+                        animationFilter.combineFilter(animationProperties.getAnimationFilter());
+                    }
+                    animationProperties = mTempProperties;
+                    animationProperties.setDuration(CANNED_ANIMATION_DURATION);
+                    animate = true;
+                    mCannedAnimationStartIndex = indexOfChild(view);
+                }
+                if (!animate && mCannedAnimationStartIndex >= 0
+                        && indexOfChild(view) > mCannedAnimationStartIndex
+                        && (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN
+                        || visibleState != StatusBarIconView.STATE_HIDDEN)) {
+                    AnimationFilter animationFilter = mTempProperties.getAnimationFilter();
+                    animationFilter.reset();
+                    animationFilter.animateX();
+                    animationProperties = mTempProperties;
+                    animationProperties.setDuration(CANNED_ANIMATION_DURATION);
+                    animate = true;
                 }
                 icon.setVisibleState(visibleState);
                 if (animate) {
@@ -336,6 +395,13 @@
                 }
             }
             justAdded = false;
+            needsCannedAnimation = false;
+        }
+
+        protected void onYTranslationAnimationFinished(View view) {
+            if (hidden) {
+                view.setVisibility(INVISIBLE);
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
index d3d58f9..38bb40e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
@@ -120,7 +120,7 @@
         }
     }
 
-    private void combineFilter(AnimationFilter filter) {
+    public void combineFilter(AnimationFilter filter) {
         animateAlpha |= filter.animateAlpha;
         animateX |= filter.animateX;
         animateY |= filter.animateY;
@@ -134,7 +134,7 @@
         hasDelays |= filter.hasDelays;
     }
 
-    private void reset() {
+    public void reset() {
         animateAlpha = false;
         animateX = false;
         animateY = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index b8f8cb2..22709f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -213,7 +213,7 @@
         mDividers.add(newIndex, divider);
 
         updateGroupOverflow();
-        row.setIconTransformationAmount(0, false /* isLastChild */);
+        row.setContentTransformationAmount(0, false /* isLastChild */);
     }
 
     public void removeNotification(ExpandableNotificationRow row) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
index 8a5ddd4..a8e5ac6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
@@ -99,36 +99,6 @@
             // don't do anything with it
             return;
         }
-        boolean becomesInvisible = this.alpha == 0.0f || this.hidden;
-        boolean animatingAlpha = isAnimating(view, TAG_ANIMATOR_ALPHA);
-        if (animatingAlpha) {
-            updateAlphaAnimation(view);
-        } else if (view.getAlpha() != this.alpha) {
-            // apply layer type
-            boolean becomesFullyVisible = this.alpha == 1.0f;
-            boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible
-                    && view.hasOverlappingRendering();
-            int layerType = view.getLayerType();
-            int newLayerType = newLayerTypeIsHardware
-                    ? View.LAYER_TYPE_HARDWARE
-                    : View.LAYER_TYPE_NONE;
-            if (layerType != newLayerType) {
-                view.setLayerType(newLayerType, null);
-            }
-
-            // apply alpha
-            view.setAlpha(this.alpha);
-        }
-
-        // apply visibility
-        int oldVisibility = view.getVisibility();
-        int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE;
-        if (newVisibility != oldVisibility) {
-            if (!(view instanceof ExpandableView) || !((ExpandableView) view).willBeGone()) {
-                // We don't want views to change visibility when they are animating to GONE
-                view.setVisibility(newVisibility);
-            }
-        }
 
         // apply xTranslation
         boolean animatingX = isAnimating(view, TAG_ANIMATOR_TRANSLATION_X);
@@ -163,6 +133,53 @@
         if (view.getScaleY() != this.scaleY) {
             view.setScaleY(this.scaleY);
         }
+
+        boolean becomesInvisible = this.alpha == 0.0f || (this.hidden && !isAnimating(view));
+        boolean animatingAlpha = isAnimating(view, TAG_ANIMATOR_ALPHA);
+        if (animatingAlpha) {
+            updateAlphaAnimation(view);
+        } else if (view.getAlpha() != this.alpha) {
+            // apply layer type
+            boolean becomesFullyVisible = this.alpha == 1.0f;
+            boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible
+                    && view.hasOverlappingRendering();
+            int layerType = view.getLayerType();
+            int newLayerType = newLayerTypeIsHardware
+                    ? View.LAYER_TYPE_HARDWARE
+                    : View.LAYER_TYPE_NONE;
+            if (layerType != newLayerType) {
+                view.setLayerType(newLayerType, null);
+            }
+
+            // apply alpha
+            view.setAlpha(this.alpha);
+        }
+
+        // apply visibility
+        int oldVisibility = view.getVisibility();
+        int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE;
+        if (newVisibility != oldVisibility) {
+            if (!(view instanceof ExpandableView) || !((ExpandableView) view).willBeGone()) {
+                // We don't want views to change visibility when they are animating to GONE
+                view.setVisibility(newVisibility);
+            }
+        }
+    }
+
+    protected boolean isAnimating(View view) {
+        if (isAnimating(view, TAG_ANIMATOR_TRANSLATION_X)) {
+            return true;
+        }
+        if (isAnimating(view, TAG_ANIMATOR_TRANSLATION_Y)) {
+            return true;
+        }
+        if (isAnimating(view, TAG_ANIMATOR_TRANSLATION_Z)) {
+            return true;
+        }
+        if (isAnimating(view, TAG_ANIMATOR_ALPHA)) {
+            return true;
+        }
+        return false;
     }
 
     private boolean isAnimating(View view, int tag) {
@@ -482,7 +499,7 @@
                 child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
                 child.setTag(TAG_START_TRANSLATION_Y, null);
                 child.setTag(TAG_END_TRANSLATION_Y, null);
-                onYTranslationAnimationFinished();
+                onYTranslationAnimationFinished(child);
             }
         });
         startAnimator(animator, listener);
@@ -491,7 +508,10 @@
         child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
     }
 
-    protected void onYTranslationAnimationFinished() {
+    protected void onYTranslationAnimationFinished(View view) {
+        if (hidden) {
+            view.setVisibility(View.INVISIBLE);
+        }
     }
 
     protected void startAnimator(Animator animator, AnimatorListenerAdapter listener) {