Merge changes I69b33b0c,If904c85e,I28444df4,I3bfa4580,I88d9667d, ... into rvc-dev am: 5f003f4e10

Change-Id: I161d20abc58cc948a1243aa5c528a9b5197e311b
This commit is contained in:
Automerger Merge Worker
2020-03-13 21:01:43 +00:00
35 changed files with 1757 additions and 155 deletions

View File

@@ -21,6 +21,7 @@ import static android.graphics.drawable.Icon.TYPE_URI;
import static android.graphics.drawable.Icon.TYPE_URI_ADAPTIVE_BITMAP;
import static com.android.internal.util.ContrastColorUtil.satisfiesTextContrast;
import static com.android.internal.widget.ConversationLayout.CONVERSATION_LAYOUT_ENABLED;
import android.annotation.ColorInt;
import android.annotation.DimenRes;
@@ -389,6 +390,7 @@ public class Notification implements Parcelable
STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_text);
STANDARD_LAYOUTS.add(R.layout.notification_template_material_inbox);
STANDARD_LAYOUTS.add(R.layout.notification_template_material_messaging);
STANDARD_LAYOUTS.add(R.layout.notification_template_material_conversation);
STANDARD_LAYOUTS.add(R.layout.notification_template_material_media);
STANDARD_LAYOUTS.add(R.layout.notification_template_material_big_media);
STANDARD_LAYOUTS.add(R.layout.notification_template_header);
@@ -5138,7 +5140,7 @@ public class Notification implements Parcelable
int color = isColorized(p) ? getPrimaryTextColor(p) : getSecondaryTextColor(p);
contentView.setDrawableTint(R.id.expand_button, false, color,
PorterDuff.Mode.SRC_ATOP);
contentView.setInt(R.id.notification_header, "setOriginalNotificationColor",
contentView.setInt(R.id.expand_button, "setOriginalNotificationColor",
color);
}
@@ -6116,7 +6118,9 @@ public class Notification implements Parcelable
}
private int getMessagingLayoutResource() {
return R.layout.notification_template_material_messaging;
return CONVERSATION_LAYOUT_ENABLED
? R.layout.notification_template_material_conversation
: R.layout.notification_template_material_messaging;
}
private int getActionLayoutResource() {
@@ -7390,7 +7394,7 @@ public class Notification implements Parcelable
public RemoteViews makeContentView(boolean increasedHeight) {
mBuilder.mOriginalActions = mBuilder.mActions;
mBuilder.mActions = new ArrayList<>();
RemoteViews remoteViews = makeMessagingView(true /* displayImagesAtEnd */,
RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */,
false /* hideLargeIcon */);
mBuilder.mActions = mBuilder.mOriginalActions;
mBuilder.mOriginalActions = null;
@@ -7480,19 +7484,18 @@ public class Notification implements Parcelable
*/
@Override
public RemoteViews makeBigContentView() {
return makeMessagingView(false /* displayImagesAtEnd */, true /* hideLargeIcon */);
return makeMessagingView(false /* isCollapsed */, true /* hideLargeIcon */);
}
/**
* Create a messaging layout.
*
* @param displayImagesAtEnd should images be displayed at the end of the content instead
* of inline.
* @param isCollapsed Should this use the collapsed layout
* @param hideRightIcons Should the reply affordance be shown at the end of the notification
* @return the created remoteView.
*/
@NonNull
private RemoteViews makeMessagingView(boolean displayImagesAtEnd, boolean hideRightIcons) {
private RemoteViews makeMessagingView(boolean isCollapsed, boolean hideRightIcons) {
CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
? super.mBigContentTitle
: mConversationTitle;
@@ -7533,14 +7536,21 @@ public class Notification implements Parcelable
mBuilder.getPrimaryTextColor(p));
contentView.setInt(R.id.status_bar_latest_event_content, "setMessageTextColor",
mBuilder.getSecondaryTextColor(p));
contentView.setBoolean(R.id.status_bar_latest_event_content, "setDisplayImagesAtEnd",
displayImagesAtEnd);
contentView.setInt(R.id.status_bar_latest_event_content,
"setNotificationBackgroundColor",
mBuilder.resolveBackgroundColor(p));
contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed",
isCollapsed);
contentView.setIcon(R.id.status_bar_latest_event_content, "setAvatarReplacement",
avatarReplacement);
contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement",
nameReplacement);
contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne",
isOneToOne);
contentView.setCharSequence(R.id.status_bar_latest_event_content,
"setConversationTitle", conversationTitle);
contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
mBuilder.mN.mLargeIcon);
contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
mBuilder.mN.extras);
return contentView;
@@ -7601,9 +7611,11 @@ public class Notification implements Parcelable
*/
@Override
public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
RemoteViews remoteViews = makeMessagingView(true /* displayImagesAtEnd */,
RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */,
true /* hideLargeIcon */);
remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
if (!CONVERSATION_LAYOUT_ENABLED) {
remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
}
return remoteViews;
}

View File

