Fixed a flicker of group notifications

When replacing a supressed group with it's summary, the icon
would flicker. We're now suppressing icon animations of
replaced notifications.

Test: add notification and children 1 by 1 and observe no flickering
Change-Id: I79440e473296e766ab80832ce74f450aee1af8ea
Fixes: 38459207
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 1844946..dd15d6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -141,12 +141,12 @@
 
             // Construct the icon.
             icon = new StatusBarIconView(context,
-                    sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), n);
+                    sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
             icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
 
             // Construct the expanded icon.
             expandedIcon = new StatusBarIconView(context,
-                    sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), n);
+                    sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
             expandedIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
             final StatusBarIcon ic = new StatusBarIcon(
                     sbn.getUser(),
@@ -182,9 +182,11 @@
          * @param n the notification to read the icon from.
          * @throws InflationException
          */
-        public void updateIcons(Context context, Notification n) throws InflationException {
+        public void updateIcons(Context context, StatusBarNotification sbn)
+                throws InflationException {
             if (icon != null) {
                 // Update the icon
+                Notification n = sbn.getNotification();
                 final StatusBarIcon ic = new StatusBarIcon(
                         notification.getUser(),
                         notification.getPackageName(),
@@ -192,8 +194,8 @@
                         n.iconLevel,
                         n.number,
                         StatusBarIconView.contentDescForNotification(context, n));
-                icon.setNotification(n);
-                expandedIcon.setNotification(n);
+                icon.setNotification(sbn);
+                expandedIcon.setNotification(sbn);
                 if (!icon.set(ic) || !expandedIcon.set(ic)) {
                     throw new InflationException("Couldn't update icon: " + ic);
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index c7fbbf9..e489ba7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -34,6 +34,7 @@
 import android.graphics.drawable.Icon;
 import android.os.Parcelable;
 import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
@@ -99,7 +100,7 @@
     private int mNumberX;
     private int mNumberY;
     private String mNumberText;
-    private Notification mNotification;
+    private StatusBarNotification mNotification;
     private final boolean mBlocked;
     private int mDensity;
     private float mIconScale = 1.0f;
@@ -127,11 +128,11 @@
     };
     private final NotificationIconDozeHelper mDozer;
 
-    public StatusBarIconView(Context context, String slot, Notification notification) {
-        this(context, slot, notification, false);
+    public StatusBarIconView(Context context, String slot, StatusBarNotification sbn) {
+        this(context, slot, sbn, false);
     }
 
-    public StatusBarIconView(Context context, String slot, Notification notification,
+    public StatusBarIconView(Context context, String slot, StatusBarNotification sbn,
             boolean blocked) {
         super(context);
         mDozer = new NotificationIconDozeHelper(context);
@@ -141,7 +142,7 @@
         mNumberPain.setTextAlign(Paint.Align.CENTER);
         mNumberPain.setColor(context.getColor(R.drawable.notification_number_text_color));
         mNumberPain.setAntiAlias(true);
-        setNotification(notification);
+        setNotification(sbn);
         maybeUpdateIconScaleDimens();
         setScaleType(ScaleType.CENTER);
         mDensity = context.getResources().getDisplayMetrics().densityDpi;
@@ -203,9 +204,11 @@
         }
     }
 
-    public void setNotification(Notification notification) {
+    public void setNotification(StatusBarNotification notification) {
         mNotification = notification;
-        setContentDescription(notification);
+        if (notification != null) {
+            setContentDescription(notification.getNotification());
+        }
     }
 
     public StatusBarIconView(Context context, AttributeSet attrs) {
@@ -311,6 +314,10 @@
         return true;
     }
 
+    public Icon getSourceIcon() {
+        return mIcon.icon;
+    }
+
     private Drawable getIcon(StatusBarIcon icon) {
         return getIcon(getContext(), icon);
     }
@@ -350,7 +357,7 @@
     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
         super.onInitializeAccessibilityEvent(event);
         if (mNotification != null) {
-            event.setParcelableData(mNotification);
+            event.setParcelableData(mNotification.getNotification());
         }
     }
 
@@ -452,6 +459,10 @@
             + " notification=" + mNotification + ")";
     }
 
+    public StatusBarNotification getNotification() {
+        return mNotification;
+    }
+
     public String getSlot() {
         return mSlot;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 52838b0..5bfdd1f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -4,11 +4,15 @@
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Rect;
+import android.graphics.drawable.Icon;
 import android.support.annotation.NonNull;
+import android.support.v4.util.ArrayMap;
+import android.support.v4.util.ArraySet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.FrameLayout;
 
+import com.android.internal.statusbar.StatusBarIcon;
 import com.android.internal.util.NotificationColorUtil;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
@@ -180,14 +184,54 @@
             }
         }
 
+        // In case we are changing the suppression of a group, the replacement shouldn't flicker
+        // and it should just be replaced instead. We therefore look for notifications that were
+        // just replaced by the child or vice-versa to suppress this.
 
+        ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons = new ArrayMap<>();
         ArrayList<View> toRemove = new ArrayList<>();
         for (int i = 0; i < hostLayout.getChildCount(); i++) {
             View child = hostLayout.getChildAt(i);
+            if (!(child instanceof StatusBarIconView)) {
+                continue;
+            }
             if (!toShow.contains(child)) {
-                toRemove.add(child);
+                boolean iconWasReplaced = false;
+                StatusBarIconView removedIcon = (StatusBarIconView) child;
+                String removedGroupKey = removedIcon.getNotification().getGroupKey();
+                for (int j = 0; j < toShow.size(); j++) {
+                    StatusBarIconView candidate = toShow.get(j);
+                    if (candidate.getSourceIcon().sameAs((removedIcon.getSourceIcon()))
+                            && candidate.getNotification().getGroupKey().equals(removedGroupKey)) {
+                        if (!iconWasReplaced) {
+                            iconWasReplaced = true;
+                        } else {
+                            iconWasReplaced = false;
+                            break;
+                        }
+                    }
+                }
+                if (iconWasReplaced) {
+                    ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(removedGroupKey);
+                    if (statusBarIcons == null) {
+                        statusBarIcons = new ArrayList<>();
+                        replacingIcons.put(removedGroupKey, statusBarIcons);
+                    }
+                    statusBarIcons.add(removedIcon.getStatusBarIcon());
+                }
+                toRemove.add(removedIcon);
             }
         }
+        // removing all duplicates
+        ArrayList<String> duplicates = new ArrayList<>();
+        for (String key : replacingIcons.keySet()) {
+            ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(key);
+            if (statusBarIcons.size() != 1) {
+                duplicates.add(key);
+            }
+        }
+        replacingIcons.removeAll(duplicates);
+        hostLayout.setReplacingIcons(replacingIcons);
 
         final int toRemoveCount = toRemove.size();
         for (int i = 0; i < toRemoveCount; i++) {
@@ -217,6 +261,7 @@
             hostLayout.addView(expected, i);
         }
         hostLayout.setChangingViewPositions(false);
+        hostLayout.setReplacingIcons(null);
     }
 
     /**
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 9000eb4..06b8041 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -21,9 +21,13 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.graphics.drawable.Icon;
+import android.support.v4.util.ArrayMap;
+import android.support.v4.util.ArraySet;
 import android.util.AttributeSet;
 import android.view.View;
 
+import com.android.internal.statusbar.StatusBarIcon;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.AlphaOptimizedFrameLayout;
@@ -33,6 +37,7 @@
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 import com.android.systemui.statusbar.stack.ViewState;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 
 /**
@@ -117,6 +122,7 @@
     private float mVisualOverflowAdaption;
     private boolean mDisallowNextAnimation;
     private boolean mAnimationsEnabled = true;
+    private ArrayMap<String, ArrayList<StatusBarIcon>> mReplacingIcons;
 
     public NotificationIconContainer(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -183,11 +189,17 @@
     @Override
     public void onViewAdded(View child) {
         super.onViewAdded(child);
+        boolean isReplacingIcon = isReplacingIcon(child);
         if (!mChangingViewPositions) {
-            mIconStates.put(child, new IconState());
+            IconState v = new IconState();
+            if (isReplacingIcon) {
+                v.justAdded = false;
+                v.justReplaced = true;
+            }
+            mIconStates.put(child, v);
         }
         int childIndex = indexOfChild(child);
-        if (childIndex < getChildCount() - 1
+        if (childIndex < getChildCount() - 1 && !isReplacingIcon
             && mIconStates.get(getChildAt(childIndex + 1)).iconAppearAmount > 0.0f) {
             if (mAddAnimationStartIndex < 0) {
                 mAddAnimationStartIndex = childIndex;
@@ -200,13 +212,34 @@
         }
     }
 
+    private boolean isReplacingIcon(View child) {
+        if (mReplacingIcons == null) {
+            return false;
+        }
+        if (!(child instanceof StatusBarIconView)) {
+            return false;
+        }
+        StatusBarIconView iconView = (StatusBarIconView) child;
+        Icon sourceIcon = iconView.getSourceIcon();
+        String groupKey = iconView.getNotification().getGroupKey();
+        ArrayList<StatusBarIcon> statusBarIcons = mReplacingIcons.get(groupKey);
+        if (statusBarIcons != null) {
+            StatusBarIcon replacedIcon = statusBarIcons.get(0);
+            if (sourceIcon.sameAs(replacedIcon.icon)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     @Override
     public void onViewRemoved(View child) {
         super.onViewRemoved(child);
         if (child instanceof StatusBarIconView) {
+            boolean isReplacingIcon = isReplacingIcon(child);
             final StatusBarIconView icon = (StatusBarIconView) child;
             if (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN
-                    && child.getVisibility() == VISIBLE) {
+                    && child.getVisibility() == VISIBLE && isReplacingIcon) {
                 int animationStartIndex = findFirstViewIndexAfter(icon.getTranslationX());
                 if (mAddAnimationStartIndex < 0) {
                     mAddAnimationStartIndex = animationStartIndex;
@@ -216,9 +249,11 @@
             }
             if (!mChangingViewPositions) {
                 mIconStates.remove(child);
-                addTransientView(icon, 0);
-                icon.setVisibleState(StatusBarIconView.STATE_HIDDEN, true /* animate */,
-                        () -> removeTransientView(icon));
+                if (!isReplacingIcon) {
+                    addTransientView(icon, 0);
+                    icon.setVisibleState(StatusBarIconView.STATE_HIDDEN, true /* animate */,
+                            () -> removeTransientView(icon));
+                }
             }
         }
     }
