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