Merge "Clean up MessagingLayout notifications" into sc-dev

This commit is contained in:
Jeff DeCew
2021-04-14 12:47:24 +00:00
committed by Android (Google) Code Review
14 changed files with 511 additions and 236 deletions

View File

@@ -5116,6 +5116,7 @@ public class Notification implements Parcelable
TemplateBindResult result) {
p.headerless(resId == getBaseLayoutResource()
|| resId == getHeadsUpBaseLayoutResource()
|| resId == getMessagingLayoutResource()
|| resId == R.layout.notification_template_material_media);
RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
@@ -6641,6 +6642,10 @@ public class Notification implements Parcelable
return R.layout.notification_template_material_messaging;
}
private int getBigMessagingLayoutResource() {
return R.layout.notification_template_material_big_messaging;
}
private int getConversationLayoutResource() {
return R.layout.notification_template_material_conversation;
}
@@ -8151,12 +8156,14 @@ public class Notification implements Parcelable
*/
@Override
public RemoteViews makeContentView(boolean increasedHeight) {
// All messaging templates contain the actions
ArrayList<Action> originalActions = mBuilder.mActions;
mBuilder.mActions = new ArrayList<>();
RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */,
false /* hideLargeIcon */);
mBuilder.mActions = originalActions;
return remoteViews;
try {
mBuilder.mActions = new ArrayList<>();
return makeMessagingView(StandardTemplateParams.VIEW_TYPE_NORMAL);
} finally {
mBuilder.mActions = originalActions;
}
}
/**
@@ -8242,18 +8249,24 @@ public class Notification implements Parcelable
*/
@Override
public RemoteViews makeBigContentView() {
return makeMessagingView(false /* isCollapsed */, true /* hideLargeIcon */);
return makeMessagingView(StandardTemplateParams.VIEW_TYPE_BIG);
}
/**
* Create a messaging layout.
*
* @param isCollapsed Should this use the collapsed layout
* @param hideRightIcons Should the reply affordance be shown at the end of the notification
* @param viewType one of StandardTemplateParams.VIEW_TYPE_NORMAL, VIEW_TYPE_BIG,
* VIEW_TYPE_HEADS_UP
* @return the created remoteView.
*/
@NonNull
private RemoteViews makeMessagingView(boolean isCollapsed, boolean hideRightIcons) {
private RemoteViews makeMessagingView(int viewType) {
boolean isCollapsed = viewType != StandardTemplateParams.VIEW_TYPE_BIG;
boolean hideRightIcons = viewType != StandardTemplateParams.VIEW_TYPE_NORMAL;
boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY;
boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT;
boolean isHeaderless = !isConversationLayout && isCollapsed;
CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
? super.mBigContentTitle
: mConversationTitle;
@@ -8271,23 +8284,26 @@ public class Notification implements Parcelable
} else {
isOneToOne = !isGroupConversation();
}
boolean isConversationLayout = mConversationType != CONVERSATION_TYPE_LEGACY;
boolean isImportantConversation = mConversationType == CONVERSATION_TYPE_IMPORTANT;
if (isHeaderless && isOneToOne && TextUtils.isEmpty(conversationTitle)) {
conversationTitle = getOtherPersonName();
}
Icon largeIcon = mBuilder.mN.mLargeIcon;
TemplateBindResult bindResult = new TemplateBindResult();
StandardTemplateParams p = mBuilder.mParams.reset()
.viewType(isCollapsed ? StandardTemplateParams.VIEW_TYPE_NORMAL
: StandardTemplateParams.VIEW_TYPE_BIG)
.viewType(viewType)
.highlightExpander(isConversationLayout)
.hideProgress(true)
.title(conversationTitle)
.title(isHeaderless ? conversationTitle : null)
.text(null)
.hideLargeIcon(hideRightIcons || isOneToOne)
.headerTextSecondary(conversationTitle);
.headerTextSecondary(isHeaderless ? null : conversationTitle);
RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
isConversationLayout
? mBuilder.getConversationLayoutResource()
: mBuilder.getMessagingLayoutResource(),
: isCollapsed
? mBuilder.getMessagingLayoutResource()
: mBuilder.getBigMessagingLayoutResource(),
p,
bindResult);
if (isConversationLayout) {
@@ -8296,14 +8312,6 @@ public class Notification implements Parcelable
}
addExtras(mBuilder.mN.extras);
if (!isConversationLayout) {
// also update the end margin if there is an image
// NOTE: This template doesn't support moving this icon to the left, so we don't
// need to fully apply the MarginSet
contentView.setViewLayoutMargin(R.id.notification_messaging, RemoteViews.MARGIN_END,
bindResult.mHeadingExtraMarginSet.getDpValue(),
TypedValue.COMPLEX_UNIT_DIP);
}
contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
mBuilder.getSmallIconColor(p));
contentView.setInt(R.id.status_bar_latest_event_content, "setSenderTextColor",
@@ -8329,6 +8337,10 @@ public class Notification implements Parcelable
contentView.setBoolean(R.id.status_bar_latest_event_content,
"setIsImportantConversation", isImportantConversation);
}
if (isHeaderless) {
// Collapsed legacy messaging style has a 1-line limit.
contentView.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
}
contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
largeIcon);
contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
@@ -8336,6 +8348,22 @@ public class Notification implements Parcelable
return contentView;
}
private CharSequence getKey(Person person) {
return person == null ? null
: person.getKey() == null ? person.getName() : person.getKey();
}
private CharSequence getOtherPersonName() {
CharSequence userKey = getKey(mUser);
for (int i = mMessages.size() - 1; i >= 0; i--) {
Person sender = mMessages.get(i).getSenderPerson();
if (sender != null && !TextUtils.equals(userKey, getKey(sender))) {
return sender.getName();
}
}
return null;
}
private boolean hasOnlyWhiteSpaceSenders() {
for (int i = 0; i < mMessages.size(); i++) {
Message m = mMessages.get(i);
@@ -8370,12 +8398,7 @@ public class Notification implements Parcelable
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */,
true /* hideLargeIcon */);
if (mConversationType == CONVERSATION_TYPE_LEGACY) {
remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
}
return remoteViews;
return makeMessagingView(StandardTemplateParams.VIEW_TYPE_HEADS_UP);
}
public static final class Message {

View File

@@ -64,6 +64,7 @@ import com.android.internal.R;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
@@ -530,13 +531,7 @@ public class ConversationLayout extends FrameLayout
mConversationText.setText(conversationText);
// Update if the groups can hide the sender if they are first (applies to 1:1 conversations)
// This needs to happen after all of the above o update all of the groups
for (int i = mGroups.size() - 1; i >= 0; i--) {
MessagingGroup messagingGroup = mGroups.get(i);
CharSequence messageSender = messagingGroup.getSenderName();
boolean canHide = mIsOneToOne
&& TextUtils.equals(conversationText, messageSender);
messagingGroup.setCanHideSenderIfFirst(canHide);
}
mPeopleHelper.maybeHideFirstSenderName(mGroups, mIsOneToOne, conversationText);
updateAppName();
updateIconPositionAndSize();
updateImageMessages();
@@ -779,35 +774,7 @@ public class ConversationLayout extends FrameLayout
private void updateTitleAndNamesDisplay() {
// Map of unique names to their prefix
ArrayMap<CharSequence, String> uniqueNames = new ArrayMap<>();
// Map of single-character string prefix to the only name which uses it, or null if multiple
ArrayMap<String, CharSequence> uniqueCharacters = new ArrayMap<>();
for (int i = 0; i < mGroups.size(); i++) {
MessagingGroup group = mGroups.get(i);
CharSequence senderName = group.getSenderName();
if (!group.needsGeneratedAvatar() || TextUtils.isEmpty(senderName)) {
continue;
}
if (!uniqueNames.containsKey(senderName)) {
String charPrefix = mPeopleHelper.findNamePrefix(senderName, null);
if (charPrefix == null) {
continue;
}
if (uniqueCharacters.containsKey(charPrefix)) {
// this character was already used, lets make it more unique. We first need to
// resolve the existing character if it exists
CharSequence existingName = uniqueCharacters.get(charPrefix);
if (existingName != null) {
uniqueNames.put(existingName, mPeopleHelper.findNameSplit(existingName));
uniqueCharacters.put(charPrefix, null);
}
uniqueNames.put(senderName, mPeopleHelper.findNameSplit(senderName));
} else {
uniqueNames.put(senderName, charPrefix);
uniqueCharacters.put(charPrefix, senderName);
}
}
}
Map<CharSequence, String> uniqueNames = mPeopleHelper.mapUniqueNamesToPrefix(mGroups);
// Now that we have the correct symbols, let's look what we have cached
ArrayMap<CharSequence, Icon> cachedAvatars = new ArrayMap<>();

View File

@@ -24,6 +24,7 @@ import android.annotation.StyleRes;
import android.app.Person;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
@@ -109,7 +110,10 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
private boolean mIsInConversation = true;
private ViewGroup mMessagingIconContainer;
private int mConversationContentStart;
private int mNonConversationMarginEnd;
private int mNonConversationContentStart;
private int mNonConversationPaddingStart;
private int mConversationAvatarSize;
private int mNonConversationAvatarSize;
private int mNotificationTextMarginTop;
public MessagingGroup(@NonNull Context context) {
@@ -141,16 +145,21 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
mMessagingIconContainer = findViewById(R.id.message_icon_container);
mContentContainer = findViewById(R.id.messaging_group_content_container);
mSendingSpinnerContainer = findViewById(R.id.messaging_group_sending_progress_container);
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
Resources res = getResources();
DisplayMetrics displayMetrics = res.getDisplayMetrics();
mDisplaySize.x = displayMetrics.widthPixels;
mDisplaySize.y = displayMetrics.heightPixels;
mSenderTextPaddingSingleLine = getResources().getDimensionPixelSize(
mSenderTextPaddingSingleLine = res.getDimensionPixelSize(
R.dimen.messaging_group_singleline_sender_padding_end);
mConversationContentStart = getResources().getDimensionPixelSize(
R.dimen.conversation_content_start);
mNonConversationMarginEnd = getResources().getDimensionPixelSize(
R.dimen.messaging_layout_margin_end);
mNotificationTextMarginTop = getResources().getDimensionPixelSize(
mConversationContentStart = res.getDimensionPixelSize(R.dimen.conversation_content_start);
mNonConversationContentStart = res.getDimensionPixelSize(
R.dimen.notification_content_margin_start);
mNonConversationPaddingStart = res.getDimensionPixelSize(
R.dimen.messaging_layout_icon_padding_start);
mConversationAvatarSize = res.getDimensionPixelSize(R.dimen.messaging_avatar_size);
mNonConversationAvatarSize = res.getDimensionPixelSize(
R.dimen.notification_icon_circle_size);
mNotificationTextMarginTop = res.getDimensionPixelSize(
R.dimen.notification_text_margin_top);
}
@@ -696,10 +705,18 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
mIsInConversation = isInConversation;
MarginLayoutParams layoutParams =
(MarginLayoutParams) mMessagingIconContainer.getLayoutParams();
layoutParams.width = mIsInConversation ? mConversationContentStart
: ViewPager.LayoutParams.WRAP_CONTENT;
layoutParams.setMarginEnd(mIsInConversation ? 0 : mNonConversationMarginEnd);
layoutParams.width = mIsInConversation
? mConversationContentStart
: mNonConversationContentStart;
mMessagingIconContainer.setLayoutParams(layoutParams);
int imagePaddingStart = isInConversation ? 0 : mNonConversationPaddingStart;
mMessagingIconContainer.setPaddingRelative(imagePaddingStart, 0, 0, 0);
ViewGroup.LayoutParams avatarLayoutParams = mAvatarView.getLayoutParams();
int size = mIsInConversation ? mConversationAvatarSize : mNonConversationAvatarSize;
avatarLayoutParams.height = size;
avatarLayoutParams.width = size;
mAvatarView.setLayoutParams(avatarLayoutParams);
}
}

View File

@@ -16,7 +16,7 @@
package com.android.internal.widget;
import static com.android.internal.widget.MessagingGroup.IMAGE_DISPLAY_LOCATION_AT_END;
import static com.android.internal.widget.MessagingGroup.IMAGE_DISPLAY_LOCATION_EXTERNAL;
import static com.android.internal.widget.MessagingGroup.IMAGE_DISPLAY_LOCATION_INLINE;
import android.annotation.AttrRes;
@@ -27,10 +27,6 @@ import android.app.Notification;
import android.app.Person;
import android.app.RemoteInputHistoryItem;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Icon;
import android.os.Bundle;
@@ -40,21 +36,22 @@ import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.RemotableViewMethod;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.RemoteViews;
import android.widget.TextView;
import com.android.internal.R;
import com.android.internal.graphics.ColorUtils;
import com.android.internal.util.ContrastColorUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.regex.Pattern;
/**
* A custom-built layout for the Notification.MessagingStyle allows dynamic addition and removal
@@ -65,15 +62,6 @@ public class MessagingLayout extends FrameLayout
implements ImageMessageConsumer, IMessagingLayout {
private static final float COLOR_SHIFT_AMOUNT = 60;
/**
* Pattren for filter some ingonable characters.
* p{Z} for any kind of whitespace or invisible separator.
* p{C} for any kind of punctuation character.
*/
private static final Pattern IGNORABLE_CHAR_PATTERN
= Pattern.compile("[\\p{C}\\p{Z}]");
private static final Pattern SPECIAL_CHAR_PATTERN
= Pattern.compile ("[!@#$%&*()_+=|<>?{}\\[\\]~-]");
private static final Consumer<MessagingMessage> REMOVE_MESSAGE
= MessagingMessage::removeMessage;
public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
@@ -81,26 +69,26 @@ public class MessagingLayout extends FrameLayout
public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
public static final OnLayoutChangeListener MESSAGING_PROPERTY_ANIMATOR
= new MessagingPropertyAnimator();
private final PeopleHelper mPeopleHelper = new PeopleHelper();
private List<MessagingMessage> mMessages = new ArrayList<>();
private List<MessagingMessage> mHistoricMessages = new ArrayList<>();
private MessagingLinearLayout mMessagingLinearLayout;
private boolean mShowHistoricMessages;
private ArrayList<MessagingGroup> mGroups = new ArrayList<>();
private TextView mTitleView;
private MessagingLinearLayout mImageMessageContainer;
private ImageView mRightIconView;
private Rect mMessagingClipRect;
private int mLayoutColor;
private int mSenderTextColor;
private int mMessageTextColor;
private int mAvatarSize;
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint mTextPaint = new Paint();
private CharSequence mConversationTitle;
private Icon mAvatarReplacement;
private boolean mIsOneToOne;
private ArrayList<MessagingGroup> mAddedGroups = new ArrayList<>();
private Person mUser;
private CharSequence mNameReplacement;
private boolean mDisplayImagesAtEnd;
private boolean mIsCollapsed;
private ImageResolver mImageResolver;
private CharSequence mConversationTitle;
public MessagingLayout(@NonNull Context context) {
super(context);
@@ -123,17 +111,16 @@ public class MessagingLayout extends FrameLayout
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mPeopleHelper.init(getContext());
mMessagingLinearLayout = findViewById(R.id.notification_messaging);
mImageMessageContainer = findViewById(R.id.conversation_image_message_container);
mRightIconView = findViewById(R.id.right_icon);
// We still want to clip, but only on the top, since views can temporarily out of bounds
// during transitions.
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
int size = Math.max(displayMetrics.widthPixels, displayMetrics.heightPixels);
Rect rect = new Rect(0, 0, size, size);
mMessagingLinearLayout.setClipBounds(rect);
mTitleView = findViewById(R.id.title);
mAvatarSize = getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size);
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setAntiAlias(true);
mMessagingClipRect = new Rect(0, 0, size, size);
setMessagingClippingDisabled(false);
}
@RemotableViewMethod
@@ -153,7 +140,7 @@ public class MessagingLayout extends FrameLayout
*/
@RemotableViewMethod
public void setIsCollapsed(boolean isCollapsed) {
mDisplayImagesAtEnd = isCollapsed;
mIsCollapsed = isCollapsed;
}
@RemotableViewMethod
@@ -168,7 +155,7 @@ public class MessagingLayout extends FrameLayout
*/
@RemotableViewMethod
public void setConversationTitle(CharSequence conversationTitle) {
// Unused
mConversationTitle = conversationTitle;
}
@RemotableViewMethod
@@ -180,11 +167,6 @@ public class MessagingLayout extends FrameLayout
List<Notification.MessagingStyle.Message> newHistoricMessages
= Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages);
setUser(extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON));
mConversationTitle = null;
TextView headerText = findViewById(R.id.header_text);
if (headerText != null) {
mConversationTitle = headerText.getText();
}
RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[])
extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
addRemoteInputHistoryToMessages(newMessages, history);
@@ -238,6 +220,41 @@ public class MessagingLayout extends FrameLayout
updateHistoricMessageVisibility();
updateTitleAndNamesDisplay();
// after groups are finalized, hide the first sender name if it's showing as the title
mPeopleHelper.maybeHideFirstSenderName(mGroups, mIsOneToOne, mConversationTitle);
updateImageMessages();
}
private void updateImageMessages() {
View newMessage = null;
if (mImageMessageContainer == null) {
return;
}
if (mIsCollapsed && !mGroups.isEmpty()) {
// When collapsed, we're displaying the image message in a dedicated container
// on the right of the layout instead of inline. Let's add the isolated image there
MessagingGroup messagingGroup = mGroups.get(mGroups.size() - 1);
MessagingImageMessage isolatedMessage = messagingGroup.getIsolatedMessage();
if (isolatedMessage != null) {
newMessage = isolatedMessage.getView();
}
}
// Remove all messages that don't belong into the image layout
View previousMessage = mImageMessageContainer.getChildAt(0);
if (previousMessage != newMessage) {
mImageMessageContainer.removeView(previousMessage);
if (newMessage != null) {
mImageMessageContainer.addView(newMessage);
}
}
mImageMessageContainer.setVisibility(newMessage != null ? VISIBLE : GONE);
// When showing an image message, do not show the large icon. Removing the drawable
// prevents it from being shown in the left_icon view (by the grouping util).
if (newMessage != null && mRightIconView != null && mRightIconView.getDrawable() != null) {
mRightIconView.setImageDrawable(null);
mRightIconView.setVisibility(GONE);
}
}
private void removeGroups(ArrayList<MessagingGroup> oldGroups) {
@@ -266,34 +283,7 @@ public class MessagingLayout extends FrameLayout
}
private void updateTitleAndNamesDisplay() {
ArrayMap<CharSequence, String> uniqueNames = new ArrayMap<>();
ArrayMap<Character, CharSequence> uniqueCharacters = new ArrayMap<>();
for (int i = 0; i < mGroups.size(); i++) {
MessagingGroup group = mGroups.get(i);
CharSequence senderName = group.getSenderName();
if (!group.needsGeneratedAvatar() || TextUtils.isEmpty(senderName)) {
continue;
}
if (!uniqueNames.containsKey(senderName)) {
// Only use visible characters to get uniqueNames
String pureSenderName = IGNORABLE_CHAR_PATTERN
.matcher(senderName).replaceAll("" /* replacement */);
char c = pureSenderName.charAt(0);
if (uniqueCharacters.containsKey(c)) {
// this character was already used, lets make it more unique. We first need to
// resolve the existing character if it exists
CharSequence existingName = uniqueCharacters.get(c);
if (existingName != null) {
uniqueNames.put(existingName, findNameSplit((String) existingName));
uniqueCharacters.put(c, null);
}
uniqueNames.put(senderName, findNameSplit((String) senderName));
} else {
uniqueNames.put(senderName, Character.toString(c));
uniqueCharacters.put(c, pureSenderName);
}
}
}
Map<CharSequence, String> uniqueNames = mPeopleHelper.mapUniqueNamesToPrefix(mGroups);
// Now that we have the correct symbols, let's look what we have cached
ArrayMap<CharSequence, Icon> cachedAvatars = new ArrayMap<>();
@@ -337,26 +327,7 @@ public class MessagingLayout extends FrameLayout
}
public Icon createAvatarSymbol(CharSequence senderName, String symbol, int layoutColor) {
if (symbol.isEmpty() || TextUtils.isDigitsOnly(symbol) ||
SPECIAL_CHAR_PATTERN.matcher(symbol).find()) {
Icon avatarIcon = Icon.createWithResource(getContext(),
com.android.internal.R.drawable.messaging_user);
avatarIcon.setTint(findColor(senderName, layoutColor));
return avatarIcon;
} else {
Bitmap bitmap = Bitmap.createBitmap(mAvatarSize, mAvatarSize, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
float radius = mAvatarSize / 2.0f;
int color = findColor(senderName, layoutColor);
mPaint.setColor(color);
canvas.drawCircle(radius, radius, radius, mPaint);
boolean needDarkText = ColorUtils.calculateLuminance(color) > 0.5f;
mTextPaint.setColor(needDarkText ? Color.BLACK : Color.WHITE);
mTextPaint.setTextSize(symbol.length() == 1 ? mAvatarSize * 0.5f : mAvatarSize * 0.3f);
int yPos = (int) (radius - ((mTextPaint.descent() + mTextPaint.ascent()) / 2));
canvas.drawText(symbol, radius, yPos, mTextPaint);
return Icon.createWithBitmap(bitmap);
}
return mPeopleHelper.createAvatarSymbol(senderName, symbol, layoutColor);
}
private int findColor(CharSequence senderName, int layoutColor) {
@@ -449,8 +420,8 @@ public class MessagingLayout extends FrameLayout
newGroup = MessagingGroup.createGroup(mMessagingLinearLayout);
mAddedGroups.add(newGroup);
}
newGroup.setImageDisplayLocation(mDisplayImagesAtEnd
? IMAGE_DISPLAY_LOCATION_AT_END
newGroup.setImageDisplayLocation(mIsCollapsed
? IMAGE_DISPLAY_LOCATION_EXTERNAL
: IMAGE_DISPLAY_LOCATION_INLINE);
newGroup.setIsInConversation(false);
newGroup.setLayoutColor(mLayoutColor);
@@ -460,6 +431,8 @@ public class MessagingLayout extends FrameLayout
if (sender != mUser && mNameReplacement != null) {
nameOverride = mNameReplacement;
}
newGroup.setSingleLine(mIsCollapsed);
newGroup.setShowingAvatar(!mIsCollapsed);
newGroup.setSender(sender, nameOverride);
newGroup.setSending(groupIndex == (groups.size() - 1) && showSpinner);
mGroups.add(newGroup);
@@ -600,12 +573,17 @@ public class MessagingLayout extends FrameLayout
return mMessagingLinearLayout;
}
@Nullable
public ViewGroup getImageMessageContainer() {
return mImageMessageContainer;
}
public ArrayList<MessagingGroup> getMessagingGroups() {
return mGroups;
}
@Override
public void setMessagingClippingDisabled(boolean clippingDisabled) {
// Don't do anything, this is only used for the ConversationLayout
mMessagingLinearLayout.setClipBounds(clippingDisabled ? null : mMessagingClipRect);
}
}

