Improved the performance of the notification shelf

The shelf had a few inefficiencies that were adding up
when calculating the positions.

Test: runtest systemui-jank -c android.platform.systemui.tests.jank.SystemUiJankTests -m testNotificationListPull_manyNotifications
Bug: 32437839
Change-Id: Iac08a7c364a924f1d0c14258461383b431f0542b
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index 173f160..d4b478a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -103,7 +103,7 @@
     private boolean mDimmed;
     private boolean mDark;
 
-    private int mBgTint = 0;
+    private int mBgTint = NO_COLOR;
     private float mBgAlpha = 1f;
 
     /**
@@ -481,8 +481,10 @@
      * Sets the tint color of the background
      */
     public void setTintColor(int color, boolean animated) {
-        mBgTint = color;
-        updateBackgroundTint(animated);
+        if (color != mBgTint) {
+            mBgTint = color;
+            updateBackgroundTint(animated);
+        }
     }
 
     /**
@@ -541,13 +543,15 @@
     }
 
     private void setBackgroundTintColor(int color) {
-        mCurrentBackgroundTint = color;
-        if (color == mNormalColor) {
-            // We don't need to tint a normal notification
-            color = 0;
+        if (color != mCurrentBackgroundTint) {
+            mCurrentBackgroundTint = color;
+            if (color == mNormalColor) {
+                // We don't need to tint a normal notification
+                color = 0;
+            }
+            mBackgroundDimmed.setTint(color);
+            mBackgroundNormal.setTint(color);
         }
-        mBackgroundDimmed.setTint(color);
-        mBackgroundNormal.setTint(color);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index e4654e6..996e2ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -1685,13 +1685,17 @@
 
     @Override
     public void setClipBottomAmount(int clipBottomAmount) {
-        super.setClipBottomAmount(clipBottomAmount);
-        mPrivateLayout.setClipBottomAmount(clipBottomAmount);
-        mPublicLayout.setClipBottomAmount(clipBottomAmount);
-        if (mGuts != null) {
-            mGuts.setClipBottomAmount(clipBottomAmount);
+        if (clipBottomAmount != mClipBottomAmount) {
+            super.setClipBottomAmount(clipBottomAmount);
+            mPrivateLayout.setClipBottomAmount(clipBottomAmount);
+            mPublicLayout.setClipBottomAmount(clipBottomAmount);
+            if (mGuts != null) {
+                mGuts.setClipBottomAmount(clipBottomAmount);
+            }
         }
         if (mChildrenContainer != null) {
+            // We have to update this even if it hasn't changed, since the children locations can
+            // have changed
             mChildrenContainer.setClipBottomAmount(clipBottomAmount);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 0138ca8..80d4188 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -37,8 +37,6 @@
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.stack.StackScrollState;
 
-import java.util.WeakHashMap;
-
 /**
  * A notification shelf view that is placed inside the notification scroller. It manages the
  * overflow icons that don't fit into the regular list anymore.
@@ -165,6 +163,7 @@
                 mShelfState.notGoneIndex = Math.min(mShelfState.notGoneIndex, mNotGoneIndex);
             }
             mShelfState.hasItemsInStableShelf = lastViewState.inShelf;
+            mShelfState.hidden = !mAmbientState.isShadeExpanded();
         } else {
             mShelfState.hidden = true;
             mShelfState.location = ExpandableViewState.LOCATION_GONE;
@@ -177,15 +176,15 @@
      * the icons from the notification area into the shelf.
      */
     public void updateAppearance() {
-        WeakHashMap<View, NotificationIconContainer.IconState> iconStates =
-                mShelfIcons.resetViewStates();
+        mShelfIcons.resetViewStates();
+        float shelfStart = getTranslationY();
         float numViewsInShelf = 0.0f;
         View lastChild = mAmbientState.getLastVisibleBackgroundChild();
         mNotGoneIndex = -1;
         float interpolationStart = mMaxLayoutHeight - getIntrinsicHeight() * 2;
         float expandAmount = 0.0f;
-        if (getTranslationY() >= interpolationStart) {
-            expandAmount = (getTranslationY() - interpolationStart) / getIntrinsicHeight();
+        if (shelfStart >= interpolationStart) {
+            expandAmount = (shelfStart - interpolationStart) / getIntrinsicHeight();
             expandAmount = Math.min(1.0f, expandAmount);
         }
         //  find the first view that doesn't overlap with the shelf
@@ -199,6 +198,7 @@
         int colorTwoBefore = NO_COLOR;
         int previousColor = NO_COLOR;
         float transitionAmount = 0.0f;
+        int baseZHeight = mAmbientState.getBaseZHeight();
         while (notificationIndex < mHostLayout.getChildCount()) {
             ExpandableView child = (ExpandableView) mHostLayout.getChildAt(notificationIndex);
             notificationIndex++;
@@ -208,26 +208,26 @@
             }
             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
             float notificationClipEnd;
-            float shelfStart = getTranslationY();
-            boolean aboveShelf = row.getTranslationZ() > mAmbientState.getBaseZHeight();
+            boolean aboveShelf = row.getTranslationZ() > baseZHeight;
             boolean isLastChild = child == lastChild;
+            float rowTranslationY = row.getTranslationY();
             if (isLastChild || aboveShelf || backgroundForceHidden) {
                 notificationClipEnd = shelfStart + getIntrinsicHeight();
             } else {
                 notificationClipEnd = shelfStart - mPaddingBetweenElements;
-                float height = notificationClipEnd - row.getTranslationY();
+                float height = notificationClipEnd - rowTranslationY;
                 if (!row.isBelowSpeedBump() && height <= getNotificationMergeSize()) {
                     // We want the gap to close when we reached the minimum size and only shrink
                     // before
                     notificationClipEnd = Math.min(shelfStart,
-                            row.getTranslationY() + getNotificationMergeSize());
+                            rowTranslationY + getNotificationMergeSize());
                 }
             }
             updateNotificationClipHeight(row, notificationClipEnd);
             float inShelfAmount = updateIconAppearance(row, expandAmount, isLastChild);
             numViewsInShelf += inShelfAmount;
             int ownColorUntinted = row.getBackgroundColorWithoutTint();
-            if (row.getTranslationY() >= getTranslationY() && mNotGoneIndex == -1) {
+            if (rowTranslationY >= shelfStart && mNotGoneIndex == -1) {
                 mNotGoneIndex = notGoneIndex;
                 setTintColor(previousColor);
                 setOverrideTintColor(colorTwoBefore, transitionAmount);
@@ -250,9 +250,6 @@
         }
         mShelfIcons.calculateIconTranslations();
         mShelfIcons.applyIconStates();
-        setVisibility(mAmbientState.isShadeExpanded()
-                ? VISIBLE
-                : INVISIBLE);
         boolean hideBackground = numViewsInShelf < 1.0f;
         setHideBackground(hideBackground || backgroundForceHidden);
         if (mNotGoneIndex == -1) {
@@ -441,9 +438,11 @@
     }
 
     private void setHideBackground(boolean hideBackground) {
-        mHideBackground = hideBackground;
-        updateBackground();
-        updateOutline();
+        if (mHideBackground != hideBackground) {
+            mHideBackground = hideBackground;
+            updateBackground();
+            updateOutline();
+        }
     }
 
     public boolean hidesBackground() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 2621e4a..6650e72 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -455,6 +455,7 @@
     }
 
     public void setVisibleState(int visibleState, boolean animate, Runnable endRunnable) {
+        boolean runnableAdded = false;
         if (visibleState != mVisibleState) {
             mVisibleState = visibleState;
             if (animate) {
@@ -467,20 +468,22 @@
                     targetAmount = 1.0f;
                     interpolator = Interpolators.LINEAR_OUT_SLOW_IN;
                 }
-                mIconAppearAnimator = ObjectAnimator.ofFloat(this, ICON_APPEAR_AMOUNT,
-                        targetAmount);
-                mIconAppearAnimator.setInterpolator(interpolator);
-                mIconAppearAnimator.setDuration(100);
-                mIconAppearAnimator.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        mIconAppearAnimator = null;
-                        if (endRunnable != null) {
-                            endRunnable.run();
+                float currentAmount = getIconAppearAmount();
+                if (targetAmount != currentAmount) {
+                    mIconAppearAnimator = ObjectAnimator.ofFloat(this, ICON_APPEAR_AMOUNT,
+                            currentAmount, targetAmount);
+                    mIconAppearAnimator.setInterpolator(interpolator);
+                    mIconAppearAnimator.setDuration(100);
+                    mIconAppearAnimator.addListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            mIconAppearAnimator = null;
+                            runRunnable(endRunnable);
                         }
-                    }
-                });
-                mIconAppearAnimator.start();
+                    });
+                    mIconAppearAnimator.start();
+                    runnableAdded = true;
+                }
 
                 if (mDotAnimator != null) {
                     mDotAnimator.cancel();
@@ -491,22 +494,39 @@
                     targetAmount = 1.0f;
                     interpolator = Interpolators.LINEAR_OUT_SLOW_IN;
                 }
