• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package com.android.internal.widget;
18 
19 import android.annotation.AttrRes;
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.StyleRes;
24 import android.app.Person;
25 import android.content.Context;
26 import android.content.res.ColorStateList;
27 import android.content.res.Resources;
28 import android.graphics.Color;
29 import android.graphics.Point;
30 import android.graphics.Rect;
31 import android.graphics.drawable.Icon;
32 import android.text.TextUtils;
33 import android.util.AttributeSet;
34 import android.util.DisplayMetrics;
35 import android.util.TypedValue;
36 import android.view.LayoutInflater;
37 import android.view.View;
38 import android.view.ViewGroup;
39 import android.view.ViewParent;
40 import android.view.ViewTreeObserver;
41 import android.widget.ImageView;
42 import android.widget.LinearLayout;
43 import android.widget.ProgressBar;
44 import android.widget.RemoteViews;
45 import android.widget.TextView;
46 
47 import com.android.internal.R;
48 
49 import java.lang.annotation.Retention;
50 import java.lang.annotation.RetentionPolicy;
51 import java.util.ArrayList;
52 import java.util.List;
53 
54 /**
55  * A message of a {@link MessagingLayout}.
56  */
57 @RemoteViews.RemoteView
58 public class MessagingGroup extends LinearLayout implements MessagingLinearLayout.MessagingChild {
59     private static final MessagingPool<MessagingGroup> sInstancePool =
60             new MessagingPool<>(10);
61 
62     /**
63      * Images are displayed inline.
64      */
65     public static final int IMAGE_DISPLAY_LOCATION_INLINE = 0;
66 
67     /**
68      * Images are displayed at the end of the group.
69      */
70     public static final int IMAGE_DISPLAY_LOCATION_AT_END = 1;
71 
72     /**
73      *     Images are displayed externally.
74      */
75     public static final int IMAGE_DISPLAY_LOCATION_EXTERNAL = 2;
76 
77 
78     private MessagingLinearLayout mMessageContainer;
79     ImageFloatingTextView mSenderView;
80     private ImageView mAvatarView;
81     private View mAvatarContainer;
82     private String mAvatarSymbol = "";
83     private int mLayoutColor;
84     private CharSequence mAvatarName = "";
85     private Icon mAvatarIcon;
86     private int mTextColor;
87     private int mSendingTextColor;
88     private List<MessagingMessage> mMessages;
89     private ArrayList<MessagingMessage> mAddedMessages = new ArrayList<>();
90     private boolean mFirstLayout;
91     private boolean mIsHidingAnimated;
92     private boolean mNeedsGeneratedAvatar;
93     private Person mSender;
94     private @ImageDisplayLocation int mImageDisplayLocation;
95     private ViewGroup mImageContainer;
96     private MessagingImageMessage mIsolatedMessage;
97     private boolean mClippingDisabled;
98     private Point mDisplaySize = new Point();
99     private ProgressBar mSendingSpinner;
100     private View mSendingSpinnerContainer;
101     private boolean mShowingAvatar = true;
102     private CharSequence mSenderName;
103     private boolean mSingleLine = false;
104     private LinearLayout mContentContainer;
105     private int mRequestedMaxDisplayedLines = Integer.MAX_VALUE;
106     private int mSenderTextPaddingSingleLine;
107     private boolean mIsFirstGroupInLayout = true;
108     private boolean mCanHideSenderIfFirst;
109     private boolean mIsInConversation = true;
110     private ViewGroup mMessagingIconContainer;
111     private int mConversationContentStart;
112     private int mNonConversationContentStart;
113     private int mNonConversationPaddingStart;
114     private int mConversationAvatarSize;
115     private int mNonConversationAvatarSize;
116     private int mNotificationTextMarginTop;
117 
MessagingGroup(@onNull Context context)118     public MessagingGroup(@NonNull Context context) {
119         super(context);
120     }
121 
MessagingGroup(@onNull Context context, @Nullable AttributeSet attrs)122     public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs) {
123         super(context, attrs);
124     }
125 
MessagingGroup(@onNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr)126     public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs,
127             @AttrRes int defStyleAttr) {
128         super(context, attrs, defStyleAttr);
129     }
130 
MessagingGroup(@onNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes)131     public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs,
132             @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
133         super(context, attrs, defStyleAttr, defStyleRes);
134     }
135 
136     @Override
onFinishInflate()137     protected void onFinishInflate() {
138         super.onFinishInflate();
139         mMessageContainer = findViewById(R.id.group_message_container);
140         mSenderView = findViewById(R.id.message_name);
141         mAvatarView = findViewById(R.id.message_icon);
142         mImageContainer = findViewById(R.id.messaging_group_icon_container);
143         mSendingSpinner = findViewById(R.id.messaging_group_sending_progress);
144         mMessagingIconContainer = findViewById(R.id.message_icon_container);
145         mContentContainer = findViewById(R.id.messaging_group_content_container);
146         mSendingSpinnerContainer = findViewById(R.id.messaging_group_sending_progress_container);
147         Resources res = getResources();
148         DisplayMetrics displayMetrics = res.getDisplayMetrics();
149         mDisplaySize.x = displayMetrics.widthPixels;
150         mDisplaySize.y = displayMetrics.heightPixels;
151         mSenderTextPaddingSingleLine = res.getDimensionPixelSize(
152                 R.dimen.messaging_group_singleline_sender_padding_end);
153         mConversationContentStart = res.getDimensionPixelSize(R.dimen.conversation_content_start);
154         mNonConversationContentStart = res.getDimensionPixelSize(
155                 R.dimen.notification_content_margin_start);
156         mNonConversationPaddingStart = res.getDimensionPixelSize(
157                 R.dimen.messaging_layout_icon_padding_start);
158         mConversationAvatarSize = res.getDimensionPixelSize(R.dimen.messaging_avatar_size);
159         mNonConversationAvatarSize = res.getDimensionPixelSize(
160                 R.dimen.notification_icon_circle_size);
161         mNotificationTextMarginTop = res.getDimensionPixelSize(
162                 R.dimen.notification_text_margin_top);
163     }
164 
updateClipRect()165     public void updateClipRect() {
166         // We want to clip to the senderName if it's available, otherwise our images will come
167         // from a weird position
168         Rect clipRect;
169         if (mSenderView.getVisibility() != View.GONE && !mClippingDisabled) {
170             int top;
171             if (mSingleLine) {
172                 top = 0;
173             } else {
174                 top = getDistanceFromParent(mSenderView, mContentContainer)
175                         - getDistanceFromParent(mMessageContainer, mContentContainer)
176                         + mSenderView.getHeight();
177             }
178             int size = Math.max(mDisplaySize.x, mDisplaySize.y);
179             clipRect = new Rect(-size, top, size, size);
180         } else {
181             clipRect = null;
182         }
183         mMessageContainer.setClipBounds(clipRect);
184     }
185 
getDistanceFromParent(View searchedView, ViewGroup parent)186     private int getDistanceFromParent(View searchedView, ViewGroup parent) {
187         int position = 0;
188         View view = searchedView;
189         while(view != parent) {
190             position += view.getTop() + view.getTranslationY();
191             view = (View) view.getParent();
192         }
193         return position;
194     }
195 
setSender(Person sender, CharSequence nameOverride)196     public void setSender(Person sender, CharSequence nameOverride) {
197         mSender = sender;
198         if (nameOverride == null) {
199             nameOverride = sender.getName();
200         }
201         mSenderName = nameOverride;
202         if (mSingleLine && !TextUtils.isEmpty(nameOverride)) {
203             nameOverride = mContext.getResources().getString(
204                     R.string.conversation_single_line_name_display, nameOverride);
205         }
206         mSenderView.setText(nameOverride);
207         mNeedsGeneratedAvatar = sender.getIcon() == null;
208         if (!mNeedsGeneratedAvatar) {
209             setAvatar(sender.getIcon());
210         }
211         updateSenderVisibility();
212     }
213 
214     /**
215      * Should the avatar be shown for this view.
216      *
217      * @param showingAvatar should it be shown
218      */
setShowingAvatar(boolean showingAvatar)219     public void setShowingAvatar(boolean showingAvatar) {
220         mAvatarView.setVisibility(showingAvatar ? VISIBLE : GONE);
221         mShowingAvatar = showingAvatar;
222     }
223 
setSending(boolean sending)224     public void setSending(boolean sending) {
225         int visibility = sending ? VISIBLE : GONE;
226         if (mSendingSpinnerContainer.getVisibility() != visibility) {
227             mSendingSpinnerContainer.setVisibility(visibility);
228             updateMessageColor();
229         }
230     }
231 
calculateSendingTextColor()232     private int calculateSendingTextColor() {
233         TypedValue alphaValue = new TypedValue();
234         mContext.getResources().getValue(
235                 R.dimen.notification_secondary_text_disabled_alpha, alphaValue, true);
236         float alpha = alphaValue.getFloat();
237         return Color.valueOf(
238                 Color.red(mTextColor),
239                 Color.green(mTextColor),
240                 Color.blue(mTextColor),
241                 alpha).toArgb();
242     }
243 
setAvatar(Icon icon)244     public void setAvatar(Icon icon) {
245         mAvatarIcon = icon;
246         if (mShowingAvatar || icon == null) {
247             mAvatarView.setImageIcon(icon);
248         }
249         mAvatarSymbol = "";
250         mAvatarName = "";
251     }
252 
createGroup(MessagingLinearLayout layout)253     static MessagingGroup createGroup(MessagingLinearLayout layout) {;
254         MessagingGroup createdGroup = sInstancePool.acquire();
255         if (createdGroup == null) {
256             createdGroup = (MessagingGroup) LayoutInflater.from(layout.getContext()).inflate(
257                     R.layout.notification_template_messaging_group, layout,
258                     false);
259             createdGroup.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
260         }
261         layout.addView(createdGroup);
262         return createdGroup;
263     }
264 
removeMessage(MessagingMessage messagingMessage, ArrayList<MessagingLinearLayout.MessagingChild> toRecycle)265     public void removeMessage(MessagingMessage messagingMessage,
266             ArrayList<MessagingLinearLayout.MessagingChild> toRecycle) {
267         View view = messagingMessage.getView();
268         boolean wasShown = view.isShown();
269         ViewGroup messageParent = (ViewGroup) view.getParent();
270         if (messageParent == null) {
271             return;
272         }
273         messageParent.removeView(view);
274         if (wasShown && !MessagingLinearLayout.isGone(view)) {
275             messageParent.addTransientView(view, 0);
276             performRemoveAnimation(view, () -> {
277                 messageParent.removeTransientView(view);
278                 messagingMessage.recycle();
279             });
280         } else {
281             toRecycle.add(messagingMessage);
282         }
283     }
284 
recycle()285     public void recycle() {
286         if (mIsolatedMessage != null) {
287             mImageContainer.removeView(mIsolatedMessage);
288         }
289         for (int i = 0; i < mMessages.size(); i++) {
290             MessagingMessage message = mMessages.get(i);
291             mMessageContainer.removeView(message.getView());
292             message.recycle();
293         }
294         setAvatar(null);
295         mAvatarView.setAlpha(1.0f);
296         mAvatarView.setTranslationY(0.0f);
297         mSenderView.setAlpha(1.0f);
298         mSenderView.setTranslationY(0.0f);
299         setAlpha(1.0f);
300         mIsolatedMessage = null;
301         mMessages = null;
302         mSenderName = null;
303         mAddedMessages.clear();
304         mFirstLayout = true;
305         setCanHideSenderIfFirst(false);
306         setIsFirstInLayout(true);
307 
308         setMaxDisplayedLines(Integer.MAX_VALUE);
309         setSingleLine(false);
310         setShowingAvatar(true);
311         MessagingPropertyAnimator.recycle(this);
312         sInstancePool.release(MessagingGroup.this);
313     }
314 
removeGroupAnimated(Runnable endAction)315     public void removeGroupAnimated(Runnable endAction) {
316         performRemoveAnimation(this, () -> {
317             setAlpha(1.0f);
318             MessagingPropertyAnimator.setToLaidOutPosition(this);
319             if (endAction != null) {
320                 endAction.run();
321             }
322         });
323     }
324 
performRemoveAnimation(View message, Runnable endAction)325     public void performRemoveAnimation(View message, Runnable endAction) {
326         performRemoveAnimation(message, -message.getHeight(), endAction);
327     }
328 
performRemoveAnimation(View view, int disappearTranslation, Runnable endAction)329     private void performRemoveAnimation(View view, int disappearTranslation, Runnable endAction) {
330         MessagingPropertyAnimator.startLocalTranslationTo(view, disappearTranslation,
331                 MessagingLayout.FAST_OUT_LINEAR_IN);
332         MessagingPropertyAnimator.fadeOut(view, endAction);
333     }
334 
getSenderName()335     public CharSequence getSenderName() {
336         return mSenderName;
337     }
338 
dropCache()339     public static void dropCache() {
340         sInstancePool.clear();
341     }
342 
343     @Override
getMeasuredType()344     public int getMeasuredType() {
345         if (mIsolatedMessage != null) {
346             // We only want to show one group if we have an inline image, so let's return shortened
347             // to avoid displaying the other ones.
348             return MEASURED_SHORTENED;
349         }
350         boolean hasNormal = false;
351         for (int i = mMessageContainer.getChildCount() - 1; i >= 0; i--) {
352             View child = mMessageContainer.getChildAt(i);
353             if (child.getVisibility() == GONE) {
354                 continue;
355             }
356             if (child instanceof MessagingLinearLayout.MessagingChild) {
357                 int type = ((MessagingLinearLayout.MessagingChild) child).getMeasuredType();
358                 boolean tooSmall = type == MEASURED_TOO_SMALL;
359                 final MessagingLinearLayout.LayoutParams lp =
360                         (MessagingLinearLayout.LayoutParams) child.getLayoutParams();
361                 tooSmall |= lp.hide;
362                 if (tooSmall) {
363                     if (hasNormal) {
364                         return MEASURED_SHORTENED;
365                     } else {
366                         return MEASURED_TOO_SMALL;
367                     }
368                 } else if (type == MEASURED_SHORTENED) {
369                     return MEASURED_SHORTENED;
370                 } else {
371                     hasNormal = true;
372                 }
373             }
374         }
375         return MEASURED_NORMAL;
376     }
377 
378     @Override
getConsumedLines()379     public int getConsumedLines() {
380         int result = 0;
381         for (int i = 0; i < mMessageContainer.getChildCount(); i++) {
382             View child = mMessageContainer.getChildAt(i);
383             if (child instanceof MessagingLinearLayout.MessagingChild) {
384                 result += ((MessagingLinearLayout.MessagingChild) child).getConsumedLines();
385             }
386         }
387         result = mIsolatedMessage != null ? Math.max(result, 1) : result;
388         // A group is usually taking up quite some space with the padding and the name, let's add 1
389         return result + 1;
390     }
391 
392     @Override
setMaxDisplayedLines(int lines)393     public void setMaxDisplayedLines(int lines) {
394         mRequestedMaxDisplayedLines = lines;
395         updateMaxDisplayedLines();
396     }
397 
updateMaxDisplayedLines()398     private void updateMaxDisplayedLines() {
399         mMessageContainer.setMaxDisplayedLines(mSingleLine ? 1 : mRequestedMaxDisplayedLines);
400     }
401 
402     @Override
hideAnimated()403     public void hideAnimated() {
404         setIsHidingAnimated(true);
405         removeGroupAnimated(() -> setIsHidingAnimated(false));
406     }
407 
408     @Override
isHidingAnimated()409     public boolean isHidingAnimated() {
410         return mIsHidingAnimated;
411     }
412 
413     @Override
setIsFirstInLayout(boolean first)414     public void setIsFirstInLayout(boolean first) {
415         if (first != mIsFirstGroupInLayout) {
416             mIsFirstGroupInLayout = first;
417             updateSenderVisibility();
418         }
419     }
420 
421     /**
422      * @param canHide true if the sender can be hidden if it is first
423      */
setCanHideSenderIfFirst(boolean canHide)424     public void setCanHideSenderIfFirst(boolean canHide) {
425         if (mCanHideSenderIfFirst != canHide) {
426             mCanHideSenderIfFirst = canHide;
427             updateSenderVisibility();
428         }
429     }
430 
updateSenderVisibility()431     private void updateSenderVisibility() {
432         boolean hidden = (mIsFirstGroupInLayout || mSingleLine) && mCanHideSenderIfFirst
433                 || TextUtils.isEmpty(mSenderName);
434         mSenderView.setVisibility(hidden ? GONE : VISIBLE);
435     }
436 
437     @Override
hasDifferentHeightWhenFirst()438     public boolean hasDifferentHeightWhenFirst() {
439         return mCanHideSenderIfFirst && !mSingleLine && !TextUtils.isEmpty(mSenderName);
440     }
441 
setIsHidingAnimated(boolean isHiding)442     private void setIsHidingAnimated(boolean isHiding) {
443         ViewParent parent = getParent();
444         mIsHidingAnimated = isHiding;
445         invalidate();
446         if (parent instanceof ViewGroup) {
447             ((ViewGroup) parent).invalidate();
448         }
449     }
450 
451     @Override
hasOverlappingRendering()452     public boolean hasOverlappingRendering() {
453         return false;
454     }
455 
getAvatarSymbolIfMatching(CharSequence avatarName, String avatarSymbol, int layoutColor)456     public Icon getAvatarSymbolIfMatching(CharSequence avatarName, String avatarSymbol,
457             int layoutColor) {
458         if (mAvatarName.equals(avatarName) && mAvatarSymbol.equals(avatarSymbol)
459                 && layoutColor == mLayoutColor) {
460             return mAvatarIcon;
461         }
462         return null;
463     }
464 
setCreatedAvatar(Icon cachedIcon, CharSequence avatarName, String avatarSymbol, int layoutColor)465     public void setCreatedAvatar(Icon cachedIcon, CharSequence avatarName, String avatarSymbol,
466             int layoutColor) {
467         if (!mAvatarName.equals(avatarName) || !mAvatarSymbol.equals(avatarSymbol)
468                 || layoutColor != mLayoutColor) {
469             setAvatar(cachedIcon);
470             mAvatarSymbol = avatarSymbol;
471             setLayoutColor(layoutColor);
472             mAvatarName = avatarName;
473         }
474     }
475 
setTextColors(int senderTextColor, int messageTextColor)476     public void setTextColors(int senderTextColor, int messageTextColor) {
477         mTextColor = messageTextColor;
478         mSendingTextColor = calculateSendingTextColor();
479         updateMessageColor();
480         mSenderView.setTextColor(senderTextColor);
481     }
482 
setLayoutColor(int layoutColor)483     public void setLayoutColor(int layoutColor) {
484         if (layoutColor != mLayoutColor){
485             mLayoutColor = layoutColor;
486             mSendingSpinner.setIndeterminateTintList(ColorStateList.valueOf(mLayoutColor));
487         }
488     }
489 
updateMessageColor()490     private void updateMessageColor() {
491         if (mMessages != null) {
492             int color = mSendingSpinnerContainer.getVisibility() == View.VISIBLE
493                     ? mSendingTextColor : mTextColor;
494             for (MessagingMessage message : mMessages) {
495                 message.setColor(message.getMessage().isRemoteInputHistory() ? color : mTextColor);
496             }
497         }
498     }
499 
setMessages(List<MessagingMessage> group)500     public void setMessages(List<MessagingMessage> group) {
501         // Let's now make sure all children are added and in the correct order
502         int textMessageIndex = 0;
503         MessagingImageMessage isolatedMessage = null;
504         for (int messageIndex = 0; messageIndex < group.size(); messageIndex++) {
505             MessagingMessage message = group.get(messageIndex);
506             if (message.getGroup() != this) {
507                 message.setMessagingGroup(this);
508                 mAddedMessages.add(message);
509             }
510             boolean isImage = message instanceof MessagingImageMessage;
511             if (mImageDisplayLocation != IMAGE_DISPLAY_LOCATION_INLINE && isImage) {
512                 isolatedMessage = (MessagingImageMessage) message;
513             } else {
514                 if (removeFromParentIfDifferent(message, mMessageContainer)) {
515                     ViewGroup.LayoutParams layoutParams = message.getView().getLayoutParams();
516                     if (layoutParams != null
517                             && !(layoutParams instanceof MessagingLinearLayout.LayoutParams)) {
518                         message.getView().setLayoutParams(
519                                 mMessageContainer.generateDefaultLayoutParams());
520                     }
521                     mMessageContainer.addView(message.getView(), textMessageIndex);
522                 }
523                 if (isImage) {
524                     ((MessagingImageMessage) message).setIsolated(false);
525                 }
526                 // Let's sort them properly
527                 if (textMessageIndex != mMessageContainer.indexOfChild(message.getView())) {
528                     mMessageContainer.removeView(message.getView());
529                     mMessageContainer.addView(message.getView(), textMessageIndex);
530                 }
531                 textMessageIndex++;
532             }
533         }
534         if (isolatedMessage != null) {
535             if (mImageDisplayLocation == IMAGE_DISPLAY_LOCATION_AT_END
536                     && removeFromParentIfDifferent(isolatedMessage, mImageContainer)) {
537                 mImageContainer.removeAllViews();
538                 mImageContainer.addView(isolatedMessage.getView());
539             } else if (mImageDisplayLocation == IMAGE_DISPLAY_LOCATION_EXTERNAL) {
540                 mImageContainer.removeAllViews();
541             }
542             isolatedMessage.setIsolated(true);
543         } else if (mIsolatedMessage != null) {
544             mImageContainer.removeAllViews();
545         }
546         mIsolatedMessage = isolatedMessage;
547         updateImageContainerVisibility();
548         mMessages = group;
549         updateMessageColor();
550     }
551 
updateImageContainerVisibility()552     private void updateImageContainerVisibility() {
553         mImageContainer.setVisibility(mIsolatedMessage != null
554                 && mImageDisplayLocation == IMAGE_DISPLAY_LOCATION_AT_END
555                 ? View.VISIBLE : View.GONE);
556     }
557 
558     /**
559      * Remove the message from the parent if the parent isn't the one provided
560      * @return whether the message was removed
561      */
removeFromParentIfDifferent(MessagingMessage message, ViewGroup newParent)562     private boolean removeFromParentIfDifferent(MessagingMessage message, ViewGroup newParent) {
563         ViewParent parent = message.getView().getParent();
564         if (parent != newParent) {
565             if (parent instanceof ViewGroup) {
566                 ((ViewGroup) parent).removeView(message.getView());
567             }
568             return true;
569         }
570         return false;
571     }
572 
573     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)574     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
575         super.onLayout(changed, left, top, right, bottom);
576         if (!mAddedMessages.isEmpty()) {
577             final boolean firstLayout = mFirstLayout;
578             getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
579                 @Override
580                 public boolean onPreDraw() {
581                     for (MessagingMessage message : mAddedMessages) {
582                         if (!message.getView().isShown()) {
583                             continue;
584                         }
585                         MessagingPropertyAnimator.fadeIn(message.getView());
586                         if (!firstLayout) {
587                             MessagingPropertyAnimator.startLocalTranslationFrom(message.getView(),
588                                     message.getView().getHeight(),
589                                     MessagingLayout.LINEAR_OUT_SLOW_IN);
590                         }
591                     }
592                     mAddedMessages.clear();
593                     getViewTreeObserver().removeOnPreDrawListener(this);
594                     return true;
595                 }
596             });
597         }
598         mFirstLayout = false;
599         updateClipRect();
600     }
601 
602     /**
603      * Calculates the group compatibility between this and another group.
604      *
605      * @param otherGroup the other group to compare it with
606      *
607      * @return 0 if the groups are totally incompatible or 1 + the number of matching messages if
608      *         they match.
609      */
calculateGroupCompatibility(MessagingGroup otherGroup)610     public int calculateGroupCompatibility(MessagingGroup otherGroup) {
611         if (TextUtils.equals(getSenderName(),otherGroup.getSenderName())) {
612             int result = 1;
613             for (int i = 0; i < mMessages.size() && i < otherGroup.mMessages.size(); i++) {
614                 MessagingMessage ownMessage = mMessages.get(mMessages.size() - 1 - i);
615                 MessagingMessage otherMessage = otherGroup.mMessages.get(
616                         otherGroup.mMessages.size() - 1 - i);
617                 if (!ownMessage.sameAs(otherMessage)) {
618                     return result;
619                 }
620                 result++;
621             }
622             return result;
623         }
624         return 0;
625     }
626 
getSenderView()627     public TextView getSenderView() {
628         return mSenderView;
629     }
630 
getAvatar()631     public View getAvatar() {
632         return mAvatarView;
633     }
634 
getAvatarIcon()635     public Icon getAvatarIcon() {
636         return mAvatarIcon;
637     }
638 
getMessageContainer()639     public MessagingLinearLayout getMessageContainer() {
640         return mMessageContainer;
641     }
642 
getIsolatedMessage()643     public MessagingImageMessage getIsolatedMessage() {
644         return mIsolatedMessage;
645     }
646 
needsGeneratedAvatar()647     public boolean needsGeneratedAvatar() {
648         return mNeedsGeneratedAvatar;
649     }
650 
getSender()651     public Person getSender() {
652         return mSender;
653     }
654 
setClippingDisabled(boolean disabled)655     public void setClippingDisabled(boolean disabled) {
656         mClippingDisabled = disabled;
657     }
658 
setImageDisplayLocation(@mageDisplayLocation int displayLocation)659     public void setImageDisplayLocation(@ImageDisplayLocation int displayLocation) {
660         if (mImageDisplayLocation != displayLocation) {
661             mImageDisplayLocation = displayLocation;
662             updateImageContainerVisibility();
663         }
664     }
665 
getMessages()666     public List<MessagingMessage> getMessages() {
667         return mMessages;
668     }
669 
670     /**
671      * Set this layout to be single line and therefore displaying both the sender and the text on
672      * the same line.
673      *
674      * @param singleLine should be layout be single line
675      */
setSingleLine(boolean singleLine)676     public void setSingleLine(boolean singleLine) {
677         if (singleLine != mSingleLine) {
678             mSingleLine = singleLine;
679             MarginLayoutParams p = (MarginLayoutParams) mMessageContainer.getLayoutParams();
680             p.topMargin = singleLine ? 0 : mNotificationTextMarginTop;
681             mMessageContainer.setLayoutParams(p);
682             mContentContainer.setOrientation(
683                     singleLine ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
684             MarginLayoutParams layoutParams = (MarginLayoutParams) mSenderView.getLayoutParams();
685             layoutParams.setMarginEnd(singleLine ? mSenderTextPaddingSingleLine : 0);
686             mSenderView.setSingleLine(singleLine);
687             updateMaxDisplayedLines();
688             updateClipRect();
689             updateSenderVisibility();
690         }
691     }
692 
isSingleLine()693     public boolean isSingleLine() {
694         return mSingleLine;
695     }
696 
697     /**
698      * Set this group to be displayed in a conversation and adjust the visual appearance
699      *
700      * @param isInConversation is this in a conversation
701      */
setIsInConversation(boolean isInConversation)702     public void setIsInConversation(boolean isInConversation) {
703         if (mIsInConversation != isInConversation) {
704             mIsInConversation = isInConversation;
705             MarginLayoutParams layoutParams =
706                     (MarginLayoutParams) mMessagingIconContainer.getLayoutParams();
707             layoutParams.width = mIsInConversation
708                     ? mConversationContentStart
709                     : mNonConversationContentStart;
710             mMessagingIconContainer.setLayoutParams(layoutParams);
711             int imagePaddingStart = isInConversation ? 0 : mNonConversationPaddingStart;
712             mMessagingIconContainer.setPaddingRelative(imagePaddingStart, 0, 0, 0);
713 
714             ViewGroup.LayoutParams avatarLayoutParams = mAvatarView.getLayoutParams();
715             int size = mIsInConversation ? mConversationAvatarSize : mNonConversationAvatarSize;
716             avatarLayoutParams.height = size;
717             avatarLayoutParams.width = size;
718             mAvatarView.setLayoutParams(avatarLayoutParams);
719         }
720     }
721 
722     @IntDef(prefix = {"IMAGE_DISPLAY_LOCATION_"}, value = {
723             IMAGE_DISPLAY_LOCATION_INLINE,
724             IMAGE_DISPLAY_LOCATION_AT_END,
725             IMAGE_DISPLAY_LOCATION_EXTERNAL
726     })
727     @Retention(RetentionPolicy.SOURCE)
728     private @interface ImageDisplayLocation {
729     }
730 }
731