View File

@@ -21,6 +21,7 @@ import static com.android.internal.widget.MessagingPropertyAnimator.ALPHA_OUT;
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -28,12 +29,15 @@ import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.drawable.Icon;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.view.View;
import com.android.internal.R;
import com.android.internal.graphics.ColorUtils;
import com.android.internal.util.ContrastColorUtil;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
/**
@@ -176,4 +180,58 @@ public class PeopleHelper {
}
return findNamePrefix(name, "");
}
/**
* Creates a mapping of the unique sender names in the groups to the string 1- or 2-character
* prefix strings for the names, which are extracted as the initials, and should be used for
* generating the avatar. Senders not requiring a generated avatar, or with an empty name are
* omitted.
*/
public Map<CharSequence, String> mapUniqueNamesToPrefix(List<MessagingGroup> groups) {
// Map of unique names to their prefix
ArrayMap<CharSequence, String> uniqueNames = new ArrayMap<>();
// Map of single-character string prefix to the only name which uses it, or null if multiple
ArrayMap<String, CharSequence> uniqueCharacters = new ArrayMap<>();
for (int i = 0; i < groups.size(); i++) {
MessagingGroup group = groups.get(i);
CharSequence senderName = group.getSenderName();
if (!group.needsGeneratedAvatar() || TextUtils.isEmpty(senderName)) {
continue;
}
if (!uniqueNames.containsKey(senderName)) {
String charPrefix = findNamePrefix(senderName, null);
if (charPrefix == null) {
continue;
}
if (uniqueCharacters.containsKey(charPrefix)) {
// this character was already used, lets make it more unique. We first need to
// resolve the existing character if it exists
CharSequence existingName = uniqueCharacters.get(charPrefix);
if (existingName != null) {
uniqueNames.put(existingName, findNameSplit(existingName));
uniqueCharacters.put(charPrefix, null);
}
uniqueNames.put(senderName, findNameSplit(senderName));
} else {
uniqueNames.put(senderName, charPrefix);
uniqueCharacters.put(charPrefix, senderName);
}
}
}
return uniqueNames;
}
/**
* Update whether the groups can hide the sender if they are first
* (happens only for 1:1 conversations where the given title matches the sender's name)
*/
public void maybeHideFirstSenderName(@NonNull List<MessagingGroup> groups,
boolean isOneToOne, @Nullable CharSequence conversationTitle) {
for (int i = groups.size() - 1; i >= 0; i--) {
MessagingGroup messagingGroup = groups.get(i);
CharSequence messageSender = messagingGroup.getSenderName();
boolean canHide = isOneToOne && TextUtils.equals(conversationTitle, messageSender);
messagingGroup.setCanHideSenderIfFirst(canHide);
}
}
}

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2021 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
<com.android.internal.widget.MessagingLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:clipChildren="false"
android:tag="messaging"
>
<include layout="@layout/notification_template_header"/>
<com.android.internal.widget.RemeasuringLinearLayout
android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_marginTop="@dimen/notification_content_margin_top"
android:clipChildren="false"
android:orientation="vertical">
<com.android.internal.widget.RemeasuringLinearLayout
android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_weight="1"
android:layout_marginEnd="@dimen/notification_content_margin_end"
android:orientation="vertical"
android:clipChildren="false"
>
<com.android.internal.widget.MessagingLinearLayout
android:id="@+id/notification_messaging"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:spacing="@dimen/notification_messaging_spacing" />
</com.android.internal.widget.RemeasuringLinearLayout>
<include layout="@layout/notification_template_smart_reply_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/notification_content_margin"
android:layout_marginStart="@dimen/notification_content_margin_start"
android:layout_marginEnd="@dimen/notification_content_margin_end" />
<include layout="@layout/notification_material_action_list" />
</com.android.internal.widget.RemeasuringLinearLayout>
<include layout="@layout/notification_template_right_icon" />
</com.android.internal.widget.MessagingLayout>