@@ -470,11 +505,16 @@
         mAnimationsEnabled = enabled;
     }
 
+    public void setReplacingIcons(ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons) {
+        mReplacingIcons = replacingIcons;
+    }
+
     public class IconState extends ViewState {
         public float iconAppearAmount = 1.0f;
         public float clampedAppearAmount = 1.0f;
         public int visibleState;
         public boolean justAdded = true;
+        private boolean justReplaced;
         public boolean needsCannedAnimation;
         public boolean useFullTransitionAmount;
         public boolean useLinearTransitionAmount;
@@ -493,9 +533,9 @@
                         && !mDisallowNextAnimation
                         && !noAnimations;
                 if (animationsAllowed) {
-                    if (justAdded) {
+                    if (justAdded || justReplaced) {
                         super.applyToView(icon);
-                        if (iconAppearAmount != 0.0f) {
+                        if (justAdded && iconAppearAmount != 0.0f) {
                             icon.setAlpha(0.0f);
                             icon.setVisibleState(StatusBarIconView.STATE_HIDDEN,
                                     false /* animate */);
@@ -554,6 +594,7 @@
                 }
             }
             justAdded = false;
+            justReplaced = false;
             needsCannedAnimation = false;
             justUndarkened = false;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 07ab6876..9b7a1a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -6762,7 +6762,7 @@
         entry.notification = notification;
         mGroupManager.onEntryUpdated(entry, oldNotification);
 
-        entry.updateIcons(mContext, n);
+        entry.updateIcons(mContext, notification);
         inflateViews(entry, mStackScroller);
 
         boolean shouldPeek = shouldPeek(entry, notification);