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 final boolean isRemoteInputHistory = 496 message.getMessage() != null && message.getMessage().isRemoteInputHistory(); 497 message.setColor(isRemoteInputHistory ? color : mTextColor); 498 } 499 } 500 } 501 setMessages(List<MessagingMessage> group)502 public void setMessages(List<MessagingMessage> group) { 503 // Let's now make sure all children are added and in the correct order 504 int textMessageIndex = 0; 505 MessagingImageMessage isolatedMessage = null; 506 for (int messageIndex = 0; messageIndex < group.size(); messageIndex++) { 507 MessagingMessage message = group.get(messageIndex); 508 if (message.getGroup() != this) { 509 message.setMessagingGroup(this); 510 mAddedMessages.add(message); 511 } 512 boolean isImage = message instanceof MessagingImageMessage; 513 if (mImageDisplayLocation != IMAGE_DISPLAY_LOCATION_INLINE && isImage) { 514 isolatedMessage = (MessagingImageMessage) message; 515 } else { 516 if (removeFromParentIfDifferent(message, mMessageContainer)) { 517 ViewGroup.LayoutParams layoutParams = message.getView().getLayoutParams(); 518 if (layoutParams != null 519 && !(layoutParams instanceof MessagingLinearLayout.LayoutParams)) { 520 message.getView().setLayoutParams( 521 mMessageContainer.generateDefaultLayoutParams()); 522 } 523 mMessageContainer.addView(message.getView(), textMessageIndex); 524 } 525 if (isImage) { 526 ((MessagingImageMessage) message).setIsolated(false); 527 } 528 // Let's sort them properly 529 if (textMessageIndex != mMessageContainer.indexOfChild(message.getView())) { 530 mMessageContainer.removeView(message.getView()); 531 mMessageContainer.addView(message.getView(), textMessageIndex); 532 } 533 textMessageIndex++; 534 } 535 } 536 if (isolatedMessage != null) { 537 if (mImageDisplayLocation == IMAGE_DISPLAY_LOCATION_AT_END 538 && removeFromParentIfDifferent(isolatedMessage, mImageContainer)) { 539 mImageContainer.removeAllViews(); 540 mImageContainer.addView(isolatedMessage.getView()); 541 } else if (mImageDisplayLocation == IMAGE_DISPLAY_LOCATION_EXTERNAL) { 542 mImageContainer.removeAllViews(); 543 } 544 isolatedMessage.setIsolated(true); 545 } else if (mIsolatedMessage != null) { 546 mImageContainer.removeAllViews(); 547 } 548 mIsolatedMessage = isolatedMessage; 549 updateImageContainerVisibility(); 550 mMessages = group; 551 updateMessageColor(); 552 } 553 updateImageContainerVisibility()554 private void updateImageContainerVisibility() { 555 mImageContainer.setVisibility(mIsolatedMessage != null 556 && mImageDisplayLocation == IMAGE_DISPLAY_LOCATION_AT_END 557 ? View.VISIBLE : View.GONE); 558 } 559 560 /** 561 * Remove the message from the parent if the parent isn't the one provided 562 * @return whether the message was removed 563 */ removeFromParentIfDifferent(MessagingMessage message, ViewGroup newParent)564 private boolean removeFromParentIfDifferent(MessagingMessage message, ViewGroup newParent) { 565 ViewParent parent = message.getView().getParent(); 566 if (parent != newParent) { 567 if (parent instanceof ViewGroup) { 568 ((ViewGroup) parent).removeView(message.getView()); 569 } 570 return true; 571 } 572 return false; 573 } 574 575 @Override onLayout(boolean changed, int left, int top, int right, int bottom)576 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 577 super.onLayout(changed, left, top, right, bottom); 578 if (!mAddedMessages.isEmpty()) { 579 final boolean firstLayout = mFirstLayout; 580 getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 581 @Override 582 public boolean onPreDraw() { 583 for (MessagingMessage message : mAddedMessages) { 584 if (!message.getView().isShown()) { 585 continue; 586 } 587 MessagingPropertyAnimator.fadeIn(message.getView()); 588 if (!firstLayout) { 589 MessagingPropertyAnimator.startLocalTranslationFrom(message.getView(), 590 message.getView().getHeight(), 591 MessagingLayout.LINEAR_OUT_SLOW_IN); 592 } 593 } 594 mAddedMessages.clear(); 595 getViewTreeObserver().removeOnPreDrawListener(this); 596 return true; 597 } 598 }); 599 } 600 mFirstLayout = false; 601 updateClipRect(); 602 } 603 604 /** 605 * Calculates the group compatibility between this and another group. 606 * 607 * @param otherGroup the other group to compare it with 608 * 609 * @return 0 if the groups are totally incompatible or 1 + the number of matching messages if 610 * they match. 611 */ calculateGroupCompatibility(MessagingGroup otherGroup)612 public int calculateGroupCompatibility(MessagingGroup otherGroup) { 613 if (TextUtils.equals(getSenderName(),otherGroup.getSenderName())) { 614 int result = 1; 615 for (int i = 0; i < mMessages.size() && i < otherGroup.mMessages.size(); i++) { 616 MessagingMessage ownMessage = mMessages.get(mMessages.size() - 1 - i); 617 MessagingMessage otherMessage = otherGroup.mMessages.get( 618 otherGroup.mMessages.size() - 1 - i); 619 if (!ownMessage.sameAs(otherMessage)) { 620 return result; 621 } 622 result++; 623 } 624 return result; 625 } 626 return 0; 627 } 628 getSenderView()629 public TextView getSenderView() { 630 return mSenderView; 631 } 632 getAvatar()633 public View getAvatar() { 634 return mAvatarView; 635 } 636 getAvatarIcon()637 public Icon getAvatarIcon() { 638 return mAvatarIcon; 639 } 640 getMessageContainer()641 public MessagingLinearLayout getMessageContainer() { 642 return mMessageContainer; 643 } 644 getIsolatedMessage()645 public MessagingImageMessage getIsolatedMessage() { 646 return mIsolatedMessage; 647 } 648 needsGeneratedAvatar()649 public boolean needsGeneratedAvatar() { 650 return mNeedsGeneratedAvatar; 651 } 652 getSender()653 public Person getSender() { 654 return mSender; 655 } 656 setClippingDisabled(boolean disabled)657 public void setClippingDisabled(boolean disabled) { 658 mClippingDisabled = disabled; 659 } 660 setImageDisplayLocation(@mageDisplayLocation int displayLocation)661 public void setImageDisplayLocation(@ImageDisplayLocation int displayLocation) { 662 if (mImageDisplayLocation != displayLocation) { 663 mImageDisplayLocation = displayLocation; 664 updateImageContainerVisibility(); 665 } 666 } 667 getMessages()668 public List<MessagingMessage> getMessages() { 669 return mMessages; 670 } 671 672 /** 673 * Set this layout to be single line and therefore displaying both the sender and the text on 674 * the same line. 675 * 676 * @param singleLine should be layout be single line 677 */ setSingleLine(boolean singleLine)678 public void setSingleLine(boolean singleLine) { 679 if (singleLine != mSingleLine) { 680 mSingleLine = singleLine; 681 MarginLayoutParams p = (MarginLayoutParams) mMessageContainer.getLayoutParams(); 682 p.topMargin = singleLine ? 0 : mNotificationTextMarginTop; 683 mMessageContainer.setLayoutParams(p); 684 mContentContainer.setOrientation( 685 singleLine ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL); 686 MarginLayoutParams layoutParams = (MarginLayoutParams) mSenderView.getLayoutParams(); 687 layoutParams.setMarginEnd(singleLine ? mSenderTextPaddingSingleLine : 0); 688 mSenderView.setSingleLine(singleLine); 689 updateMaxDisplayedLines(); 690 updateClipRect(); 691 updateSenderVisibility(); 692 } 693 } 694 isSingleLine()695 public boolean isSingleLine() { 696 return mSingleLine; 697 } 698 699 /** 700 * Set this group to be displayed in a conversation and adjust the visual appearance 701 * 702 * @param isInConversation is this in a conversation 703 */ setIsInConversation(boolean isInConversation)704 public void setIsInConversation(boolean isInConversation) { 705 if (mIsInConversation != isInConversation) { 706 mIsInConversation = isInConversation; 707 MarginLayoutParams layoutParams = 708 (MarginLayoutParams) mMessagingIconContainer.getLayoutParams(); 709 layoutParams.width = mIsInConversation 710 ? mConversationContentStart 711 : mNonConversationContentStart; 712 mMessagingIconContainer.setLayoutParams(layoutParams); 713 int imagePaddingStart = isInConversation ? 0 : mNonConversationPaddingStart; 714 mMessagingIconContainer.setPaddingRelative(imagePaddingStart, 0, 0, 0); 715 716 ViewGroup.LayoutParams avatarLayoutParams = mAvatarView.getLayoutParams(); 717 int size = mIsInConversation ? mConversationAvatarSize : mNonConversationAvatarSize; 718 avatarLayoutParams.height = size; 719 avatarLayoutParams.width = size; 720 mAvatarView.setLayoutParams(avatarLayoutParams); 721 } 722 } 723 724 @IntDef(prefix = {"IMAGE_DISPLAY_LOCATION_"}, value = { 725 IMAGE_DISPLAY_LOCATION_INLINE, 726 IMAGE_DISPLAY_LOCATION_AT_END, 727 IMAGE_DISPLAY_LOCATION_EXTERNAL 728 }) 729 @Retention(RetentionPolicy.SOURCE) 730 private @interface ImageDisplayLocation { 731 } 732 } 733