View File

@@ -38,7 +38,7 @@
<com.android.internal.widget.RemeasuringLinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="-12dp"
android:layout_marginTop="-20dp"
android:clipChildren="false"
android:orientation="vertical"
>

View File

@@ -22,32 +22,186 @@
android:clipChildren="false"
android:tag="messaging"
>
<include layout="@layout/notification_template_header"/>
<com.android.internal.widget.RemeasuringLinearLayout
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:orientation="vertical"
>
<com.android.internal.widget.NotificationMaxHeightFrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="@dimen/notification_min_height"
android:clipChildren="false"
>
<ImageView
android:id="@+id/left_icon"
android:layout_width="@dimen/notification_left_icon_size"
android:layout_height="@dimen/notification_left_icon_size"
android:layout_gravity="center_vertical|start"
android:layout_marginStart="@dimen/notification_left_icon_start"
android:background="@drawable/notification_large_icon_outline"
android:clipToOutline="true"
android:importantForAccessibility="no"
android:scaleType="centerCrop"
android:visibility="gone"
/>
<com.android.internal.widget.CachingIconView
android:id="@+id/icon"
android:layout_width="@dimen/notification_icon_circle_size"
android:layout_height="@dimen/notification_icon_circle_size"
android:layout_gravity="center_vertical|start"
android:layout_marginStart="@dimen/notification_icon_circle_start"
android:background="@drawable/notification_icon_circle"
android:padding="@dimen/notification_icon_circle_padding"
/>
<FrameLayout
android:id="@+id/alternate_expand_target"
android:layout_width="@dimen/notification_content_margin_start"
android:layout_height="match_parent"
android:layout_gravity="start"
android:importantForAccessibility="no"
/>
<!--
NOTE: to make the expansion animation of id/notification_messaging happen vertically,
its X positioning must be the left edge of the notification, so instead of putting the
layout_marginStart on the id/notification_headerless_view_row, we put it on
id/notification_top_line, making the layout here just a bit different from the base.
-->
<LinearLayout
android:id="@+id/notification_headerless_view_row"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:clipChildren="false"
>
<!--
NOTE: because messaging will always have 2 lines, this LinearLayout should NOT
have the id/notification_headerless_view_column, as that is used for modifying
vertical margins to accommodate the single-line state that base supports
-->
<LinearLayout
android:layout_width="0px"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:layout_marginBottom="@dimen/notification_headerless_margin_twoline"
android:layout_marginTop="@dimen/notification_headerless_margin_twoline"
android:clipChildren="false"
android:orientation="vertical"
>
<NotificationTopLineView
android:id="@+id/notification_top_line"
android:layout_width="wrap_content"
android:layout_height="@dimen/notification_headerless_line_height"
android:layout_marginStart="@dimen/notification_content_margin_start"
android:clipChildren="false"
android:theme="@style/Theme.DeviceDefault.Notification"
>
<!--
NOTE: The notification_top_line_views layout contains the app_name_text.
In order to include the title view at the beginning, the Notification.Builder
has logic to hide that view whenever this title view is to be visible.
-->
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/notification_header_separating_margin"
android:ellipsize="marquee"
android:fadingEdge="horizontal"
android:singleLine="true"
android:textAlignment="viewStart"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
/>
<include layout="@layout/notification_top_line_views" />
</NotificationTopLineView>
<LinearLayout
android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:clipChildren="false"
>
<com.android.internal.widget.MessagingLinearLayout
android:id="@+id/notification_messaging"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:spacing="@dimen/notification_messaging_spacing" />
</LinearLayout>
</LinearLayout>
<!-- Images -->
<com.android.internal.widget.MessagingLinearLayout
android:id="@+id/conversation_image_message_container"
android:layout_width="@dimen/notification_right_icon_size"
android:layout_height="@dimen/notification_right_icon_size"
android:layout_gravity="center_vertical|end"
android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
android:layout_marginStart="@dimen/notification_right_icon_content_margin"
android:forceHasOverlappingRendering="false"
android:spacing="0dp"
android:clipChildren="false"
android:visibility="gone"
/>
<ImageView
android:id="@+id/right_icon"
android:layout_width="@dimen/notification_right_icon_size"
android:layout_height="@dimen/notification_right_icon_size"
android:layout_gravity="center_vertical|end"
android:layout_marginTop="@dimen/notification_right_icon_headerless_margin"
android:layout_marginBottom="@dimen/notification_right_icon_headerless_margin"
android:layout_marginStart="@dimen/notification_right_icon_content_margin"
android:background="@drawable/notification_large_icon_outline"
android:clipToOutline="true"
android:importantForAccessibility="no"
android:scaleType="centerCrop"
/>
<FrameLayout
android:id="@+id/expand_button_touch_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:minWidth="@dimen/notification_content_margin_end"
>
<include layout="@layout/notification_expand_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
/>
</FrameLayout>
</LinearLayout>
</com.android.internal.widget.NotificationMaxHeightFrameLayout>
<LinearLayout
android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_marginTop="@dimen/notification_content_margin_top"
android:clipToPadding="false"
android:layout_marginTop="-20dp"
android:clipChildren="false"
android:orientation="vertical">
<com.android.internal.widget.RemeasuringLinearLayout
android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_weight="1"
android:layout_marginStart="@dimen/notification_content_margin_start"
android:layout_marginEnd="@dimen/notification_content_margin_end"
android:orientation="vertical"
>
<com.android.internal.widget.MessagingLinearLayout
android:id="@+id/notification_messaging"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:spacing="@dimen/notification_messaging_spacing" />
</com.android.internal.widget.RemeasuringLinearLayout>
<include layout="@layout/notification_template_smart_reply_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -55,6 +209,6 @@
android:layout_marginStart="@dimen/notification_content_margin_start"
android:layout_marginEnd="@dimen/notification_content_margin_end" />
<include layout="@layout/notification_material_action_list" />
</com.android.internal.widget.RemeasuringLinearLayout>
<include layout="@layout/notification_template_right_icon" />
</LinearLayout>
</LinearLayout>
</com.android.internal.widget.MessagingLayout>

