Inline replace notification views when layout changes

Instead of going through a full remove / add when a notification
is updated with a different layout, only re-inflate the inner views.

Bug: 15869868
Change-Id: Ie18c431e7b3e2a6209d4a9b6418b3150781a063f
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 17757ff..df3e25d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -897,15 +897,15 @@
     protected void onShowSearchPanel() {
     }
 
-    public boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
+    private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
             return inflateViews(entry, parent, false);
     }
 
-    public boolean inflateViewsForHeadsUp(NotificationData.Entry entry, ViewGroup parent) {
+    protected boolean inflateViewsForHeadsUp(NotificationData.Entry entry, ViewGroup parent) {
             return inflateViews(entry, parent, true);
     }
 
-    public boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) {
+    private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) {
         int maxHeight = mRowMaxHeight;
         StatusBarNotification sbn = entry.notification;
         RemoteViews contentView = sbn.getNotification().contentView;
@@ -927,11 +927,30 @@
 
         Notification publicNotification = sbn.getNotification().publicVersion;
 
-        // create the row view
-        LayoutInflater inflater = (LayoutInflater)mContext.getSystemService(
-                Context.LAYOUT_INFLATER_SERVICE);
-        ExpandableNotificationRow row = (ExpandableNotificationRow) inflater.inflate(
-                R.layout.status_bar_notification_row, parent, false);
+        ExpandableNotificationRow row;
+
+        // Stash away previous user expansion state so we can restore it at
+        // the end.
+        boolean hasUserChangedExpansion = false;
+        boolean userExpanded = false;
+        boolean userLocked = false;
+
+        if (entry.row != null) {
+            row = entry.row;
+            hasUserChangedExpansion = row.hasUserChangedExpansion();
+            userExpanded = row.isUserExpanded();
+            userLocked = row.isUserLocked();
+            row.reset();
+            if (hasUserChangedExpansion) {
+                row.setUserExpanded(userExpanded);
+            }
+        } else {
+            // create the row view
+            LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
+                    Context.LAYOUT_INFLATER_SERVICE);
+            row = (ExpandableNotificationRow) inflater.inflate(R.layout.status_bar_notification_row,
+                    parent, false);
+        }
 
         // for blaming (see SwipeHelper.setLongPressListener)
         row.setTag(sbn.getPackageName());
@@ -1077,6 +1096,14 @@
 
         applyLegacyRowBackground(sbn, entry);
 
+        // Restore previous flags.
+        if (hasUserChangedExpansion) {
+            // Note: setUserExpanded() conveniently ignores calls with
+            //       userExpanded=true if !isExpandable().
+            row.setUserExpanded(userExpanded);
+        }
+        row.setUserLocked(userLocked);
+
         return true;
     }
 
@@ -1317,12 +1344,13 @@
             RankingMap ranking);
     protected abstract void updateNotificationRanking(RankingMap ranking);
     public abstract void removeNotification(String key, RankingMap ranking);
+
     public void updateNotification(StatusBarNotification notification, RankingMap ranking) {
         if (DEBUG) Log.d(TAG, "updateNotification(" + notification + ")");
 
         final String key = notification.getKey();
         boolean wasHeadsUp = isHeadsUp(key);
-        NotificationData.Entry oldEntry;
+        Entry oldEntry;
         if (wasHeadsUp) {
             oldEntry = mHeadsUpNotificationView.getEntry();
         } else {
@@ -1363,8 +1391,7 @@
                     + " publicView=" + publicContentView);
         }
 
-        // Can we just reapply the RemoteViews in place?  If when didn't change, the order
-        // didn't change.
+        // Can we just reapply the RemoteViews in place?
 
         // 1U is never null
         boolean contentsUnchanged = oldEntry.expanded != null
@@ -1477,15 +1504,9 @@
                     addNotification(notification, ranking);  //this will pop the headsup
                 } else {
                     if (DEBUG) Log.d(TAG, "rebuilding update in place for key: " + key);
-                    removeNotificationViews(key, ranking);
-                    addNotificationViews(notification, ranking);
-                    final NotificationData.Entry newEntry = mNotificationData.findByKey(key);
-                    final boolean userChangedExpansion = oldEntry.row.hasUserChangedExpansion();
-                    if (userChangedExpansion) {
-                        boolean userExpanded = oldEntry.row.isUserExpanded();
-                        newEntry.row.setUserExpanded(userExpanded);
-                        newEntry.row.notifyHeightChanged();
-                    }
+                    oldEntry.notification = notification;
+                    inflateViews(oldEntry, mStackScroller, wasHeadsUp);
+                    updateNotifications();
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index c6000af..280bade 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -58,6 +58,23 @@
         super(context, attrs);
     }
 
+    /**
+     * Resets this view so it can be re-used for an updated notification.
+     */
+    public void reset() {
+        mRowMinHeight = 0;
+        mRowMaxHeight = 0;
+        mExpandable = false;
+        mHasUserChangedExpansion = false;
+        mUserLocked = false;
+        mShowingPublic = false;
+        mIsSystemExpanded = false;
+        mExpansionDisabled = false;
+        mPublicLayout.reset();
+        mPrivateLayout.reset();
+        mMaxExpandHeight = 0;
+    }
+
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
@@ -110,6 +127,7 @@
      * @param userExpanded whether the user wants this notification to be expanded
      */
     public void setUserExpanded(boolean userExpanded) {
+        if (userExpanded && !mExpandable) return;
         mHasUserChangedExpansion = true;
         mUserExpanded = userExpanded;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index f919501..f3aba0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -51,13 +51,12 @@
 
     private boolean mContractedVisible = true;
 
-    private Paint mFadePaint = new Paint();
+    private final Paint mFadePaint = new Paint();
 
     public NotificationContentView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
-        mActualHeight = mSmallHeight;
         mFadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
+        reset();
     }
 
     @Override
@@ -66,6 +65,15 @@
         updateClipping();
     }
 
+    public void reset() {
+        removeAllViews();
+        mContractedChild = null;
+        mExpandedChild = null;
+        mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
+        mActualHeight = mSmallHeight;
+        mContractedVisible = true;
+    }
+
     public void setContractedChild(View child) {
         if (mContractedChild != null) {
             removeView(mContractedChild);