Ensured that the sender of the first message is hidden

If the sendername of the first message matches the overall sender,
it's name will be hidden, otherwise it will be shown.

Bug: 150905003
Test: visually, use Notify, observe sender hidden when first
Change-Id: Iea67954b3a54ead641813bae6349cfd7af14320f
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 128f544..7267718 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -271,34 +271,35 @@
         updateHistoricMessageVisibility();
         updateTitleAndNamesDisplay();
 
-        updateConversationIconAndHeaderText();
+        updateConversationLayout();
 
     }
 
-    private void updateConversationIconAndHeaderText() {
+    /**
+     * Update the layout according to the data provided (i.e mIsOneToOne, expanded etc);
+     */
+    private void updateConversationLayout() {
         // TODO: resolve this from shortcuts
         // Set avatar and name
+        CharSequence personOnTop = null;
         if (mIsOneToOne) {
             // Let's resolve the icon / text from the last sender
             mConversationIcon.setVisibility(VISIBLE);
             mHeaderText.setVisibility(VISIBLE);
-            boolean found = false;
+            CharSequence userKey = getKey(mUser);
             for (int i = mGroups.size() - 1; i >= 0; i--) {
                 MessagingGroup messagingGroup = mGroups.get(i);
                 Person messageSender = messagingGroup.getSender();
-                if (!mUser.equals(messageSender)) {
+                if ((messageSender != null && !TextUtils.equals(userKey, getKey(messageSender)))
+                        || i == 0) {
                     // Make sure the header is actually visible
                     // TODO: figure out what to do if there's a converationtitle + a Sender
                     mHeaderText.setText(messagingGroup.getSenderName());
                     mConversationIcon.setImageIcon(messagingGroup.getAvatarIcon());
-                    found = true;
+                    personOnTop = messagingGroup.getSenderName();
                     break;
                 }
             }
-            if (!found) {
-                mHeaderText.setText(mUser.getName());
-                mConversationIcon.setImageIcon(mUser.getIcon());
-            }
         } else {
             mHeaderText.setVisibility(GONE);
             if (mIsCollapsed) {
@@ -312,7 +313,22 @@
                 mConversationIcon.setVisibility(GONE);
             }
         }
-        // update the icon position and sizing
+        // Update if the groups can hide the sender if they are first (applies to 1:1 conversations)
+        // This needs to happen after all of the above o update all of the groups
+        for (int i = mGroups.size() - 1; i >= 0; i--) {
+            MessagingGroup messagingGroup = mGroups.get(i);
+            CharSequence messageSender = messagingGroup.getSenderName();
+            boolean canHide = mIsOneToOne
+                    && TextUtils.equals(personOnTop, messageSender);
+            messagingGroup.setCanHideSenderIfFirst(canHide);
+        }
+        updateIconPositionAndSize();
+    }
+
+    /**
+     * update the icon position and sizing
+     */
+    private void updateIconPositionAndSize() {
         int gravity;
         int marginStart;
         int marginTop;
@@ -329,7 +345,7 @@
             marginTop = mExpandedGroupTopMargin;
             iconSize = mIconSizeCentered;
         }
-        FrameLayout.LayoutParams layoutParams =
+        LayoutParams layoutParams =
                 (LayoutParams) mConversationIconBadge.getLayoutParams();
         layoutParams.gravity = gravity;
         layoutParams.topMargin = marginTop;
@@ -572,8 +588,7 @@
             }
             boolean isNewGroup = currentGroup == null;
             Person sender = message.getMessage().getSenderPerson();
-            CharSequence key = sender == null ? null
-                    : sender.getKey() == null ? sender.getName() : sender.getKey();
+            CharSequence key = getKey(sender);
             isNewGroup |= !TextUtils.equals(key, currentSenderKey);
             if (isNewGroup) {
                 currentGroup = new ArrayList<>();
@@ -588,6 +603,10 @@
         }
     }
 
+    private CharSequence getKey(Person person) {
+        return person == null ? null : person.getKey() == null ? person.getName() : person.getKey();
+    }
+
     /**
      * Creates new messages, reusing existing ones if they are available.
      *
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
index 3238131..530cbb9 100644
--- a/core/java/com/android/internal/widget/MessagingGroup.java
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -83,6 +83,8 @@
     private LinearLayout mContentContainer;
     private int mRequestedMaxDisplayedLines = Integer.MAX_VALUE;
     private int mSenderTextPaddingSingleLine;
+    private boolean mIsFirstGroupInLayout = true;
+    private boolean mCanHideSenderIfFirst;
 
     public MessagingGroup(@NonNull Context context) {
         super(context);
@@ -161,7 +163,7 @@
         if (!mNeedsGeneratedAvatar) {
             setAvatar(sender.getIcon());
         }
-        mSenderView.setVisibility(TextUtils.isEmpty(nameOverride) ? GONE : VISIBLE);
+        updateSenderVisibility();
     }
 
     /**
@@ -255,6 +257,9 @@
         mSenderName = null;
         mAddedMessages.clear();
         mFirstLayout = true;
+        setCanHideSenderIfFirst(false);
+        setIsFirstInLayout(true);
+
         setMaxDisplayedLines(Integer.MAX_VALUE);
         setSingleLine(false);
         setShowingAvatar(true);
@@ -360,6 +365,35 @@
         return mIsHidingAnimated;
     }
 
+    @Override
+    public void setIsFirstInLayout(boolean first) {
+        if (first != mIsFirstGroupInLayout) {
+            mIsFirstGroupInLayout = first;
+            updateSenderVisibility();
+        }
+    }
+
+    /**
+     * @param canHide true if the sender can be hidden if it is first
+     */
+    public void setCanHideSenderIfFirst(boolean canHide) {
+        if (mCanHideSenderIfFirst != canHide) {
+            mCanHideSenderIfFirst = canHide;
+            updateSenderVisibility();
+        }
+    }
+
+    private void updateSenderVisibility() {
+        boolean hidden = (mIsFirstGroupInLayout || mSingleLine) && mCanHideSenderIfFirst
+                || TextUtils.isEmpty(mSenderName);
+        mSenderView.setVisibility(hidden ? GONE : VISIBLE);
+    }
+
+    @Override
+    public boolean hasDifferentHeightWhenFirst() {
+        return mCanHideSenderIfFirst && !mSingleLine && !TextUtils.isEmpty(mSenderName);
+    }
+
     private void setIsHidingAnimated(boolean isHiding) {
         ViewParent parent = getParent();
         mIsHidingAnimated = isHiding;
@@ -599,6 +633,7 @@
             layoutParams.setMarginEnd(singleLine ? mSenderTextPaddingSingleLine : 0);
             updateMaxDisplayedLines();
             updateClipRect();
+            updateSenderVisibility();
         }
     }
 }
diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java
index 9e54d11..ac04862 100644
--- a/core/java/com/android/internal/widget/MessagingLinearLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java
@@ -84,6 +84,11 @@
             final View child = getChildAt(i);
             final LayoutParams lp = (LayoutParams) child.getLayoutParams();
             lp.hide = true;
+            if (child instanceof MessagingChild) {
+                MessagingChild messagingChild = (MessagingChild) child;
+                // Whenever we encounter the message first, it's always first in the layout
+                messagingChild.setIsFirstInLayout(true);
+            }
         }
 
         totalHeight = mPaddingTop + mPaddingBottom;
@@ -91,6 +96,11 @@
         int linesRemaining = mMaxDisplayedLines;
         // Starting from the bottom: we measure every view as if it were the only one. If it still
         // fits, we take it, otherwise we stop there.
+        MessagingChild previousChild = null;
+        View previousView = null;
+        int previousChildHeight = 0;
+        int previousTotalHeight = 0;
+        int previousLinesConsumed = 0;
         for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) {
             if (getChildAt(i).getVisibility() == GONE) {
                 continue;
@@ -99,7 +109,16 @@
             LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
             MessagingChild messagingChild = null;
             int spacing = mSpacing;
+            int previousChildIncrease = 0;
             if (child instanceof MessagingChild) {
+                // We need to remeasure the previous child again if it's not the first anymore
+                if (previousChild != null && previousChild.hasDifferentHeightWhenFirst()) {
+                    previousChild.setIsFirstInLayout(false);
+                    measureChildWithMargins(previousView, widthMeasureSpec, 0, heightMeasureSpec,
+                            previousTotalHeight - previousChildHeight);
+                    previousChildIncrease = previousView.getMeasuredHeight() - previousChildHeight;
+                    linesRemaining -= previousChild.getConsumedLines() - previousLinesConsumed;
+                }
                 messagingChild = (MessagingChild) child;
                 messagingChild.setMaxDisplayedLines(linesRemaining);
                 spacing += messagingChild.getExtraSpacing();
@@ -110,18 +129,26 @@
 
             final int childHeight = child.getMeasuredHeight();
             int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin +
-                    lp.bottomMargin + spacing);
+                    lp.bottomMargin + spacing + previousChildIncrease);
             int measureType = MessagingChild.MEASURED_NORMAL;
             if (messagingChild != null) {
                 measureType = messagingChild.getMeasuredType();
-                linesRemaining -= messagingChild.getConsumedLines();
             }
 
             // We never measure the first item as too small, we want to at least show something.
             boolean isTooSmall = measureType == MessagingChild.MEASURED_TOO_SMALL && !first;
             boolean isShortened = measureType == MessagingChild.MEASURED_SHORTENED
                     || measureType == MessagingChild.MEASURED_TOO_SMALL && first;
-            if (newHeight <= targetHeight && !isTooSmall) {
+            boolean showView = newHeight <= targetHeight && !isTooSmall;
+            if (showView) {
+                if (messagingChild != null) {
+                    previousLinesConsumed = messagingChild.getConsumedLines();
+                    linesRemaining -= previousLinesConsumed;
+                    previousChild = messagingChild;
+                    previousView = child;
+                    previousChildHeight = childHeight;
+                    previousTotalHeight = totalHeight;
+                }
                 totalHeight = newHeight;
                 measuredWidth = Math.max(measuredWidth,
                         child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin
@@ -131,6 +158,16 @@
                     break;
                 }
             } else {
+                // We now became too short, let's make sure to reset any previous views to be first
+                // and remeasure it.
+                if (previousChild != null && previousChild.hasDifferentHeightWhenFirst()) {
+                    previousChild.setIsFirstInLayout(true);
+                    // We need to remeasure the previous child again since it became first
+                    measureChildWithMargins(previousView, widthMeasureSpec, 0, heightMeasureSpec,
+                            previousTotalHeight - previousChildHeight);
+                    // The totalHeight is already correct here since we only set it during the
+                    // first pass
+                }
                 break;
             }
             first = false;
@@ -273,6 +310,20 @@
         void setMaxDisplayedLines(int lines);
         void hideAnimated();
         boolean isHidingAnimated();
+
+        /**
+         * Set that this view is first in layout. Relevant and only set if
+         * {@link #hasDifferentHeightWhenFirst()}.
+         * @param first is this first?
+         */
+        default void setIsFirstInLayout(boolean first) {}
+
+        /**
+         * @return if this layout has different height it is first in the layout
+         */
+        default boolean hasDifferentHeightWhenFirst() {
+            return false;
+        }
         default int getExtraSpacing() {
             return 0;
         }