@@ -35,6 +35,7 @@ import android.widget.RemoteViews;
import com.android.internal.R;
import com.android.internal.widget.CachingIconView;
import com.android.internal.widget.NotificationExpandButton;
import java.util.ArrayList;
@@ -56,7 +57,7 @@ public class NotificationHeaderView extends ViewGroup {
private OnClickListener mAppOpsListener;
private HeaderTouchListener mTouchListener = new HeaderTouchListener();
private LinearLayout mTransferChip;
private ImageView mExpandButton;
private NotificationExpandButton mExpandButton;
private CachingIconView mIcon;
private View mProfileBadge;
private View mOverlayIcon;
@@ -65,7 +66,6 @@ public class NotificationHeaderView extends ViewGroup {
private View mAppOps;
private View mAudiblyAlertedIcon;
private int mIconColor;
private int mOriginalNotificationColor;
private boolean mExpanded;
private boolean mShowExpandButtonAtEnd;
private boolean mShowWorkBadgeAtEnd;
@@ -324,13 +324,8 @@ public class NotificationHeaderView extends ViewGroup {
return mIconColor;
}
@RemotableViewMethod
public void setOriginalNotificationColor(int color) {
mOriginalNotificationColor = color;
}
public int getOriginalNotificationColor() {
return mOriginalNotificationColor;
return mExpandButton.getOriginalNotificationColor();
}
@RemotableViewMethod
@@ -371,7 +366,7 @@ public class NotificationHeaderView extends ViewGroup {
contentDescriptionId = R.string.expand_button_content_description_collapsed;
}
mExpandButton.setImageDrawable(getContext().getDrawable(drawableId));
mExpandButton.setColorFilter(mOriginalNotificationColor);
mExpandButton.setColorFilter(getOriginalNotificationColor());
mExpandButton.setContentDescription(mContext.getText(contentDescriptionId));
}

View File

@@ -32,6 +32,7 @@ import android.widget.ImageView;
import android.widget.RemoteViews;
import java.util.Objects;
import java.util.function.Consumer;
/**
* An ImageView for displaying an Icon. Avoids reloading the Icon when possible.
@@ -44,6 +45,7 @@ public class CachingIconView extends ImageView {
private boolean mInternalSetDrawable;
private boolean mForceHidden;
private int mDesiredVisibility;
private Consumer<Integer> mOnVisibilityChangedListener;
@UnsupportedAppUsage
public CachingIconView(Context context, @Nullable AttributeSet attrs) {
@@ -198,6 +200,13 @@ public class CachingIconView extends ImageView {
private void updateVisibility() {
int visibility = mDesiredVisibility == VISIBLE && mForceHidden ? INVISIBLE
: mDesiredVisibility;
if (mOnVisibilityChangedListener != null) {
mOnVisibilityChangedListener.accept(visibility);
}
super.setVisibility(visibility);
}
public void setOnVisibilityChangedListener(Consumer<Integer> listener) {
mOnVisibilityChangedListener = listener;
}
}

View File

@@ -0,0 +1,866 @@
/*
* Copyright (C) 2020 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.
*/
package com.android.internal.widget;
import android.annotation.AttrRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StyleRes;
import android.app.Notification;
import android.app.Person;
import android.app.RemoteInputHistoryItem;
import android.content.Context;
import android.content.res.ColorStateList;
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;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Gravity;
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.function.Consumer;
import java.util.regex.Pattern;
/**
* A custom-built layout for the Notification.MessagingStyle allows dynamic addition and removal
* messages and adapts the layout accordingly.
*/
@RemoteViews.RemoteView
public class ConversationLayout extends FrameLayout
implements ImageMessageConsumer, IMessagingLayout {
public static final boolean CONVERSATION_LAYOUT_ENABLED = true;
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);
public static final Interpolator FAST_OUT_LINEAR_IN = new PathInterpolator(0.4f, 0f, 1f, 1f);
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 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 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 Icon mAvatarReplacement;
private boolean mIsOneToOne;
private ArrayList<MessagingGroup> mAddedGroups = new ArrayList<>();
private Person mUser;
private CharSequence mNameReplacement;
private boolean mIsCollapsed;
private ImageResolver mImageResolver;
private ImageView mConversationIcon;
private TextView mConversationText;
private View mConversationIconBadge;
private Icon mLargeIcon;
private View mExpandButtonContainer;
private ViewGroup mExpandButtonAndContentContainer;
private NotificationExpandButton mExpandButton;
private int mExpandButtonExpandedTopMargin;
private int mBadgedSideMargins;
private int mIconSizeBadged;
private int mIconSizeCentered;
private CachingIconView mIcon;
private int mExpandedGroupTopMargin;
private int mExpandButtonExpandedSize;
private View mConversationFacePile;
private int mNotificationBackgroundColor;
private CharSequence mFallbackChatName;
private CharSequence mFallbackGroupChatName;
private CharSequence mConversationTitle;
public ConversationLayout(@NonNull Context context) {
super(context);
}
public ConversationLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public ConversationLayout(@NonNull Context context, @Nullable AttributeSet attrs,
@AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ConversationLayout(@NonNull Context context, @Nullable AttributeSet attrs,
@AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mMessagingLinearLayout = findViewById(R.id.notification_messaging);
mMessagingLinearLayout.setMessagingLayout(this);
// 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);
mConversationIcon = findViewById(R.id.conversation_icon);
mIcon = findViewById(R.id.icon);
mConversationIconBadge = findViewById(R.id.conversation_icon_badge);
mIcon.setOnVisibilityChangedListener((visibility) -> {
// Always keep the badge visibility in sync with the icon. This is necessary in cases
// Where the icon is being hidden externally like in group children.
mConversationIconBadge.setVisibility(visibility);
});
mConversationText = findViewById(R.id.conversation_text);
mExpandButtonContainer = findViewById(R.id.expand_button_container);
mExpandButtonAndContentContainer = findViewById(R.id.expand_button_and_content_container);
mExpandButton = findViewById(R.id.expand_button);
mExpandButtonExpandedTopMargin = getResources().getDimensionPixelSize(
R.dimen.conversation_expand_button_top_margin_expanded);
mExpandButtonExpandedSize = getResources().getDimensionPixelSize(
R.dimen.conversation_expand_button_expanded_size);
mBadgedSideMargins = getResources().getDimensionPixelSize(
R.dimen.conversation_badge_side_margin);
mIconSizeBadged = getResources().getDimensionPixelSize(
R.dimen.conversation_icon_size_badged);
mIconSizeCentered = getResources().getDimensionPixelSize(
R.dimen.conversation_icon_size_centered);
mExpandedGroupTopMargin = getResources().getDimensionPixelSize(
R.dimen.conversation_icon_margin_top_centered);
mConversationFacePile = findViewById(R.id.conversation_face_pile);
mFallbackChatName = getResources().getString(
R.string.conversation_title_fallback_one_to_one);
mFallbackGroupChatName = getResources().getString(
R.string.conversation_title_fallback_group_chat);
}
@RemotableViewMethod
public void setAvatarReplacement(Icon icon) {
mAvatarReplacement = icon;
}
@RemotableViewMethod
public void setNameReplacement(CharSequence nameReplacement) {
mNameReplacement = nameReplacement;
}
/**
* Set this layout to show the collapsed representation.
*
* @param isCollapsed is it collapsed
*/
@RemotableViewMethod
public void setIsCollapsed(boolean isCollapsed) {
mIsCollapsed = isCollapsed;
mMessagingLinearLayout.setMaxDisplayedLines(isCollapsed ? 1 : Integer.MAX_VALUE);
updateExpandButton();
}
@RemotableViewMethod
public void setData(Bundle extras) {
Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
List<Notification.MessagingStyle.Message> newMessages
= Notification.MessagingStyle.Message.getMessagesFromBundleArray(messages);
Parcelable[] histMessages = extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES);
List<Notification.MessagingStyle.Message> newHistoricMessages
= Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages);
// mUser now set (would be nice to avoid the side effect but WHATEVER)
setUser(extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON));
// Append remote input history to newMessages (again, side effect is lame but WHATEVS)
RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[])
extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
addRemoteInputHistoryToMessages(newMessages, history);
boolean showSpinner =
extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
// bind it, baby
bind(newMessages, newHistoricMessages, showSpinner);
}
@Override
public void setImageResolver(ImageResolver resolver) {
mImageResolver = resolver;
}
private void addRemoteInputHistoryToMessages(
List<Notification.MessagingStyle.Message> newMessages,
RemoteInputHistoryItem[] remoteInputHistory) {
if (remoteInputHistory == null || remoteInputHistory.length == 0) {
return;
}
for (int i = remoteInputHistory.length - 1; i >= 0; i--) {
RemoteInputHistoryItem historyMessage = remoteInputHistory[i];
Notification.MessagingStyle.Message message = new Notification.MessagingStyle.Message(
historyMessage.getText(), 0, (Person) null, true /* remoteHistory */);
if (historyMessage.getUri() != null) {
message.setData(historyMessage.getMimeType(), historyMessage.getUri());
}
newMessages.add(message);
}
}
private void bind(List<Notification.MessagingStyle.Message> newMessages,
List<Notification.MessagingStyle.Message> newHistoricMessages,
boolean showSpinner) {
// convert MessagingStyle.Message to MessagingMessage, re-using ones from a previous binding
// if they exist
List<MessagingMessage> historicMessages = createMessages(newHistoricMessages,
true /* isHistoric */);
List<MessagingMessage> messages = createMessages(newMessages, false /* isHistoric */);
// Copy our groups, before they get clobbered
ArrayList<MessagingGroup> oldGroups = new ArrayList<>(mGroups);
// Add our new MessagingMessages to groups
List<List<MessagingMessage>> groups = new ArrayList<>();
List<Person> senders = new ArrayList<>();
// Lets first find the groups (populate `groups` and `senders`)
findGroups(historicMessages, messages, groups, senders);
// Let's now create the views and reorder them accordingly
// side-effect: updates mGroups, mAddedGroups
createGroupViews(groups, senders, showSpinner);
// Let's first check which groups were removed altogether and remove them in one animation
removeGroups(oldGroups);
// Let's remove the remaining messages
mMessages.forEach(REMOVE_MESSAGE);
mHistoricMessages.forEach(REMOVE_MESSAGE);
mMessages = messages;
mHistoricMessages = historicMessages;
updateHistoricMessageVisibility();
updateTitleAndNamesDisplay();
updateConversationLayout();
}
/**
* Update the layout according to the data provided (i.e mIsOneToOne, expanded etc);
*/
private void updateConversationLayout() {
// TODO: resolve this from shortcuts
// Set avatar and name
CharSequence conversationText = mConversationTitle;
// TODO: display the secondary text somewhere
if (mIsOneToOne) {
// Let's resolve the icon / text from the last sender
mConversationIcon.setVisibility(VISIBLE);
mConversationFacePile.setVisibility(GONE);
CharSequence userKey = getKey(mUser);
for (int i = mGroups.size() - 1; i >= 0; i--) {
MessagingGroup messagingGroup = mGroups.get(i);
Person messageSender = messagingGroup.getSender();
if ((messageSender != null && !TextUtils.equals(userKey, getKey(messageSender)))
|| i == 0) {
if (TextUtils.isEmpty(conversationText)) {
// We use the sendername as header text if no conversation title is provided
// (This usually happens for most 1:1 conversations)
conversationText = messagingGroup.getSenderName();
}
Icon avatarIcon = messagingGroup.getAvatarIcon();
if (avatarIcon == null) {
avatarIcon = createAvatarSymbol(conversationText, "", mLayoutColor);
}
mConversationIcon.setImageIcon(avatarIcon);
break;
}
}
} else {
if (mIsCollapsed) {
if (mLargeIcon != null) {
mConversationIcon.setVisibility(VISIBLE);
mConversationFacePile.setVisibility(GONE);
mConversationIcon.setImageIcon(mLargeIcon);
} else {
mConversationIcon.setVisibility(GONE);
// This will also inflate it!
mConversationFacePile.setVisibility(VISIBLE);
mConversationFacePile = findViewById(R.id.conversation_face_pile);
bindFacePile();
}
} else {
mConversationFacePile.setVisibility(GONE);
mConversationIcon.setVisibility(GONE);
}
}
if (TextUtils.isEmpty(conversationText)) {
conversationText = mIsOneToOne ? mFallbackChatName : mFallbackGroupChatName;
}
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);
}
updateIconPositionAndSize();
}
private void bindFacePile() {
// Let's bind the face pile
View bottomBackground = mConversationFacePile.findViewById(
R.id.conversation_face_pile_bottom_background);
applyNotificationBackgroundColor(bottomBackground);
ImageView bottomView = mConversationFacePile.findViewById(
R.id.conversation_face_pile_bottom);
ImageView topView = mConversationFacePile.findViewById(
R.id.conversation_face_pile_top);
// Let's find the two last conversations:
Icon secondLastIcon = null;
CharSequence lastKey = null;
Icon lastIcon = null;
CharSequence userKey = getKey(mUser);
for (int i = mGroups.size() - 1; i >= 0; i--) {
MessagingGroup messagingGroup = mGroups.get(i);
Person messageSender = messagingGroup.getSender();
boolean notUser = messageSender != null
&& !TextUtils.equals(userKey, getKey(messageSender));
boolean notIncluded = messageSender != null
&& !TextUtils.equals(lastKey, getKey(messageSender));
if ((notUser && notIncluded)
|| (i == 0 && lastKey == null)) {
if (lastIcon == null) {
lastIcon = messagingGroup.getAvatarIcon();
lastKey = getKey(messageSender);
} else {
secondLastIcon = messagingGroup.getAvatarIcon();
break;
}
}
}
if (lastIcon == null) {
lastIcon = createAvatarSymbol(" ", "", mLayoutColor);
}
bottomView.setImageIcon(lastIcon);
if (secondLastIcon == null) {
secondLastIcon = createAvatarSymbol("", "", mLayoutColor);
}
topView.setImageIcon(secondLastIcon);
}
/**
* update the icon position and sizing
*/
private void updateIconPositionAndSize() {
int gravity;
int marginStart;
int marginTop;
int iconSize;
if (mIsOneToOne || mIsCollapsed) {
// Baded format
gravity = Gravity.LEFT;
marginStart = mBadgedSideMargins;
marginTop = mBadgedSideMargins;
iconSize = mIconSizeBadged;
} else {
gravity = Gravity.CENTER_HORIZONTAL;
marginStart = 0;
marginTop = mExpandedGroupTopMargin;
iconSize = mIconSizeCentered;
}
LayoutParams layoutParams =
(LayoutParams) mConversationIconBadge.getLayoutParams();
layoutParams.gravity = gravity;
layoutParams.topMargin = marginTop;
layoutParams.setMarginStart(marginStart);
mConversationIconBadge.setLayoutParams(layoutParams);
ViewGroup.LayoutParams iconParams = mIcon.getLayoutParams();
iconParams.width = iconSize;
iconParams.height = iconSize;
mIcon.setLayoutParams(iconParams);
}
@RemotableViewMethod
public void setLargeIcon(Icon largeIcon) {
mLargeIcon = largeIcon;
}
/**
* Sets the conversation title of this conversation.
*
* @param conversationTitle the conversation title
*/
@RemotableViewMethod
public void setConversationTitle(CharSequence conversationTitle) {
mConversationTitle = conversationTitle;
}
private void removeGroups(ArrayList<MessagingGroup> oldGroups) {
int size = oldGroups.size();
for (int i = 0; i < size; i++) {
MessagingGroup group = oldGroups.get(i);
if (!mGroups.contains(group)) {
List<MessagingMessage> messages = group.getMessages();
Runnable endRunnable = () -> {
mMessagingLinearLayout.removeTransientView(group);
group.recycle();
};
boolean wasShown = group.isShown();
mMessagingLinearLayout.removeView(group);
if (wasShown && !MessagingLinearLayout.isGone(group)) {
mMessagingLinearLayout.addTransientView(group, 0);
group.removeGroupAnimated(endRunnable);
} else {
endRunnable.run();
}
mMessages.removeAll(messages);
mHistoricMessages.removeAll(messages);
}
}
}
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);
}
}
}
// Now that we have the correct symbols, let's look what we have cached
ArrayMap<CharSequence, Icon> cachedAvatars = new ArrayMap<>();
for (int i = 0; i < mGroups.size(); i++) {
// Let's now set the avatars
MessagingGroup group = mGroups.get(i);
boolean isOwnMessage = group.getSender() == mUser;
CharSequence senderName = group.getSenderName();
if (!group.needsGeneratedAvatar() || TextUtils.isEmpty(senderName)
|| (mIsOneToOne && mAvatarReplacement != null && !isOwnMessage)) {
continue;
}
String symbol = uniqueNames.get(senderName);
Icon cachedIcon = group.getAvatarSymbolIfMatching(senderName,
symbol, mLayoutColor);
if (cachedIcon != null) {
cachedAvatars.put(senderName, cachedIcon);
}
}
for (int i = 0; i < mGroups.size(); i++) {
// Let's now set the avatars
MessagingGroup group = mGroups.get(i);
CharSequence senderName = group.getSenderName();
if (!group.needsGeneratedAvatar() || TextUtils.isEmpty(senderName)) {
continue;
}
if (mIsOneToOne && mAvatarReplacement != null && group.getSender() != mUser) {
group.setAvatar(mAvatarReplacement);
} else {
Icon cachedIcon = cachedAvatars.get(senderName);
if (cachedIcon == null) {
cachedIcon = createAvatarSymbol(senderName, uniqueNames.get(senderName),
mLayoutColor);
cachedAvatars.put(senderName, cachedIcon);
}
group.setCreatedAvatar(cachedIcon, senderName, uniqueNames.get(senderName),
mLayoutColor);
}
}
}
private 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(),
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);
}
}
private int findColor(CharSequence senderName, int layoutColor) {
double luminance = ContrastColorUtil.calculateLuminance(layoutColor);
float shift = Math.abs(senderName.hashCode()) % 5 / 4.0f - 0.5f;
// we need to offset the range if the luminance is too close to the borders
shift += Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - luminance, 0);
shift -= Math.max(COLOR_SHIFT_AMOUNT / 2.0f / 100 - (1.0f - luminance), 0);
return ContrastColorUtil.getShiftedColor(layoutColor,
(int) (shift * COLOR_SHIFT_AMOUNT));
}
private String findNameSplit(String existingName) {
String[] split = existingName.split(" ");
if (split.length > 1) {
return Character.toString(split[0].charAt(0))
+ Character.toString(split[1].charAt(0));
}
return existingName.substring(0, 1);
}
@RemotableViewMethod
public void setLayoutColor(int color) {
mLayoutColor = color;
}
@RemotableViewMethod
public void setIsOneToOne(boolean oneToOne) {
mIsOneToOne = oneToOne;
}
@RemotableViewMethod
public void setSenderTextColor(int color) {
mSenderTextColor = color;
}
/**
* @param color the color of the notification background
*/
@RemotableViewMethod
public void setNotificationBackgroundColor(int color) {
mNotificationBackgroundColor = color;
applyNotificationBackgroundColor(mConversationIconBadge);
}
private void applyNotificationBackgroundColor(View view) {
view.setBackgroundTintList(ColorStateList.valueOf(mNotificationBackgroundColor));
}
@RemotableViewMethod
public void setMessageTextColor(int color) {
mMessageTextColor = color;
}
private void setUser(Person user) {
mUser = user;
if (mUser.getIcon() == null) {
Icon userIcon = Icon.createWithResource(getContext(),
R.drawable.messaging_user);
userIcon.setTint(mLayoutColor);
mUser = mUser.toBuilder().setIcon(userIcon).build();
}
}
private void createGroupViews(List<List<MessagingMessage>> groups,
List<Person> senders, boolean showSpinner) {
mGroups.clear();
for (int groupIndex = 0; groupIndex < groups.size(); groupIndex++) {
List<MessagingMessage> group = groups.get(groupIndex);
MessagingGroup newGroup = null;
// we'll just take the first group that exists or create one there is none
for (int messageIndex = group.size() - 1; messageIndex >= 0; messageIndex--) {
MessagingMessage message = group.get(messageIndex);
newGroup = message.getGroup();
if (newGroup != null) {
break;
}
}
// Create a new group, adding it to the linear layout as well
if (newGroup == null) {
newGroup = MessagingGroup.createGroup(mMessagingLinearLayout);
mAddedGroups.add(newGroup);
}
newGroup.setDisplayImagesAtEnd(mIsCollapsed);
newGroup.setLayoutColor(mLayoutColor);
newGroup.setTextColors(mSenderTextColor, mMessageTextColor);
Person sender = senders.get(groupIndex);
CharSequence nameOverride = null;
if (sender != mUser && mNameReplacement != null) {
nameOverride = mNameReplacement;
}
newGroup.setShowingAvatar(!mIsOneToOne && !mIsCollapsed);
newGroup.setSingleLine(mIsCollapsed);
newGroup.setSender(sender, nameOverride);
newGroup.setSending(groupIndex == (groups.size() - 1) && showSpinner);
mGroups.add(newGroup);
// Reposition to the correct place (if we're re-using a group)
if (mMessagingLinearLayout.indexOfChild(newGroup) != groupIndex) {
mMessagingLinearLayout.removeView(newGroup);
mMessagingLinearLayout.addView(newGroup, groupIndex);
}
newGroup.setMessages(group);
}
}
private void findGroups(List<MessagingMessage> historicMessages,
List<MessagingMessage> messages, List<List<MessagingMessage>> groups,
List<Person> senders) {
CharSequence currentSenderKey = null;
List<MessagingMessage> currentGroup = null;
int histSize = historicMessages.size();
for (int i = 0; i < histSize + messages.size(); i++) {
MessagingMessage message;
if (i < histSize) {
message = historicMessages.get(i);
} else {
message = messages.get(i - histSize);
}
boolean isNewGroup = currentGroup == null;
Person sender = message.getMessage().getSenderPerson();
CharSequence key = getKey(sender);
isNewGroup |= !TextUtils.equals(key, currentSenderKey);
if (isNewGroup) {
currentGroup = new ArrayList<>();
groups.add(currentGroup);
if (sender == null) {
sender = mUser;
}
senders.add(sender);
currentSenderKey = key;
}
currentGroup.add(message);
}
}
private CharSequence getKey(Person person) {
return person == null ? null : person.getKey() == null ? person.getName() : person.getKey();
}
/**
* Creates new messages, reusing existing ones if they are available.
*
* @param newMessages the messages to parse.
*/
private List<MessagingMessage> createMessages(
List<Notification.MessagingStyle.Message> newMessages, boolean historic) {
List<MessagingMessage> result = new ArrayList<>();
for (int i = 0; i < newMessages.size(); i++) {
Notification.MessagingStyle.Message m = newMessages.get(i);
MessagingMessage message = findAndRemoveMatchingMessage(m);
if (message == null) {
message = MessagingMessage.createMessage(this, m, mImageResolver);
}
message.setIsHistoric(historic);
result.add(message);
}
return result;
}
private MessagingMessage findAndRemoveMatchingMessage(Notification.MessagingStyle.Message m) {
for (int i = 0; i < mMessages.size(); i++) {
MessagingMessage existing = mMessages.get(i);
if (existing.sameAs(m)) {
mMessages.remove(i);
return existing;
}
}
for (int i = 0; i < mHistoricMessages.size(); i++) {
MessagingMessage existing = mHistoricMessages.get(i);
if (existing.sameAs(m)) {
mHistoricMessages.remove(i);
return existing;
}
}
return null;
}
public void showHistoricMessages(boolean show) {
mShowHistoricMessages = show;
updateHistoricMessageVisibility();
}
private void updateHistoricMessageVisibility() {
int numHistoric = mHistoricMessages.size();
for (int i = 0; i < numHistoric; i++) {
MessagingMessage existing = mHistoricMessages.get(i);
existing.setVisibility(mShowHistoricMessages ? VISIBLE : GONE);
}
int numGroups = mGroups.size();
for (int i = 0; i < numGroups; i++) {
MessagingGroup group = mGroups.get(i);
int visibleChildren = 0;
List<MessagingMessage> messages = group.getMessages();
int numGroupMessages = messages.size();
for (int j = 0; j < numGroupMessages; j++) {
MessagingMessage message = messages.get(j);
if (message.getVisibility() != GONE) {
visibleChildren++;
}
}
if (visibleChildren > 0 && group.getVisibility() == GONE) {
group.setVisibility(VISIBLE);
} else if (visibleChildren == 0 && group.getVisibility() != GONE) {
group.setVisibility(GONE);
}
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (!mAddedGroups.isEmpty()) {
getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
for (MessagingGroup group : mAddedGroups) {
if (!group.isShown()) {
continue;
}
MessagingPropertyAnimator.fadeIn(group.getAvatar());
MessagingPropertyAnimator.fadeIn(group.getSenderView());
MessagingPropertyAnimator.startLocalTranslationFrom(group,
group.getHeight(), LINEAR_OUT_SLOW_IN);
}
mAddedGroups.clear();
getViewTreeObserver().removeOnPreDrawListener(this);
return true;
}
});
}
}
public MessagingLinearLayout getMessagingLinearLayout() {
return mMessagingLinearLayout;
}
public ArrayList<MessagingGroup> getMessagingGroups() {
return mGroups;
}
private void updateExpandButton() {
int drawableId;
int contentDescriptionId;
int gravity;
int topMargin = 0;
ViewGroup newContainer;
int newContainerHeight;
if (mIsCollapsed) {
drawableId = R.drawable.ic_expand_notification;
contentDescriptionId = R.string.expand_button_content_description_collapsed;
gravity = Gravity.CENTER;
newContainer = mExpandButtonAndContentContainer;
newContainerHeight = LayoutParams.MATCH_PARENT;
} else {
drawableId = R.drawable.ic_collapse_notification;
contentDescriptionId = R.string.expand_button_content_description_expanded;
gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
topMargin = mExpandButtonExpandedTopMargin;
newContainer = this;
newContainerHeight = mExpandButtonExpandedSize;
}
mExpandButton.setImageDrawable(getContext().getDrawable(drawableId));
mExpandButton.setColorFilter(mExpandButton.getOriginalNotificationColor());
// We need to make sure that the expand button is in the linearlayout pushing over the
// content when collapsed, but allows the content to flow under it when expanded.
if (newContainer != mExpandButtonContainer.getParent()) {
((ViewGroup) mExpandButtonContainer.getParent()).removeView(mExpandButtonContainer);
newContainer.addView(mExpandButtonContainer);
MarginLayoutParams layoutParams =
(MarginLayoutParams) mExpandButtonContainer.getLayoutParams();
layoutParams.height = newContainerHeight;
mExpandButtonContainer.setLayoutParams(layoutParams);
}
// update if the expand button is centered
FrameLayout.LayoutParams layoutParams = (LayoutParams) mExpandButton.getLayoutParams();
layoutParams.gravity = gravity;
layoutParams.topMargin = topMargin;
mExpandButton.setLayoutParams(layoutParams);
mExpandButtonContainer.setContentDescription(mContext.getText(contentDescriptionId));
}
public void updateExpandability(boolean expandable, @Nullable OnClickListener onClickListener) {
if (expandable) {
mExpandButtonContainer.setVisibility(VISIBLE);
mExpandButtonContainer.setOnClickListener(onClickListener);
} else {
// TODO: handle content paddings to end of layout
mExpandButtonContainer.setVisibility(GONE);
}
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2020 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
*/
package com.android.internal.widget;
import android.content.Context;
import java.util.ArrayList;
/**
* An interface for a MessagingLayout
*/
public interface IMessagingLayout {
/**
* @return the layout containing the messages
*/
MessagingLinearLayout getMessagingLinearLayout();
/**
* @return the context of this view
*/
Context getContext();
/**
* @return the list of messaging groups
*/
ArrayList<MessagingGroup> getMessagingGroups();
}

View File

@@ -55,8 +55,9 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
private static Pools.SimplePool<MessagingGroup> sInstancePool
= new Pools.SynchronizedPool<>(10);
private MessagingLinearLayout mMessageContainer;
private ImageFloatingTextView mSenderName;
ImageFloatingTextView mSenderView;
private ImageView mAvatarView;
private View mAvatarContainer;
private String mAvatarSymbol = "";
private int mLayoutColor;
private CharSequence mAvatarName = "";
@@ -72,10 +73,18 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
private boolean mImagesAtEnd;
private ViewGroup mImageContainer;
private MessagingImageMessage mIsolatedMessage;
private boolean mTransformingImages;
private boolean mClippingDisabled;
private Point mDisplaySize = new Point();
private ProgressBar mSendingSpinner;
private View mSendingSpinnerContainer;
private boolean mShowingAvatar = true;
private CharSequence mSenderName;
private boolean mSingleLine = false;
private LinearLayout mContentContainer;
private int mRequestedMaxDisplayedLines = Integer.MAX_VALUE;
private int mSenderTextPaddingSingleLine;
private boolean mIsFirstGroupInLayout = true;
private boolean mCanHideSenderIfFirst;
public MessagingGroup(@NonNull Context context) {
super(context);
@@ -99,26 +108,34 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
protected void onFinishInflate() {
super.onFinishInflate();
mMessageContainer = findViewById(R.id.group_message_container);
mSenderName = findViewById(R.id.message_name);
mSenderView = findViewById(R.id.message_name);
mAvatarView = findViewById(R.id.message_icon);
mImageContainer = findViewById(R.id.messaging_group_icon_container);
mSendingSpinner = findViewById(R.id.messaging_group_sending_progress);
mContentContainer = findViewById(R.id.messaging_group_content_container);
mSendingSpinnerContainer = findViewById(R.id.messaging_group_sending_progress_container);
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
mDisplaySize.x = displayMetrics.widthPixels;
mDisplaySize.y = displayMetrics.heightPixels;
mSenderTextPaddingSingleLine = getResources().getDimensionPixelSize(
R.dimen.messaging_group_singleline_sender_padding_end);
}
public void updateClipRect() {
// We want to clip to the senderName if it's available, otherwise our images will come
// from a weird position
Rect clipRect;
if (mSenderName.getVisibility() != View.GONE && !mTransformingImages) {
ViewGroup parent = (ViewGroup) mSenderName.getParent();
int top = getDistanceFromParent(mSenderName, parent) - getDistanceFromParent(
mMessageContainer, parent) + mSenderName.getHeight();
if (mSenderView.getVisibility() != View.GONE && !mClippingDisabled) {
int top;
if (mSingleLine) {
top = 0;
} else {
top = getDistanceFromParent(mSenderView, mContentContainer)
- getDistanceFromParent(mMessageContainer, mContentContainer)
+ mSenderView.getHeight();
}
int size = Math.max(mDisplaySize.x, mDisplaySize.y);
clipRect = new Rect(0, top, size, size);
clipRect = new Rect(-size, top, size, size);
} else {
clipRect = null;
}
@@ -140,17 +157,31 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
if (nameOverride == null) {
nameOverride = sender.getName();
}
mSenderName.setText(nameOverride);
mSenderName = nameOverride;
if (mSingleLine && !TextUtils.isEmpty(nameOverride)) {
nameOverride = mContext.getResources().getString(
R.string.conversation_single_line_name_display, nameOverride);
}
mSenderView.setText(nameOverride);
mNeedsGeneratedAvatar = sender.getIcon() == null;
if (!mNeedsGeneratedAvatar) {
setAvatar(sender.getIcon());
}
mAvatarView.setVisibility(VISIBLE);
mSenderName.setVisibility(TextUtils.isEmpty(nameOverride) ? GONE : VISIBLE);
updateSenderVisibility();
}
/**
* Should the avatar be shown for this view.
*
* @param showingAvatar should it be shown
*/
public void setShowingAvatar(boolean showingAvatar) {
mAvatarView.setVisibility(showingAvatar ? VISIBLE : GONE);
mShowingAvatar = showingAvatar;
}
public void setSending(boolean sending) {
int visibility = sending ? View.VISIBLE : View.GONE;
int visibility = sending ? VISIBLE : GONE;
if (mSendingSpinnerContainer.getVisibility() != visibility) {
mSendingSpinnerContainer.setVisibility(visibility);
updateMessageColor();
@@ -171,7 +202,9 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
public void setAvatar(Icon icon) {
mAvatarIcon = icon;
mAvatarView.setImageIcon(icon);
if (mShowingAvatar || icon == null) {
mAvatarView.setImageIcon(icon);
}
mAvatarSymbol = "";
mAvatarName = "";
}
@@ -220,13 +253,20 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
setAvatar(null);
mAvatarView.setAlpha(1.0f);
mAvatarView.setTranslationY(0.0f);
mSenderName.setAlpha(1.0f);
mSenderName.setTranslationY(0.0f);
mSenderView.setAlpha(1.0f);
mSenderView.setTranslationY(0.0f);
setAlpha(1.0f);
mIsolatedMessage = null;
mMessages = null;
mSenderName = null;
mAddedMessages.clear();
mFirstLayout = true;
setCanHideSenderIfFirst(false);
setIsFirstInLayout(true);
setMaxDisplayedLines(Integer.MAX_VALUE);
setSingleLine(false);
setShowingAvatar(true);
MessagingPropertyAnimator.recycle(this);
sInstancePool.release(MessagingGroup.this);
}
@@ -252,7 +292,7 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
}
public CharSequence getSenderName() {
return mSenderName.getText();
return mSenderName;
}
public static void dropCache() {
@@ -310,7 +350,12 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
@Override
public void setMaxDisplayedLines(int lines) {
mMessageContainer.setMaxDisplayedLines(lines);
mRequestedMaxDisplayedLines = lines;
updateMaxDisplayedLines();
}
private void updateMaxDisplayedLines() {
mMessageContainer.setMaxDisplayedLines(mSingleLine ? 1 : mRequestedMaxDisplayedLines);
}
@Override
@@ -324,6 +369,35 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
return mIsHidingAnimated;
}
@Override
public void setIsFirstInLayout(boolean first) {
if (first != mIsFirstGroupInLayout) {
mIsFirstGroupInLayout = first;
updateSenderVisibility();
}
}
/**
* @param canHide true if the sender can be hidden if it is first
*/
public void setCanHideSenderIfFirst(boolean canHide) {
if (mCanHideSenderIfFirst != canHide) {
mCanHideSenderIfFirst = canHide;
updateSenderVisibility();
}
}
private void updateSenderVisibility() {
boolean hidden = (mIsFirstGroupInLayout || mSingleLine) && mCanHideSenderIfFirst
|| TextUtils.isEmpty(mSenderName);
mSenderView.setVisibility(hidden ? GONE : VISIBLE);
}
@Override
public boolean hasDifferentHeightWhenFirst() {
return mCanHideSenderIfFirst && !mSingleLine && !TextUtils.isEmpty(mSenderName);
}
private void setIsHidingAnimated(boolean isHiding) {
ViewParent parent = getParent();
mIsHidingAnimated = isHiding;
@@ -362,7 +436,7 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
mTextColor = messageTextColor;
mSendingTextColor = calculateSendingTextColor();
updateMessageColor();
mSenderName.setTextColor(senderTextColor);
mSenderView.setTextColor(senderTextColor);
}
public void setLayoutColor(int layoutColor) {
@@ -506,13 +580,17 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
}
public View getSenderView() {
return mSenderName;
return mSenderView;
}
public View getAvatar() {
return mAvatarView;
}
public Icon getAvatarIcon() {
return mAvatarIcon;
}
public MessagingLinearLayout getMessageContainer() {
return mMessageContainer;
}
@@ -529,8 +607,8 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
return mSender;
}
public void setTransformingImages(boolean transformingImages) {
mTransformingImages = transformingImages;
public void setClippingDisabled(boolean disabled) {
mClippingDisabled = disabled;
}
public void setDisplayImagesAtEnd(boolean atEnd) {
@@ -543,4 +621,27 @@ public class MessagingGroup extends LinearLayout implements MessagingLinearLayou
public List<MessagingMessage> getMessages() {
return mMessages;
}
/**
* Set this layout to be single line and therefore displaying both the sender and the text on
* the same line.
*
* @param singleLine should be layout be single line
*/
public void setSingleLine(boolean singleLine) {
if (singleLine != mSingleLine) {
mSingleLine = singleLine;
mContentContainer.setOrientation(
singleLine ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
MarginLayoutParams layoutParams = (MarginLayoutParams) mSenderView.getLayoutParams();
layoutParams.setMarginEnd(singleLine ? mSenderTextPaddingSingleLine : 0);
updateMaxDisplayedLines();
updateClipRect();
updateSenderVisibility();
}
}
public boolean isSingleLine() {
return mSingleLine;
}
}

View File

@@ -120,7 +120,7 @@ public class MessagingImageMessage extends ImageView implements MessagingMessage
return true;
}
static MessagingMessage createMessage(MessagingLayout layout,
static MessagingMessage createMessage(IMessagingLayout layout,
Notification.MessagingStyle.Message m, ImageResolver resolver) {
MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
MessagingImageMessage createdMessage = sInstancePool.acquire();

View File

@@ -58,7 +58,8 @@ import java.util.regex.Pattern;
* messages and adapts the layout accordingly.
*/
@RemoteViews.RemoteView
public class MessagingLayout extends FrameLayout implements ImageMessageConsumer {
public class MessagingLayout extends FrameLayout
implements ImageMessageConsumer, IMessagingLayout {
private static final float COLOR_SHIFT_AMOUNT = 60;
/**
@@ -143,9 +144,29 @@ public class MessagingLayout extends FrameLayout implements ImageMessageConsumer
mNameReplacement = nameReplacement;
}
/**
* Set this layout to show the collapsed representation.
*
* @param isCollapsed is it collapsed
*/
@RemotableViewMethod
public void setDisplayImagesAtEnd(boolean atEnd) {
mDisplayImagesAtEnd = atEnd;
public void setIsCollapsed(boolean isCollapsed) {
mDisplayImagesAtEnd = isCollapsed;
}
@RemotableViewMethod
public void setLargeIcon(Icon largeIcon) {
// Unused
}
/**
* Sets the conversation title of this conversation.
*
* @param conversationTitle the conversation title
*/
@RemotableViewMethod
public void setConversationTitle(CharSequence conversationTitle) {
// Unused
}
@RemotableViewMethod
@@ -371,6 +392,15 @@ public class MessagingLayout extends FrameLayout implements ImageMessageConsumer
mSenderTextColor = color;
}
/**
* @param color the color of the notification background
*/
@RemotableViewMethod
public void setNotificationBackgroundColor(int color) {
// Nothing to do with this
}
@RemotableViewMethod
public void setMessageTextColor(int color) {
mMessageTextColor = color;

View File

@@ -43,7 +43,7 @@ public class MessagingLinearLayout extends ViewGroup {
private int mMaxDisplayedLines = Integer.MAX_VALUE;
private MessagingLayout mMessagingLayout;
private IMessagingLayout mMessagingLayout;
public MessagingLinearLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
@@ -84,6 +84,11 @@ public class MessagingLinearLayout extends ViewGroup {
final View child = getChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
lp.hide = true;
if (child instanceof MessagingChild) {
MessagingChild messagingChild = (MessagingChild) child;
// Whenever we encounter the message first, it's always first in the layout
messagingChild.setIsFirstInLayout(true);
}
}
totalHeight = mPaddingTop + mPaddingBottom;
@@ -91,6 +96,11 @@ public class MessagingLinearLayout extends ViewGroup {
int linesRemaining = mMaxDisplayedLines;
// Starting from the bottom: we measure every view as if it were the only one. If it still
// fits, we take it, otherwise we stop there.
MessagingChild previousChild = null;
View previousView = null;
int previousChildHeight = 0;
int previousTotalHeight = 0;
int previousLinesConsumed = 0;
for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) {
if (getChildAt(i).getVisibility() == GONE) {
continue;
@@ -99,7 +109,16 @@ public class MessagingLinearLayout extends ViewGroup {
LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
MessagingChild messagingChild = null;
int spacing = mSpacing;
int previousChildIncrease = 0;
if (child instanceof MessagingChild) {
// We need to remeasure the previous child again if it's not the first anymore
if (previousChild != null && previousChild.hasDifferentHeightWhenFirst()) {
previousChild.setIsFirstInLayout(false);
measureChildWithMargins(previousView, widthMeasureSpec, 0, heightMeasureSpec,
previousTotalHeight - previousChildHeight);
previousChildIncrease = previousView.getMeasuredHeight() - previousChildHeight;
linesRemaining -= previousChild.getConsumedLines() - previousLinesConsumed;
}
messagingChild = (MessagingChild) child;
messagingChild.setMaxDisplayedLines(linesRemaining);
spacing += messagingChild.getExtraSpacing();
@@ -110,18 +129,26 @@ public class MessagingLinearLayout extends ViewGroup {
final int childHeight = child.getMeasuredHeight();
int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin +
lp.bottomMargin + spacing);
lp.bottomMargin + spacing + previousChildIncrease);
int measureType = MessagingChild.MEASURED_NORMAL;
if (messagingChild != null) {
measureType = messagingChild.getMeasuredType();
linesRemaining -= messagingChild.getConsumedLines();
}
// We never measure the first item as too small, we want to at least show something.
boolean isTooSmall = measureType == MessagingChild.MEASURED_TOO_SMALL && !first;
boolean isShortened = measureType == MessagingChild.MEASURED_SHORTENED
|| measureType == MessagingChild.MEASURED_TOO_SMALL && first;
if (newHeight <= targetHeight && !isTooSmall) {
boolean showView = newHeight <= targetHeight && !isTooSmall;
if (showView) {
if (messagingChild != null) {
previousLinesConsumed = messagingChild.getConsumedLines();
linesRemaining -= previousLinesConsumed;
previousChild = messagingChild;
previousView = child;
previousChildHeight = childHeight;
previousTotalHeight = totalHeight;
}
totalHeight = newHeight;
measuredWidth = Math.max(measuredWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin
@@ -131,6 +158,16 @@ public class MessagingLinearLayout extends ViewGroup {
break;
}
} else {
// We now became too short, let's make sure to reset any previous views to be first
// and remeasure it.
if (previousChild != null && previousChild.hasDifferentHeightWhenFirst()) {
previousChild.setIsFirstInLayout(true);
// We need to remeasure the previous child again since it became first
measureChildWithMargins(previousView, widthMeasureSpec, 0, heightMeasureSpec,
previousTotalHeight - previousChildHeight);
// The totalHeight is already correct here since we only set it during the
// first pass
}
break;
}
first = false;
@@ -255,11 +292,11 @@ public class MessagingLinearLayout extends ViewGroup {
mMaxDisplayedLines = numberLines;
}
public void setMessagingLayout(MessagingLayout layout) {
public void setMessagingLayout(IMessagingLayout layout) {
mMessagingLayout = layout;
}
public MessagingLayout getMessagingLayout() {
public IMessagingLayout getMessagingLayout() {
return mMessagingLayout;
}
@@ -273,6 +310,20 @@ public class MessagingLinearLayout extends ViewGroup {
void setMaxDisplayedLines(int lines);
void hideAnimated();
boolean isHidingAnimated();
/**
* Set that this view is first in layout. Relevant and only set if
* {@link #hasDifferentHeightWhenFirst()}.
* @param first is this first?
*/
default void setIsFirstInLayout(boolean first) {}
/**
* @return if this layout has different height it is first in the layout
*/
default boolean hasDifferentHeightWhenFirst() {
return false;
}
default int getExtraSpacing() {
return 0;
}

View File

@@ -32,7 +32,7 @@ public interface MessagingMessage extends MessagingLinearLayout.MessagingChild {
**/
String IMAGE_MIME_TYPE_PREFIX = "image/";
static MessagingMessage createMessage(MessagingLayout layout,
static MessagingMessage createMessage(IMessagingLayout layout,
Notification.MessagingStyle.Message m, ImageResolver resolver) {
if (hasImage(m) && !ActivityManager.isLowRamDeviceStatic()) {
return MessagingImageMessage.createMessage(layout, m, resolver);

View File

@@ -26,14 +26,10 @@ import android.text.Layout;
import android.util.AttributeSet;
import android.util.Pools;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.RemoteViews;
import com.android.internal.R;
import java.util.Objects;
/**
* A message of a {@link MessagingLayout}.
*/
@@ -74,7 +70,7 @@ public class MessagingTextMessage extends ImageFloatingTextView implements Messa
return true;
}
static MessagingMessage createMessage(MessagingLayout layout,
static MessagingMessage createMessage(IMessagingLayout layout,
Notification.MessagingStyle.Message m) {
MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
MessagingTextMessage createdMessage = sInstancePool.acquire();

View File

@@ -20,7 +20,7 @@ import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.RemotableViewMethod;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Button;
import android.widget.ImageView;
@@ -32,6 +32,8 @@ import android.widget.RemoteViews;
@RemoteViews.RemoteView
public class NotificationExpandButton extends ImageView {
private int mOriginalNotificationColor;
public NotificationExpandButton(Context context) {
super(context);
}
@@ -56,6 +58,15 @@ public class NotificationExpandButton extends ImageView {
extendRectToMinTouchSize(outRect);
}
@RemotableViewMethod
public void setOriginalNotificationColor(int color) {
mOriginalNotificationColor = color;
}
public int getOriginalNotificationColor() {
return mOriginalNotificationColor;
}
private void extendRectToMinTouchSize(Rect rect) {
int touchTargetSize = (int) (getResources().getDisplayMetrics().density * 48);
rect.left = rect.centerX() - touchTargetSize / 2;

View File

@@ -23,6 +23,8 @@ import android.view.View;
import android.widget.LinearLayout;
import android.widget.RemoteViews;
import java.util.ArrayList;
/**
* A LinearLayout that sets it's height again after the last measure pass. This is needed for
* MessagingLayouts where groups need to be able to snap it's height to.
@@ -30,6 +32,8 @@ import android.widget.RemoteViews;
@RemoteViews.RemoteView
public class RemeasuringLinearLayout extends LinearLayout {
private ArrayList<View> mMatchParentViews = new ArrayList<>();
public RemeasuringLinearLayout(Context context) {
super(context);
}
@@ -53,6 +57,8 @@ public class RemeasuringLinearLayout extends LinearLayout {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int count = getChildCount();
int height = 0;
boolean isVertical = getOrientation() == LinearLayout.VERTICAL;
boolean isWrapContent = getLayoutParams().height == LayoutParams.WRAP_CONTENT;
for (int i = 0; i < count; ++i) {
final View child = getChildAt(i);
if (child == null || child.getVisibility() == View.GONE) {
@@ -60,9 +66,25 @@ public class RemeasuringLinearLayout extends LinearLayout {
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
height = Math.max(height, height + child.getMeasuredHeight() + lp.topMargin +
lp.bottomMargin);
if (!isWrapContent || lp.height != LayoutParams.MATCH_PARENT || isVertical) {
int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
height = Math.max(height, isVertical ? height + childHeight : childHeight);
} else {
// We have match parent children in a wrap content view, let's measure the
// view properly
mMatchParentViews.add(child);
}
}
if (mMatchParentViews.size() > 0) {
int exactHeightSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
for (View child : mMatchParentViews) {
child.measure(getChildMeasureSpec(
widthMeasureSpec, getPaddingStart() + getPaddingEnd(),
child.getLayoutParams().width),
exactHeightSpec);
}
}
mMatchParentViews.clear();
setMeasuredDimension(getMeasuredWidth(), height);
}
}

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 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
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid
android:color="#ffffff"/>
<size
android:width="26dp"
android:height="26dp"/>
</shape>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2015 The Android Open Source Project
Copyright (C) 2020 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.
@@ -15,11 +15,14 @@ Copyright (C) 2015 The Android Open Source Project
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="14.0dp"
android:height="14.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
android:width="22.0dp"
android:height="22.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12.0,8.0l-6.0,6.0l1.4,1.4l4.6,-4.6l4.6,4.6L18.0,14.0L12.0,8.0z"/>
</vector>
android:pathData="M18.59,16.41L20.0,15.0l-8.0,-8.0 -8.0,8.0 1.41,1.41L12.0,9.83"/>
<path
android:pathData="M0 0h24v24H0V0z"
android:fillColor="#00000000"/>
</vector>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2015 The Android Open Source Project
Copyright (C) 2014 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.
@@ -15,11 +15,14 @@ Copyright (C) 2015 The Android Open Source Project
limitations under the License.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="14.0dp"
android:height="14.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
android:width="22.0dp"
android:height="22.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M16.6,8.6L12.0,13.2L7.4,8.6L6.0,10.0l6.0,6.0l6.0,-6.0L16.6,8.6z"/>
</vector>
android:pathData="M5.41,7.59L4.0,9.0l8.0,8.0 8.0,-8.0 -1.41,-1.41L12.0,14.17"/>
<path
android:pathData="M24 24H0V0h24v24z"
android:fillColor="#00000000"/>
</vector>

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 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
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/conversation_face_pile"
android:layout_width="@dimen/conversation_avatar_size"
android:layout_height="@dimen/conversation_avatar_size"
android:forceHasOverlappingRendering="false"
>
<ImageView
android:id="@+id/conversation_face_pile_top"
android:layout_width="36dp"
android:layout_height="36dp"
android:scaleType="centerCrop"
android:layout_gravity="end|top"
/>
<FrameLayout
android:id="@+id/conversation_face_pile_bottom_background"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="start|bottom"
android:background="@drawable/conversation_badge_background">
<ImageView
android:id="@+id/conversation_face_pile_bottom"
android:layout_width="36dp"
android:layout_height="36dp"
android:scaleType="centerCrop"
android:layout_gravity="center"
/>
</FrameLayout>
</FrameLayout>

View File

@@ -91,7 +91,6 @@
android:textAppearance="@style/TextAppearance.Material.Notification.Time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="@dimen/notification_header_separating_margin"
android:layout_marginEnd="@dimen/notification_header_separating_margin"
android:showRelative="true"

View File

@@ -0,0 +1,205 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 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.ConversationLayout
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:clipChildren="false"
android:tag="conversation"
android:theme="@style/Theme.DeviceDefault.Notification"
>
<FrameLayout
android:layout_width="@dimen/conversation_content_start"
android:layout_height="wrap_content"
android:gravity="start|top"
android:clipChildren="false"
android:clipToPadding="false"
android:paddingTop="12dp"
>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|center_horizontal"
>
<!-- Big icon: 52x52, 12dp padding left + top, 16dp padding right -->
<ImageView
android:id="@+id/conversation_icon"
android:layout_width="@dimen/conversation_avatar_size"
android:layout_height="@dimen/conversation_avatar_size"
android:scaleType="centerCrop"
android:importantForAccessibility="no"
/>
<ViewStub
android:layout="@layout/conversation_face_pile_layout"
android:layout_width="@dimen/conversation_avatar_size"
android:layout_height="@dimen/conversation_avatar_size"
android:id="@+id/conversation_face_pile"
/>
<FrameLayout
android:id="@+id/conversation_icon_badge"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginLeft="@dimen/conversation_badge_side_margin"
android:layout_marginTop="@dimen/conversation_badge_side_margin"
android:background="@drawable/conversation_badge_background" >
<!-- Badge: 20x20, 48dp padding left + top -->
<com.android.internal.widget.CachingIconView
android:id="@+id/icon"
android:layout_width="@dimen/conversation_icon_size_badged"
android:layout_height="@dimen/conversation_icon_size_badged"
android:layout_gravity="center"
/>
</FrameLayout>
</FrameLayout>
</FrameLayout>
<!-- Wraps entire "expandable" notification -->
<com.android.internal.widget.RemeasuringLinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:clipToPadding="false"
android:clipChildren="false"
android:orientation="vertical"
>
<!-- LinearLayout for Expand Button-->
<com.android.internal.widget.RemeasuringLinearLayout
android:id="@+id/expand_button_and_content_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="start|top"
android:orientation="horizontal"
android:clipChildren="false"
android:clipToPadding="false">
<!--TODO: move this into a separate layout and share logic with the header to bring back app opps etc-->
<FrameLayout
android:id="@+id/notification_action_list_margin_target"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1">
<!-- Header -->
<LinearLayout
android:id="@+id/conversation_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="16dp"
android:paddingStart="@dimen/conversation_content_start"
>
<TextView
android:id="@+id/conversation_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/notification_header_separating_margin"
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
android:textSize="16sp"
android:singleLine="true"
/>
<TextView
android:id="@+id/time_divider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?attr/notificationHeaderTextAppearance"
android:layout_marginStart="@dimen/notification_header_separating_margin"
android:layout_marginEnd="@dimen/notification_header_separating_margin"
android:text="@string/notification_header_divider_symbol"
android:layout_gravity="center"
android:paddingTop="1sp"
android:singleLine="true"
android:visibility="gone"
/>
<DateTimeView
android:id="@+id/time"
android:textAppearance="@style/TextAppearance.Material.Notification.Time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="@dimen/notification_header_separating_margin"
android:paddingTop="1sp"
android:showRelative="true"
android:singleLine="true"
android:visibility="gone"
/>
<ImageView
android:id="@+id/profile_badge"
android:layout_width="@dimen/notification_badge_size"
android:layout_height="@dimen/notification_badge_size"
android:layout_gravity="center"
android:layout_marginStart="4dp"
android:paddingTop="2dp"
android:scaleType="fitCenter"
android:visibility="gone"
android:contentDescription="@string/notification_work_profile_content_description"
/>
</LinearLayout>
<!-- Messages -->
<com.android.internal.widget.MessagingLinearLayout
android:id="@+id/notification_messaging"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:spacing="@dimen/notification_messaging_spacing"
android:clipToPadding="false"
android:clipChildren="false"
/>
</FrameLayout>
<!-- Unread Count -->
<!-- <TextView /> -->
<!-- This is where the expand button will be placed when collapsed-->
</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/conversation_content_start"
android:layout_marginEnd="@dimen/notification_content_margin_end" />
<include layout="@layout/notification_material_action_list" />
</com.android.internal.widget.RemeasuringLinearLayout>
<!--This is dynamically placed between here and at the end of the layout-->
<FrameLayout
android:id="@+id/expand_button_container"
android:layout_width="wrap_content"
android:layout_height="@dimen/conversation_expand_button_expanded_size"
android:layout_gravity="end|top"
android:paddingStart="16dp"
android:paddingEnd="@dimen/notification_content_margin_end">
<com.android.internal.widget.NotificationExpandButton
android:id="@+id/expand_button"
android:layout_width="@dimen/notification_header_expand_icon_size"
android:layout_height="@dimen/notification_header_expand_icon_size"
android:layout_gravity="center"
android:drawable="@drawable/ic_expand_notification"
android:clickable="false"
android:importantForAccessibility="no"
/>
</FrameLayout>
</com.android.internal.widget.ConversationLayout>

View File

@@ -20,14 +20,19 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal" >
<ImageView
android:id="@+id/message_icon"
android:layout_width="@dimen/messaging_avatar_size"
android:layout_height="@dimen/messaging_avatar_size"
android:layout_marginEnd="12dp"
android:scaleType="centerCrop"
android:importantForAccessibility="no" />
<FrameLayout
android:layout_width="@dimen/conversation_content_start"
android:layout_height="wrap_content"> <!--TODO: make sure to make this padding dynamic-->
<ImageView
android:layout_gravity="top|center_horizontal"
android:id="@+id/message_icon"
android:layout_width="@dimen/messaging_avatar_size"
android:layout_height="@dimen/messaging_avatar_size"
android:scaleType="centerCrop"
android:importantForAccessibility="no" />
</FrameLayout>
<com.android.internal.widget.RemeasuringLinearLayout
android:id="@+id/messaging_group_content_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
@@ -43,7 +48,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/notification_text_margin_top"
android:spacing="2dp"/>
android:spacing="2dp" />
</com.android.internal.widget.RemeasuringLinearLayout>
<FrameLayout
android:id="@+id/messaging_group_icon_container"

View File

@@ -682,7 +682,25 @@
<!-- The size of the right icon image when on low ram -->
<dimen name="notification_right_icon_size_low_ram">@dimen/notification_right_icon_size</dimen>
<dimen name="messaging_avatar_size">52dp</dimen>
<dimen name="messaging_avatar_size">@dimen/notification_right_icon_size</dimen>
<dimen name="conversation_avatar_size">52dp</dimen>
<!-- Start of the content in the conversation template -->
<dimen name="conversation_content_start">80dp</dimen>
<!-- Size of the expand button when expanded -->
<dimen name="conversation_expand_button_expanded_size">80dp</dimen>
<!-- Top margin of the expand button for conversations when expanded -->
<dimen name="conversation_expand_button_top_margin_expanded">18dp</dimen>
<!-- Side margins of the conversation badge in relation to the conversation icon -->
<dimen name="conversation_badge_side_margin">36dp</dimen>
<!-- size of the notification icon when badged in a conversation -->
<dimen name="conversation_icon_size_badged">15dp</dimen>
<!-- size of the notification icon when centered in a conversation -->
<dimen name="conversation_icon_size_centered">20dp</dimen>
<!-- margin on the top when the icon is centered for group conversations -->
<dimen name="conversation_icon_margin_top_centered">5dp</dimen>
<!-- Padding between text and sender when singleline -->
<dimen name="messaging_group_singleline_sender_padding_end">4dp</dimen>
<dimen name="messaging_group_sending_progress_size">24dp</dimen>

View File

@@ -5440,6 +5440,15 @@
<string name="as_app_forced_to_restricted_bucket">
<xliff:g id="package_name" example="com.android.example">%1$s</xliff:g> has been put into the RESTRICTED bucket</string>
<!-- The way a conversation name is displayed when single line. The text will be displayed to the end of this text with some spacing -->
<string name="conversation_single_line_name_display"><xliff:g id="sender_name" example="Sara">%1$s</xliff:g>:</string>
<!-- Conversation Title fallback if the there is no name provided in a 1:1 conversation [CHAR LIMIT=40]-->
<string name="conversation_title_fallback_one_to_one">Conversation</string>
<!-- Conversation Title fallback if the there is no name provided in a group chat conversation [CHAR LIMIT=40]-->
<string name="conversation_title_fallback_group_chat">Group Conversation</string>
<!-- ResolverActivity - profile tabs -->
<!-- Label of a tab on a screen. A user can tap this tap to switch to the 'Personal' view (that shows their personal content) if they have a work profile on their device. [CHAR LIMIT=NONE] -->
<string name="resolver_personal_tab">Personal</string>

View File

@@ -146,7 +146,7 @@ easier.
<item name="textAppearance">@style/TextAppearance.DeviceDefault.Notification</item>
</style>
<style name="Widget.DeviceDefault.Notification.MessagingName" parent="Widget.Material.Notification.MessagingName">
<item name="textAppearance">@style/TextAppearance.DeviceDefault.Notification.MessagingName</item>
<item name="textAppearance">@style/TextAppearance.DeviceDefault.Notification.Title</item>
</style>
<style name="Widget.DeviceDefault.PreferenceFrameLayout" parent="Widget.Material.PreferenceFrameLayout"/>
<style name="Widget.DeviceDefault.ProgressBar.Inverse" parent="Widget.Material.ProgressBar.Inverse"/>
@@ -290,9 +290,6 @@ easier.
<style name="TextAppearance.DeviceDefault.Notification.Title" parent="TextAppearance.Material.Notification.Title">
<item name="fontFamily">@string/config_headlineFontFamilyMedium</item>
</style>
<style name="TextAppearance.DeviceDefault.Notification.MessagingName" parent="TextAppearance.DeviceDefault.Notification.Title">
<item name="textSize">16sp</item>
</style>
<style name="TextAppearance.DeviceDefault.Notification.Reply" parent="TextAppearance.Material.Notification.Reply">
<item name="fontFamily">@string/config_bodyFontFamily</item>
</style>

View File

@@ -504,7 +504,7 @@ please see styles_device_defaults.xml.
<style name="Widget.Material.Notification.MessagingText" parent="Widget.Material.Notification.Text">
<item name="layout_width">wrap_content</item>
<item name="layout_height">wrap_content</item>
<item name="ellipsize">end</item>z
<item name="ellipsize">end</item>
</style>
<style name="Widget.Material.Notification.MessagingName" parent="Widget.Material.Light.TextView">

View File

@@ -3876,6 +3876,30 @@
<java-symbol type="array" name="config_defaultImperceptibleKillingExemptionPkgs" />
<java-symbol type="array" name="config_defaultImperceptibleKillingExemptionProcStates" />
<java-symbol type="string" name="conversation_single_line_name_display" />
<java-symbol type="string" name="conversation_title_fallback_one_to_one" />
<java-symbol type="string" name="conversation_title_fallback_group_chat" />
<java-symbol type="id" name="conversation_icon" />
<java-symbol type="id" name="conversation_icon_badge" />
<java-symbol type="id" name="expand_button_container" />
<java-symbol type="id" name="messaging_group_content_container" />
<java-symbol type="id" name="expand_button_and_content_container" />
<java-symbol type="id" name="conversation_header" />
<java-symbol type="id" name="conversation_face_pile_bottom_background" />
<java-symbol type="id" name="conversation_face_pile_bottom" />
<java-symbol type="id" name="conversation_face_pile_top" />
<java-symbol type="id" name="conversation_face_pile" />
<java-symbol type="id" name="conversation_text" />
<java-symbol type="dimen" name="conversation_expand_button_top_margin_expanded" />
<java-symbol type="dimen" name="conversation_expand_button_expanded_size" />
<java-symbol type="dimen" name="messaging_group_singleline_sender_padding_end" />
<java-symbol type="dimen" name="conversation_badge_side_margin" />
<java-symbol type="dimen" name="conversation_icon_size_badged" />
<java-symbol type="dimen" name="conversation_icon_size_centered" />
<java-symbol type="dimen" name="conversation_icon_margin_top_centered" />
<java-symbol type="layout" name="notification_template_material_conversation" />
<java-symbol type="layout" name="conversation_face_pile_layout" />
<!-- Intent resolver and share sheet -->
<java-symbol type="color" name="resolver_tabs_active_color" />
<java-symbol type="color" name="resolver_tabs_inactive_color" />

View File

@@ -124,9 +124,6 @@
<!-- Increased height of a collapsed media notification in the status bar -->
<dimen name="notification_min_height_media">160dp</dimen>
<!-- Increased height of a collapsed messaging notification in the status bar -->
<dimen name="notification_min_height_messaging">118dp</dimen>
<!-- Height of a small notification in the status bar which was used before android N -->
<dimen name="notification_min_height_legacy">64dp</dimen>

View File

@@ -23,6 +23,7 @@ import android.graphics.drawable.Icon;
import android.text.TextUtils;
import android.view.NotificationHeaderView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
@@ -182,24 +183,24 @@ public class NotificationHeaderUtil {
private void sanitizeChild(View child) {
if (child != null) {
NotificationHeaderView header = (NotificationHeaderView) child.findViewById(
ViewGroup header = child.findViewById(
com.android.internal.R.id.notification_header);
sanitizeHeader(header);
}
}
private void sanitizeHeader(NotificationHeaderView rowHeader) {
private void sanitizeHeader(ViewGroup rowHeader) {
if (rowHeader == null) {
return;
}
final int childCount = rowHeader.getChildCount();
View time = rowHeader.findViewById(com.android.internal.R.id.time);
boolean hasVisibleText = false;
for (int i = 1; i < childCount - 1 ; i++) {
for (int i = 0; i < childCount; i++) {
View child = rowHeader.getChildAt(i);
if (child instanceof TextView
&& child.getVisibility() != View.GONE
&& !mDividers.contains(Integer.valueOf(child.getId()))
&& !mDividers.contains(child.getId())
&& child != time) {
hasVisibleText = true;
break;
@@ -212,14 +213,14 @@ public class NotificationHeaderUtil {
time.setVisibility(timeVisibility);
View left = null;
View right;
for (int i = 1; i < childCount - 1 ; i++) {
for (int i = 0; i < childCount; i++) {
View child = rowHeader.getChildAt(i);
if (mDividers.contains(Integer.valueOf(child.getId()))) {
if (mDividers.contains(child.getId())) {
boolean visible = false;
// Lets find the item to the right
for (i++; i < childCount - 1; i++) {
for (i++; i < childCount; i++) {
right = rowHeader.getChildAt(i);
if (mDividers.contains(Integer.valueOf(right.getId()))) {
if (mDividers.contains(right.getId())) {
// A divider was found, this needs to be hidden
i--;
break;
@@ -276,14 +277,18 @@ public class NotificationHeaderUtil {
if (!mApply) {
return;
}
NotificationHeaderView header = row.getContractedNotificationHeader();
if (header == null) {
// No header found. We still consider this to be the same to avoid weird flickering
View contractedChild = row.getPrivateLayout().getContractedChild();
if (contractedChild == null) {
return;
}
View ownView = contractedChild.findViewById(mId);
if (ownView == null) {
// No view found. We still consider this to be the same to avoid weird flickering
// when for example showing an undo notification
return;
}
Object childData = mExtractor == null ? null : mExtractor.extractData(row);
mApply = mComparator.compare(mParentView, header.findViewById(mId),
mApply = mComparator.compare(mParentView, ownView,
mParentData, childData);
}

View File

@@ -41,6 +41,7 @@ public class ViewTransformationHelper implements TransformableView,
private static final int TAG_CONTAINS_TRANSFORMED_VIEW = R.id.contains_transformed_view;
private ArrayMap<Integer, View> mTransformedViews = new ArrayMap<>();
private ArraySet<Integer> mKeysTransformingToSimilar = new ArraySet<>();
private ArrayMap<Integer, CustomTransformation> mCustomTransformations = new ArrayMap<>();
private ValueAnimator mViewTransformationAnimation;
@@ -48,8 +49,22 @@ public class ViewTransformationHelper implements TransformableView,
mTransformedViews.put(key, transformedView);
}
/**
* Add a view that transforms to a similar sibling, meaning that we should consider any mapping
* found treated as the same viewType. This is useful for imageViews, where it's hard to compare
* if the source images are the same when they are bitmap based.
*
* @param key The key how this is added
* @param transformedView the view that is added
*/
public void addViewTransformingToSimilar(int key, View transformedView) {
addTransformedView(key, transformedView);
mKeysTransformingToSimilar.add(key);
}
public void reset() {
mTransformedViews.clear();
mKeysTransformingToSimilar.clear();
}
public void setCustomTransformation(CustomTransformation transformation, int viewType) {
@@ -60,7 +75,11 @@ public class ViewTransformationHelper implements TransformableView,
public TransformState getCurrentState(int fadingView) {
View view = mTransformedViews.get(fadingView);
if (view != null && view.getVisibility() != View.GONE) {
return TransformState.createFrom(view, this);
TransformState transformState = TransformState.createFrom(view, this);
if (mKeysTransformingToSimilar.contains(fadingView)) {
transformState.setIsSameAsAnyView(true);
}
return transformState;
}
return null;
}

View File

@@ -21,9 +21,9 @@ import android.util.Pools;
import android.view.View;
import android.view.ViewGroup;
import com.android.internal.widget.IMessagingLayout;
import com.android.internal.widget.MessagingGroup;
import com.android.internal.widget.MessagingImageMessage;
import com.android.internal.widget.MessagingLayout;
import com.android.internal.widget.MessagingLinearLayout;
import com.android.internal.widget.MessagingMessage;
import com.android.internal.widget.MessagingPropertyAnimator;
@@ -41,7 +41,7 @@ public class MessagingLayoutTransformState extends TransformState {
private static Pools.SimplePool<MessagingLayoutTransformState> sInstancePool
= new Pools.SimplePool<>(40);
private MessagingLinearLayout mMessageContainer;
private MessagingLayout mMessagingLayout;
private IMessagingLayout mMessagingLayout;
private HashMap<MessagingGroup, MessagingGroup> mGroupMap = new HashMap<>();
private float mRelativeTranslationOffset;
@@ -266,8 +266,9 @@ public class MessagingLayoutTransformState extends TransformState {
transformView(transformationAmount, to, child, otherChild, false, /* sameAsAny */
useLinearTransformation);
boolean otherIsIsolated = otherGroup.getIsolatedMessage() == otherChild;
if (transformationAmount == 0.0f && otherIsIsolated) {
ownGroup.setTransformingImages(true);
if (transformationAmount == 0.0f
&& (otherIsIsolated || otherGroup.isSingleLine())) {
ownGroup.setClippingDisabled(true);
}
if (otherChild == null) {
child.setTranslationY(previousTranslation);
@@ -291,11 +292,20 @@ public class MessagingLayoutTransformState extends TransformState {
if (useLinearTransformation) {
ownState.setDefaultInterpolator(Interpolators.LINEAR);
}
ownState.setIsSameAsAnyView(sameAsAny);
ownState.setIsSameAsAnyView(sameAsAny && !isGone(otherView));
if (to) {
if (otherView != null) {
TransformState otherState = TransformState.createFrom(otherView, mTransformInfo);
ownState.transformViewTo(otherState, transformationAmount);
if (!isGone(otherView)) {
ownState.transformViewTo(otherState, transformationAmount);
} else {
if (!isGone(ownView)) {
ownState.disappear(transformationAmount, null);
}
// We still want to transform vertically if the view is gone,
// since avatars serve as anchors for the rest of the layout transition
ownState.transformViewVerticalTo(otherState, transformationAmount);
}
otherState.recycle();
} else {
ownState.disappear(transformationAmount, null);
@@ -303,7 +313,16 @@ public class MessagingLayoutTransformState extends TransformState {
} else {
if (otherView != null) {
TransformState otherState = TransformState.createFrom(otherView, mTransformInfo);
ownState.transformViewFrom(otherState, transformationAmount);
if (!isGone(otherView)) {
ownState.transformViewFrom(otherState, transformationAmount);
} else {
if (!isGone(ownView)) {
ownState.appear(transformationAmount, null);
}
// We still want to transform vertically if the view is gone,
// since avatars serve as anchors for the rest of the layout transition
ownState.transformViewVerticalFrom(otherState, transformationAmount);
}
otherState.recycle();
} else {
ownState.appear(transformationAmount, null);
@@ -337,6 +356,9 @@ public class MessagingLayoutTransformState extends TransformState {
}
private boolean isGone(View view) {
if (view == null) {
return true;
}
if (view.getVisibility() == View.GONE) {
return true;
}
@@ -408,7 +430,7 @@ public class MessagingLayoutTransformState extends TransformState {
ownGroup.getMessageContainer().setTranslationY(0);
ownGroup.getSenderView().setTranslationY(0);
}
ownGroup.setTransformingImages(false);
ownGroup.setClippingDisabled(false);
ownGroup.updateClipRect();
}
}

View File

@@ -31,7 +31,6 @@ import android.animation.ObjectAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Notification;
import android.app.NotificationChannel;
import android.content.Context;
import android.content.pm.PackageInfo;
@@ -151,7 +150,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private int mNotificationMinHeight;
private int mNotificationMinHeightLarge;
private int mNotificationMinHeightMedia;
private int mNotificationMinHeightMessaging;
private int mNotificationMaxHeight;
private int mIncreasedPaddingBetweenElements;
private int mNotificationLaunchHeight;
@@ -640,16 +638,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
&& expandedView.findViewById(com.android.internal.R.id.media_actions) != null;
boolean showCompactMediaSeekbar = mMediaManager.getShowCompactMediaSeekbar();
Class<? extends Notification.Style> style =
mEntry.getSbn().getNotification().getNotificationStyle();
boolean isMessagingLayout = Notification.MessagingStyle.class.equals(style);
if (customView && beforeP && !mIsSummaryWithChildren) {
minHeight = beforeN ? mNotificationMinHeightBeforeN : mNotificationMinHeightBeforeP;
} else if (isMediaLayout && showCompactMediaSeekbar) {
minHeight = mNotificationMinHeightMedia;
} else if (isMessagingLayout) {
minHeight = mNotificationMinHeightMessaging;
} else if (mUseIncreasedCollapsedHeight && layout == mPrivateLayout) {
minHeight = mNotificationMinHeightLarge;
} else {
@@ -1057,19 +1049,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
return getShowingLayout().getVisibleNotificationHeader();
}
/**
* @return the contracted notification header. This can be different from
* {@link #getNotificationHeader()} and also {@link #getVisibleNotificationHeader()} and only
* returns the contracted version.
*/
public NotificationHeaderView getContractedNotificationHeader() {
if (mIsSummaryWithChildren) {
return mChildrenContainer.getHeaderView();
}
return mPrivateLayout.getContractedNotificationHeader();
}
public void setLongPressListener(LongPressListener longPressListener) {
mLongPressListener = longPressListener;
}
@@ -1654,8 +1633,6 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
R.dimen.notification_min_height_increased);
mNotificationMinHeightMedia = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_min_height_media);
mNotificationMinHeightMessaging = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_min_height_messaging);
mNotificationMaxHeight = NotificationUtils.getFontScaledHeight(mContext,
R.dimen.notification_max_height);
mMaxHeadsUpHeightBeforeN = NotificationUtils.getFontScaledHeight(mContext,

View File

@@ -1492,13 +1492,6 @@ public class NotificationContentView extends FrameLayout {
}
}
public NotificationHeaderView getContractedNotificationHeader() {
if (mContractedChild != null) {
return mContractedWrapper.getNotificationHeader();
}
return null;
}
public NotificationHeaderView getVisibleNotificationHeader() {
NotificationViewWrapper wrapper = getVisibleWrapper(mVisibleType);
return wrapper == null ? null : wrapper.getNotificationHeader();

View File

@@ -0,0 +1,100 @@
/*
* Copyright (C) 2020 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
*/
package com.android.systemui.statusbar.notification.row.wrapper
import android.content.Context
import android.view.View
import com.android.internal.widget.ConversationLayout
import com.android.internal.widget.MessagingLinearLayout
import com.android.systemui.R
import com.android.systemui.statusbar.notification.NotificationUtils
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
/**
* Wraps a notification containing a converation template
*/
class NotificationConversationTemplateViewWrapper constructor(
ctx: Context,
view: View,
row: ExpandableNotificationRow
)
: NotificationTemplateViewWrapper(ctx, view, row) {
private val minHeightWithActions: Int
private val conversationLayout: ConversationLayout
private var conversationIcon: View? = null
private var conversationBadge: View? = null
private var expandButton: View? = null
private var messagingLinearLayout: MessagingLinearLayout? = null
init {
conversationLayout = view as ConversationLayout
minHeightWithActions = NotificationUtils.getFontScaledHeight(ctx,
R.dimen.notification_messaging_actions_min_height)
}
private fun resolveViews() {
messagingLinearLayout = conversationLayout.messagingLinearLayout
conversationIcon = conversationLayout.requireViewById(
com.android.internal.R.id.conversation_icon)
conversationBadge = conversationLayout.requireViewById(
com.android.internal.R.id.conversation_icon_badge)
expandButton = conversationLayout.requireViewById(
com.android.internal.R.id.expand_button)
}
override fun onContentUpdated(row: ExpandableNotificationRow) {
// Reinspect the notification. Before the super call, because the super call also updates
// the transformation types and we need to have our values set by then.
resolveViews()
super.onContentUpdated(row)
}
override fun updateTransformedTypes() {
// This also clears the existing types
super.updateTransformedTypes()
messagingLinearLayout?.let {
mTransformationHelper.addTransformedView(it.id, it)
}
conversationIcon?.let {
mTransformationHelper.addViewTransformingToSimilar(it.id, it)
}
conversationBadge?.let {
mTransformationHelper.addViewTransformingToSimilar(it.id, it)
}
expandButton?.let {
mTransformationHelper.addViewTransformingToSimilar(it.id, it)
}
}
override fun setRemoteInputVisible(visible: Boolean) {
conversationLayout.showHistoricMessages(visible)
}
override fun updateExpandability(expandable: Boolean, onClickListener: View.OnClickListener?) {
conversationLayout.updateExpandability(expandable, onClickListener)
}
override fun getMinLayoutHeight(): Int {
if (mActionsContainer != null && mActionsContainer.visibility != View.GONE) {
return minHeightWithActions
} else {
return super.getMinLayoutHeight()
}
}
}

View File

@@ -53,12 +53,13 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
protected final ViewTransformationHelper mTransformationHelper;
protected int mColor;
private ImageView mIcon;
private ImageView mIcon;
private NotificationExpandButton mExpandButton;
protected NotificationHeaderView mNotificationHeader;
private TextView mHeaderText;
private ImageView mWorkProfileImage;
private boolean mIsLowPriority;
private boolean mTransformLowPriorityTitle;
private boolean mShowExpandButtonAtEnd;
@@ -105,12 +106,16 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
mExpandButton = mView.findViewById(com.android.internal.R.id.expand_button);
mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge);
mNotificationHeader = mView.findViewById(com.android.internal.R.id.notification_header);
mNotificationHeader.setShowExpandButtonAtEnd(mShowExpandButtonAtEnd);
mColor = mNotificationHeader.getOriginalIconColor();
if (mNotificationHeader != null) {
mNotificationHeader.setShowExpandButtonAtEnd(mShowExpandButtonAtEnd);
mColor = mNotificationHeader.getOriginalIconColor();
}
}
private void addAppOpsOnClickListener(ExpandableNotificationRow row) {
mNotificationHeader.setAppOpsOnClickListener(row.getAppOpsOnClickListener());
if (mNotificationHeader != null) {
mNotificationHeader.setAppOpsOnClickListener(row.getAppOpsOnClickListener());
}
}
@Override
@@ -127,9 +132,11 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
updateCropToPaddingForImageViews();
Notification notification = row.getEntry().getSbn().getNotification();
mIcon.setTag(ImageTransformState.ICON_TAG, notification.getSmallIcon());
// The work profile image is always the same lets just set the icon tag for it not to
// animate
mWorkProfileImage.setTag(ImageTransformState.ICON_TAG, notification.getSmallIcon());
if (mWorkProfileImage != null) {
// The work profile image is always the same lets just set the icon tag for it not to
// animate
mWorkProfileImage.setTag(ImageTransformState.ICON_TAG, notification.getSmallIcon());
}
// We need to reset all views that are no longer transforming in case a view was previously
// transformed, but now we decided to transform its container instead.
@@ -174,8 +181,9 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
protected void updateTransformedTypes() {
mTransformationHelper.reset();
mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_ICON, mIcon);
if (mIsLowPriority) {
mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_ICON,
mIcon);
if (mIsLowPriority && mHeaderText != null) {
mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE,
mHeaderText);
}
@@ -184,7 +192,9 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper {
@Override
public void updateExpandability(boolean expandable, View.OnClickListener onClickListener) {
mExpandButton.setVisibility(expandable ? View.VISIBLE : View.GONE);
mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
if (mNotificationHeader != null) {
mNotificationHeader.setOnClickListener(expandable ? onClickListener : null);
}
}
@Override

View File

@@ -353,8 +353,12 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp
@Override
public void setHeaderVisibleAmount(float headerVisibleAmount) {
super.setHeaderVisibleAmount(headerVisibleAmount);
mNotificationHeader.setAlpha(headerVisibleAmount);
mHeaderTranslation = (1.0f - headerVisibleAmount) * mFullHeaderTranslation;
float headerTranslation = 0f;
if (mNotificationHeader != null) {
mNotificationHeader.setAlpha(headerVisibleAmount);
headerTranslation = (1.0f - headerVisibleAmount) * mFullHeaderTranslation;
}
mHeaderTranslation = headerTranslation;
mView.setTranslationY(mHeaderTranslation);
}

View File

@@ -35,6 +35,7 @@ import android.widget.TextView;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
import com.android.internal.util.ContrastColorUtil;
import com.android.internal.widget.ConversationLayout;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.notification.TransformState;
@@ -61,6 +62,9 @@ public abstract class NotificationViewWrapper implements TransformableView {
return new NotificationMediaTemplateViewWrapper(ctx, v, row);
} else if ("messaging".equals(v.getTag())) {
return new NotificationMessagingTemplateViewWrapper(ctx, v, row);
} else if ("conversation".equals(v.getTag())) {
return new NotificationConversationTemplateViewWrapper(ctx, (ConversationLayout) v,
row);
}
Class<? extends Notification.Style> style =
row.getEntry().getSbn().getNotification().getNotificationStyle();