Merge changes I69b33b0c,If904c85e,I28444df4,I3bfa4580,I88d9667d, ... into rvc-dev am: 5f003f4e10
Change-Id: I161d20abc58cc948a1243aa5c528a9b5197e311b
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
866
core/java/com/android/internal/widget/ConversationLayout.java
Normal file
866
core/java/com/android/internal/widget/ConversationLayout.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
42
core/java/com/android/internal/widget/IMessagingLayout.java
Normal file
42
core/java/com/android/internal/widget/IMessagingLayout.java
Normal 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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
28
core/res/res/drawable/conversation_badge_background.xml
Normal file
28
core/res/res/drawable/conversation_badge_background.xml
Normal 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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
45
core/res/res/layout/conversation_face_pile_layout.xml
Normal file
45
core/res/res/layout/conversation_face_pile_layout.xml
Normal 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>
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user