View File

@@ -803,8 +803,8 @@
<!-- The padding of the conversation header when expanded. This is calculated from the expand button size + notification_content_margin_end -->
<dimen name="conversation_header_expanded_padding_end">38dp</dimen>
<!-- margin at the end of messaging group icons when not conversations -->
<dimen name="messaging_layout_margin_end">12dp</dimen>
<!-- extra padding at the start of the icons when not conversations to keep them horizontally aligned with the notification icon -->
<dimen name="messaging_layout_icon_padding_start">4dp</dimen>
<!-- Padding between text and sender when singleline -->
<dimen name="messaging_group_singleline_sender_padding_end">4dp</dimen>

View File

@@ -3015,6 +3015,7 @@
<java-symbol type="layout" name="app_anr_dialog" />
<java-symbol type="layout" name="notification_template_material_messaging" />
<java-symbol type="layout" name="notification_template_material_big_messaging" />
<java-symbol type="id" name="aerr_wait" />
@@ -3647,6 +3648,7 @@
<java-symbol type="id" name="bubble_button" />
<java-symbol type="id" name="snooze_button" />
<java-symbol type="dimen" name="text_size_body_2_material" />
<java-symbol type="dimen" name="notification_icon_circle_size" />
<java-symbol type="dimen" name="messaging_avatar_size" />
<java-symbol type="dimen" name="messaging_group_sending_progress_size" />
<java-symbol type="dimen" name="messaging_image_rounding" />
@@ -4063,7 +4065,7 @@
<java-symbol type="dimen" name="conversation_badge_side_margin_group_expanded_face_pile" />
<java-symbol type="dimen" name="conversation_content_start" />
<java-symbol type="dimen" name="expanded_group_conversation_message_padding" />
<java-symbol type="dimen" name="messaging_layout_margin_end" />
<java-symbol type="dimen" name="messaging_layout_icon_padding_start" />
<java-symbol type="dimen" name="conversation_header_expanded_padding_end" />
<java-symbol type="dimen" name="conversation_icon_container_top_padding" />
<java-symbol type="dimen" name="conversation_icon_container_top_padding_small_avatar" />