-                mDotAnimator = ObjectAnimator.ofFloat(this, DOT_APPEAR_AMOUNT,
-                        targetAmount);
-                mDotAnimator.setInterpolator(interpolator);
-                mDotAnimator.setDuration(100);
-                mDotAnimator.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        mDotAnimator = null;
-                    }
-                });
-                mDotAnimator.start();
+                currentAmount = getDotAppearAmount();
+                if (targetAmount != currentAmount) {
+                    mDotAnimator = ObjectAnimator.ofFloat(this, DOT_APPEAR_AMOUNT,
+                            currentAmount, targetAmount);
+                    mDotAnimator.setInterpolator(interpolator);
+                    mDotAnimator.setDuration(100);
+                    final boolean runRunnable = !runnableAdded;
+                    mDotAnimator.addListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            mDotAnimator = null;
+                            if (runRunnable) {
+                                runRunnable(endRunnable);
+                            }
+                        }
+                    });
+                    mDotAnimator.start();
+                    runnableAdded = true;
+                }
             } else {
                 setIconAppearAmount(visibleState == STATE_ICON ? 1.0f : 0.0f);
                 setDotAppearAmount(visibleState == STATE_DOT ? 1.0f : 0.0f);
             }
         }
+        if (!runnableAdded) {
+            runRunnable(endRunnable);
+        }
+    }
+
+    private void runRunnable(Runnable runnable) {
+        if (runnable != null) {
+            runnable.run();
+        }
     }
 
     public void setIconAppearAmount(float iconAppearAmount) {
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 160b233..2940584 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -31,7 +31,7 @@
 import com.android.systemui.statusbar.stack.AnimationProperties;
 import com.android.systemui.statusbar.stack.ViewState;
 
-import java.util.WeakHashMap;
+import java.util.HashMap;
 
 /**
  * A container for notification icons. It handles overflowing icons properly and positions them
@@ -80,7 +80,7 @@
     }.setDuration(200).setDelay(50);
 
     private boolean mShowAllIcons = true;
-    private WeakHashMap<View, IconState> mIconStates = new WeakHashMap<>();
+    private final HashMap<View, IconState> mIconStates = new HashMap<>();
     private int mDotPadding;
     private int mStaticDotRadius;
     private int mActualLayoutWidth = -1;
@@ -200,14 +200,13 @@
         return getChildCount();
     }
 
-    public WeakHashMap<View, IconState> resetViewStates() {
+    public void resetViewStates() {
         for (int i = 0; i < getChildCount(); i++) {
             View view = getChildAt(i);
             ViewState iconState = mIconStates.get(view);
             iconState.initFrom(view);
             iconState.alpha = 1.0f;
         }
-        return mIconStates;
     }
 
     /**
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 10d995c..4fb982b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -715,7 +715,6 @@
             requestChildrenUpdate();
         }
         setStackTranslation(translationY);
-        requestChildrenUpdate();
     }
 
     private void setRequestedClipBounds(Rect clipRect) {