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