View File

@@ -71,7 +71,6 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.ContrastColorUtil;
import com.android.internal.widget.CachingIconView;
import com.android.internal.widget.CallLayout;
import com.android.internal.widget.MessagingLayout;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
@@ -658,10 +657,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
boolean beforeS = mEntry.targetSdk < Build.VERSION_CODES.S;
int smallHeight;
View expandedView = layout.getExpandedChild();
boolean isMediaLayout = expandedView != null
&& expandedView.findViewById(com.android.internal.R.id.media_actions) != null;
boolean isMessagingLayout = contractedView instanceof MessagingLayout;
boolean isCallLayout = contractedView instanceof CallLayout;
if (customView && beforeS && !mIsSummaryWithChildren) {
@@ -672,12 +667,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
} else {
smallHeight = mMaxSmallHeightBeforeS;
}
} else if (isMessagingLayout) {
// TODO(b/173204301): MessagingStyle notifications currently look broken when we enforce
// the standard notification height, so we have to afford them more vertical space to
// make sure we don't crop them terribly. We actually need to revisit this and give
// them a headerless design, then remove this hack.
smallHeight = mMaxSmallHeightLarge;
} else if (isCallLayout) {
smallHeight = mMaxExpandedHeight;
} else if (mUseIncreasedCollapsedHeight && layout == mPrivateLayout) {

View File

@@ -23,12 +23,9 @@ import com.android.internal.widget.CachingIconView
import com.android.internal.widget.ConversationLayout
import com.android.internal.widget.MessagingLinearLayout
import com.android.systemui.R
import com.android.systemui.statusbar.TransformableView
import com.android.systemui.statusbar.ViewTransformationHelper
import com.android.systemui.statusbar.notification.NotificationUtils
import com.android.systemui.statusbar.notification.TransformState
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.HybridNotificationView
import com.android.systemui.statusbar.notification.row.wrapper.NotificationMessagingTemplateViewWrapper.setCustomImageMessageTransform
/**
* Wraps a notification containing a conversation template
@@ -93,33 +90,7 @@ class NotificationConversationTemplateViewWrapper constructor(
appName,
conversationTitleView)
// Let's ignore the image message container since that is transforming as part of the
// messages already
mTransformationHelper.setCustomTransformation(
object : ViewTransformationHelper.CustomTransformation() {
override fun transformTo(
ownState: TransformState,
otherView: TransformableView,
transformationAmount: Float
): Boolean {
if (otherView is HybridNotificationView) {
return false
}
// we're hidden by default by the transformState
ownState.ensureVisible()
// Let's do nothing otherwise, this is already handled by the messages
return true
}
override fun transformFrom(
ownState: TransformState,
otherView: TransformableView,
transformationAmount: Float
): Boolean =
transformTo(ownState, otherView, transformationAmount)
},
imageMessageContainer.id
)
setCustomImageMessageTransform(mTransformationHelper, imageMessageContainer)
addViewsTransformingToSimilar(
conversationIconView,

View File

@@ -18,12 +18,17 @@ package com.android.systemui.statusbar.notification.row.wrapper;
import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import com.android.internal.widget.MessagingLayout;
import com.android.internal.widget.MessagingLinearLayout;
import com.android.systemui.R;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.ViewTransformationHelper;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.TransformState;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.HybridNotificationView;
/**
* Wraps a notification containing a messaging template
@@ -31,12 +36,17 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
public class NotificationMessagingTemplateViewWrapper extends NotificationTemplateViewWrapper {
private final int mMinHeightWithActions;
private final View mTitle;
private final View mTitleInHeader;
private MessagingLayout mMessagingLayout;
private MessagingLinearLayout mMessagingLinearLayout;
private ViewGroup mImageMessageContainer;
protected NotificationMessagingTemplateViewWrapper(Context ctx, View view,
ExpandableNotificationRow row) {
super(ctx, view, row);
mTitle = mView.findViewById(com.android.internal.R.id.title);
mTitleInHeader = mView.findViewById(com.android.internal.R.id.header_text_secondary);
mMessagingLayout = (MessagingLayout) view;
mMinHeightWithActions = NotificationUtils.getFontScaledHeight(ctx,
R.dimen.notification_messaging_actions_min_height);
@@ -44,6 +54,7 @@ public class NotificationMessagingTemplateViewWrapper extends NotificationTempla
private void resolveViews() {
mMessagingLinearLayout = mMessagingLayout.getMessagingLinearLayout();
mImageMessageContainer = mMessagingLayout.getImageMessageContainer();
}
@Override
@@ -59,8 +70,48 @@ public class NotificationMessagingTemplateViewWrapper extends NotificationTempla
// This also clears the existing types
super.updateTransformedTypes();
if (mMessagingLinearLayout != null) {
mTransformationHelper.addTransformedView(mMessagingLinearLayout.getId(),
mMessagingLinearLayout);
mTransformationHelper.addTransformedView(mMessagingLinearLayout);
}
// The title is not as important for messaging, and stays in the header when expanded,
// but this ensures it animates cleanly between the two positions
if (mTitle == null && mTitleInHeader != null) {
mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE,
mTitleInHeader);
}
setCustomImageMessageTransform(mTransformationHelper, mImageMessageContainer);
}
static void setCustomImageMessageTransform(
ViewTransformationHelper transformationHelper, ViewGroup imageMessageContainer) {
if (imageMessageContainer != null) {
// Let's ignore the image message container since that is transforming as part of the
// messages already. This is also required to prevent a clipping artifact caused by the
// alpha layering triggering hardware rendering mode that in turn results in more
// aggressive clipping than we want.
transformationHelper.setCustomTransformation(
new ViewTransformationHelper.CustomTransformation() {
@Override
public boolean transformTo(
TransformState ownState,
TransformableView otherView,
float transformationAmount) {
if (otherView instanceof HybridNotificationView) {
return false;
}
// we're hidden by default by the transformState
ownState.ensureVisible();
// Let's do nothing otherwise, this is already handled by the messages
return true;
}
@Override
public boolean transformFrom(
TransformState ownState,
TransformableView otherView,
float transformationAmount) {
return transformTo(ownState, otherView, transformationAmount);
}
}, imageMessageContainer.getId());
}
}

View File

@@ -56,6 +56,7 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp
private ProgressBar mProgressBar;
private TextView mTitle;
private TextView mText;
protected View mSmartReplyContainer;
protected View mActionsContainer;
private int mContentHeight;
@@ -160,6 +161,7 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp
// It's still a viewstub
mProgressBar = null;
}
mSmartReplyContainer = mView.findViewById(com.android.internal.R.id.smart_reply_container);
mActionsContainer = mView.findViewById(com.android.internal.R.id.actions_container);
mActions = mView.findViewById(com.android.internal.R.id.actions);
mRemoteInputHistory = mView.findViewById(
@@ -275,6 +277,7 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp
mProgressBar);
}
addViewsTransformingToSimilar(mLeftIcon);
addTransformedViews(mSmartReplyContainer);
}
@Override