Redesigned the messaging style
The layout now looks much more recognizable
as a messaging template and enables us to
prepare for more useful functionality.
Test: Send messages and observe display
Bug: 63708826
Change-Id: I896b3692a1e84976e8fd37cf37611ddb1d358fb9
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8226e0f..f0e09b7 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -22,6 +22,7 @@
import android.annotation.DrawableRes;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
@@ -3900,7 +3901,7 @@
final Bundle ex = mN.extras;
updateBackgroundColor(contentView);
bindNotificationHeader(contentView, p.ambient);
- bindLargeIcon(contentView);
+ bindLargeIcon(contentView, p.hideLargeIcon, p.alwaysShowReply);
boolean showProgress = handleProgressBar(p.hasProgress, contentView, ex);
if (p.title != null) {
contentView.setViewVisibility(R.id.title, View.VISIBLE);
@@ -4110,11 +4111,13 @@
}
}
- private void bindLargeIcon(RemoteViews contentView) {
+ private void bindLargeIcon(RemoteViews contentView, boolean hideLargeIcon,
+ boolean alwaysShowReply) {
if (mN.mLargeIcon == null && mN.largeIcon != null) {
mN.mLargeIcon = Icon.createWithBitmap(mN.largeIcon);
}
- if (mN.mLargeIcon != null) {
+ boolean showLargeIcon = mN.mLargeIcon != null && !hideLargeIcon;
+ if (showLargeIcon) {
contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
contentView.setImageViewIcon(R.id.right_icon, mN.mLargeIcon);
processLargeLegacyIcon(mN.mLargeIcon, contentView);
@@ -4122,32 +4125,45 @@
contentView.setViewLayoutMarginEndDimen(R.id.line1, endMargin);
contentView.setViewLayoutMarginEndDimen(R.id.text, endMargin);
contentView.setViewLayoutMarginEndDimen(R.id.progress, endMargin);
- // Bind the reply action
- Action action = findReplyAction();
- contentView.setViewVisibility(R.id.reply_icon_action, action != null
- ? View.VISIBLE
- : View.GONE);
+ }
+ // Bind the reply action
+ Action action = findReplyAction();
- if (action != null) {
- int contrastColor = resolveContrastColor();
+ boolean actionVisible = action != null && (showLargeIcon || alwaysShowReply);
+ int replyId = showLargeIcon ? R.id.reply_icon_action : R.id.right_icon;
+ if (actionVisible) {
+ // We're only showing the icon as big if we're hiding the large icon
+ int contrastColor = resolveContrastColor();
+ int iconColor;
+ if (showLargeIcon) {
contentView.setDrawableTint(R.id.reply_icon_action,
true /* targetBackground */,
contrastColor, PorterDuff.Mode.SRC_ATOP);
- int iconColor = NotificationColorUtil.isColorLight(contrastColor)
- ? Color.BLACK : Color.WHITE;
- contentView.setDrawableTint(R.id.reply_icon_action,
- false /* targetBackground */,
- iconColor, PorterDuff.Mode.SRC_ATOP);
contentView.setOnClickPendingIntent(R.id.right_icon,
action.actionIntent);
- contentView.setOnClickPendingIntent(R.id.reply_icon_action,
- action.actionIntent);
contentView.setRemoteInputs(R.id.right_icon, action.mRemoteInputs);
- contentView.setRemoteInputs(R.id.reply_icon_action, action.mRemoteInputs);
-
+ iconColor = NotificationColorUtil.isColorLight(contrastColor)
+ ? Color.BLACK : Color.WHITE;
+ } else {
+ contentView.setImageViewResource(R.id.right_icon,
+ R.drawable.ic_reply_notification_large);
+ contentView.setViewVisibility(R.id.right_icon, View.VISIBLE);
+ iconColor = contrastColor;
}
+ contentView.setDrawableTint(replyId,
+ false /* targetBackground */,
+ iconColor,
+ PorterDuff.Mode.SRC_ATOP);
+ contentView.setOnClickPendingIntent(replyId,
+ action.actionIntent);
+ contentView.setRemoteInputs(replyId, action.mRemoteInputs);
+ } else {
+ contentView.setRemoteInputs(R.id.right_icon, null);
}
- contentView.setViewVisibility(R.id.right_icon_container, mN.mLargeIcon != null
+ contentView.setViewVisibility(R.id.reply_icon_action, actionVisible && showLargeIcon
+ ? View.VISIBLE
+ : View.GONE);
+ contentView.setViewVisibility(R.id.right_icon_container, actionVisible || showLargeIcon
? View.VISIBLE
: View.GONE);
}
@@ -6055,18 +6071,12 @@
protected void restoreFromExtras(Bundle extras) {
super.restoreFromExtras(extras);
- mMessages.clear();
- mHistoricMessages.clear();
mUserDisplayName = extras.getCharSequence(EXTRA_SELF_DISPLAY_NAME);
mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
- if (messages != null && messages instanceof Parcelable[]) {
- mMessages = Message.getMessagesFromBundleArray(messages);
- }
+ mMessages = Message.getMessagesFromBundleArray(messages);
Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
- if (histMessages != null && histMessages instanceof Parcelable[]) {
- mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
- }
+ mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
}
/**
@@ -6096,16 +6106,25 @@
}
private Message findLatestIncomingMessage() {
- for (int i = mMessages.size() - 1; i >= 0; i--) {
- Message m = mMessages.get(i);
+ return findLatestIncomingMessage(mMessages);
+ }
+
+ /**
+ * @hide
+ */
+ @Nullable
+ public static Message findLatestIncomingMessage(
+ List<Message> messages) {
+ for (int i = messages.size() - 1; i >= 0; i--) {
+ Message m = messages.get(i);
// Incoming messages have a non-empty sender.
if (!TextUtils.isEmpty(m.mSender)) {
return m;
}
}
- if (!mMessages.isEmpty()) {
+ if (!messages.isEmpty()) {
// No incoming messages, fall back to outgoing message
- return mMessages.get(mMessages.size() - 1);
+ return messages.get(messages.size() - 1);
}
return null;
}
@@ -6115,95 +6134,57 @@
*/
@Override
public RemoteViews makeBigContentView() {
- CharSequence title = !TextUtils.isEmpty(super.mBigContentTitle)
+ CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
? super.mBigContentTitle
: mConversationTitle;
- boolean hasTitle = !TextUtils.isEmpty(title);
-
- if (mMessages.size() == 1) {
- // Special case for a single message: Use the big text style
- // so the collapsed and expanded versions match nicely.
- CharSequence bigTitle;
- CharSequence text;
- if (hasTitle) {
- bigTitle = title;
- text = makeMessageLine(mMessages.get(0), mBuilder);
- } else {
- bigTitle = mMessages.get(0).mSender;
- text = mMessages.get(0).mText;
- }
- RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
- mBuilder.getBigTextLayoutResource(),
- mBuilder.mParams.reset().hasProgress(false).title(bigTitle).text(null));
- BigTextStyle.applyBigTextContentView(mBuilder, contentView, text);
- return contentView;
+ boolean isOneToOne = TextUtils.isEmpty(conversationTitle);
+ if (isOneToOne) {
+ // Let's add the conversationTitle in case we didn't have one before and all
+ // messages are from the same sender
+ conversationTitle = createConversationTitleFromMessages();
}
-
+ boolean hasTitle = !TextUtils.isEmpty(conversationTitle);
RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
mBuilder.getMessagingLayoutResource(),
- mBuilder.mParams.reset().hasProgress(false).title(title).text(null));
-
- int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
- R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
-
- // Make sure all rows are gone in case we reuse a view.
- for (int rowId : rowIds) {
- contentView.setViewVisibility(rowId, View.GONE);
- }
-
- int i=0;
+ mBuilder.mParams.reset().hasProgress(false).title(conversationTitle).text(null)
+ .hideLargeIcon(isOneToOne).alwaysShowReply(true));
contentView.setViewLayoutMarginBottomDimen(R.id.line1,
hasTitle ? R.dimen.notification_messaging_spacing : 0);
contentView.setInt(R.id.notification_messaging, "setNumIndentLines",
!mBuilder.mN.hasLargeIcon() ? 0 : (hasTitle ? 1 : 2));
-
- int contractedChildId = View.NO_ID;
- Message contractedMessage = findLatestIncomingMessage();
- int firstHistoricMessage = Math.max(0, mHistoricMessages.size()
- - (rowIds.length - mMessages.size()));
- while (firstHistoricMessage + i < mHistoricMessages.size() && i < rowIds.length) {
- Message m = mHistoricMessages.get(firstHistoricMessage + i);
- int rowId = rowIds[i];
-
- contentView.setTextViewText(rowId, makeMessageLine(m, mBuilder));
-
- if (contractedMessage == m) {
- contractedChildId = rowId;
- }
-
- i++;
- }
-
- int firstMessage = Math.max(0, mMessages.size() - rowIds.length);
- while (firstMessage + i < mMessages.size() && i < rowIds.length) {
- Message m = mMessages.get(firstMessage + i);
- int rowId = rowIds[i];
-
- contentView.setViewVisibility(rowId, View.VISIBLE);
- contentView.setTextViewText(rowId, mBuilder.processTextSpans(
- makeMessageLine(m, mBuilder)));
- mBuilder.setTextViewColorSecondary(contentView, rowId);
-
- if (contractedMessage == m) {
- contractedChildId = rowId;
- }
-
- i++;
- }
- // Clear the remaining views for reapply. Ensures that historic message views can
- // reliably be identified as being GONE and having non-null text.
- while (i < rowIds.length) {
- int rowId = rowIds[i];
- contentView.setTextViewText(rowId, null);
- i++;
- }
-
- // Record this here to allow transformation between the contracted and expanded views.
- contentView.setInt(R.id.notification_messaging, "setContractedChildId",
- contractedChildId);
+ addExtras(mBuilder.mN.extras);
+ contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
+ mBuilder.resolveContrastColor());
+ contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
+ mBuilder.mN.mLargeIcon);
+ contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsOneToOne",
+ isOneToOne);
+ contentView.setBundle(R.id.status_bar_latest_event_content, "setData",
+ mBuilder.mN.extras);
return contentView;
}
+ private CharSequence createConversationTitleFromMessages() {
+ ArraySet<CharSequence> names = new ArraySet<>();
+ for (int i = 0; i < mMessages.size(); i++) {
+ Message m = mMessages.get(i);
+ CharSequence sender = m.getSender();
+ if (sender != null) {
+ names.add(sender);
+ }
+ }
+ SpannableStringBuilder title = new SpannableStringBuilder();
+ int size = names.size();
+ for (int i = 0; i < size; i++) {
+ CharSequence name = names.valueAt(i);
+ if (!TextUtils.isEmpty(title)) {
+ title.append(", ");
+ }
+ title.append(BidiFormatter.getInstance().unicodeWrap(name));
+ }
+ return title;
+ }
+
private CharSequence makeMessageLine(Message m, Builder builder) {
BidiFormatter bidi = BidiFormatter.getInstance();
SpannableStringBuilder sb = new SpannableStringBuilder();
@@ -6394,7 +6375,15 @@
return bundles;
}
- static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) {
+ /**
+ * @return A list of messages read from the bundles.
+ *
+ * @hide
+ */
+ public static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) {
+ if (bundles == null) {
+ return new ArrayList<>();
+ }
List<Message> messages = new ArrayList<>(bundles.length);
for (int i = 0; i < bundles.length; i++) {
if (bundles[i] instanceof Bundle) {
@@ -8487,6 +8476,8 @@
boolean ambient = false;
CharSequence title;
CharSequence text;
+ boolean hideLargeIcon;
+ public boolean alwaysShowReply;
final StandardTemplateParams reset() {
hasProgress = true;
@@ -8511,6 +8502,16 @@
return this;
}
+ final StandardTemplateParams alwaysShowReply(boolean alwaysShowReply) {
+ this.alwaysShowReply = alwaysShowReply;
+ return this;
+ }
+
+ final StandardTemplateParams hideLargeIcon(boolean hideLargeIcon) {
+ this.hideLargeIcon = hideLargeIcon;
+ return this;
+ }
+
final StandardTemplateParams ambient(boolean ambient) {
Preconditions.checkState(title == null && text == null, "must set ambient before text");
this.ambient = ambient;
@@ -8527,7 +8528,6 @@
text = extras.getCharSequence(EXTRA_TEXT);
}
this.text = b.processLegacyText(text, ambient);
-
return this;
}
}
diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java
index 7870333..09f7282 100644
--- a/core/java/com/android/internal/widget/ImageFloatingTextView.java
+++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java
@@ -176,8 +176,4 @@
}
return false;
}
-
- public int getLayoutHeight() {
- return getLayout().getHeight();
- }
}
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
new file mode 100644
index 0000000..ae08f87
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2017 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.content.Context;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuffColorFilter;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.util.AttributeSet;
+import android.util.Pools;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+
+import com.android.internal.R;
+import com.android.internal.util.NotificationColorUtil;
+
+import java.util.List;
+
+/**
+ * A message of a {@link MessagingLayout}.
+ */
+@RemoteViews.RemoteView
+public class MessagingGroup extends LinearLayout implements MessagingLinearLayout.MessagingChild {
+
+ private static Pools.SimplePool<MessagingGroup> sInstancePool
+ = new Pools.SynchronizedPool<>(10);
+ private MessagingLinearLayout mMessageContainer;
+ private ImageFloatingTextView mSenderName;
+ private ImageView mAvatarView;
+ private String mAvatarSymbol = "";
+ private int mLayoutColor;
+ private CharSequence mAvatarName = "";
+ private Icon mAvatarIcon;
+ private ColorFilter mMessageBackgroundFilter;
+ private int mTextColor;
+
+ public MessagingGroup(@NonNull Context context) {
+ super(context);
+ }
+
+ public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public MessagingGroup(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mMessageContainer = findViewById(R.id.group_message_container);
+ mSenderName = findViewById(R.id.message_name);
+ mAvatarView = findViewById(R.id.message_icon);
+ }
+
+ public void setSender(CharSequence sender) {
+ if (sender == null) {
+ mAvatarView.setVisibility(GONE);
+ mSenderName.setVisibility(GONE);
+ setGravity(Gravity.END);
+ mMessageBackgroundFilter = new PorterDuffColorFilter(mLayoutColor,
+ PorterDuff.Mode.SRC_ATOP);
+ mTextColor = NotificationColorUtil.isColorLight(mLayoutColor) ? getNormalTextColor()
+ : Color.WHITE;
+ } else {
+ mSenderName.setText(sender);
+ mAvatarView.setVisibility(VISIBLE);
+ mSenderName.setVisibility(VISIBLE);
+ setGravity(Gravity.START);
+ mMessageBackgroundFilter = null;
+ mTextColor = getNormalTextColor();
+ }
+ }
+
+ private int getNormalTextColor() {
+ return mContext.getColor(R.color.notification_primary_text_color_light);
+ }
+
+ public void setAvatar(Icon icon) {
+ mAvatarIcon = icon;
+ mAvatarView.setImageIcon(icon);
+ mAvatarSymbol = "";
+ mLayoutColor = 0;
+ mAvatarName = "";
+ }
+
+ static MessagingGroup createGroup(MessagingLinearLayout layout) {;
+ MessagingGroup createdGroup = sInstancePool.acquire();
+ if (createdGroup == null) {
+ createdGroup = (MessagingGroup) LayoutInflater.from(layout.getContext()).inflate(
+ R.layout.notification_template_messaging_group, layout,
+ false);
+ }
+ layout.addView(createdGroup);
+ return createdGroup;
+ }
+
+ public void removeMessage(MessagingMessage messagingMessage) {
+ // TODO: add removal animation
+ mMessageContainer.removeView(messagingMessage);
+ if (mMessageContainer.getChildCount() == 0) {
+ ViewParent parent = getParent();
+ if (parent instanceof ViewGroup) {
+ ((ViewGroup) parent).removeView(this);
+ }
+ setAvatar(null);
+ sInstancePool.release(this);
+ }
+ }
+
+ public CharSequence getSenderName() {
+ return mSenderName.getText();
+ }
+
+ public void setSenderVisible(boolean visible) {
+ mSenderName.setVisibility(visible ? VISIBLE : GONE);
+ }
+
+ public static void dropCache() {
+ sInstancePool = new Pools.SynchronizedPool<>(10);
+ }
+
+ @Override
+ public int getMeasuredType() {
+ boolean hasNormal = false;
+ for (int i = mMessageContainer.getChildCount() - 1; i >= 0; i--) {
+ View child = mMessageContainer.getChildAt(i);
+ if (child instanceof MessagingLinearLayout.MessagingChild) {
+ int type = ((MessagingLinearLayout.MessagingChild) child).getMeasuredType();
+ if (type == MEASURED_TOO_SMALL) {
+ if (hasNormal) {
+ return MEASURED_SHORTENED;
+ } else {
+ return MEASURED_TOO_SMALL;
+ }
+ } else if (type == MEASURED_SHORTENED) {
+ return MEASURED_SHORTENED;
+ } else {
+ hasNormal = true;
+ }
+ }
+ }
+ return MEASURED_NORMAL;
+ }
+
+ @Override
+ public int getConsumedLines() {
+ int result = 0;
+ for (int i = 0; i < mMessageContainer.getChildCount(); i++) {
+ View child = mMessageContainer.getChildAt(i);
+ if (child instanceof MessagingLinearLayout.MessagingChild) {
+ result += ((MessagingLinearLayout.MessagingChild) child).getConsumedLines();
+ }
+ }
+ return result;
+ }
+
+ public Icon getAvatarSymbolIfMatching(CharSequence avatarName, String avatarSymbol,
+ int layoutColor) {
+ if (mAvatarName.equals(avatarName) && mAvatarSymbol.equals(avatarSymbol)
+ && layoutColor == mLayoutColor) {
+ return mAvatarIcon;
+ }
+ return null;
+ }
+
+ public void setCreatedAvatar(Icon cachedIcon, CharSequence avatarName, String avatarSymbol,
+ int layoutColor) {
+ if (!mAvatarName.equals(avatarName) || !mAvatarSymbol.equals(avatarSymbol)
+ || layoutColor != mLayoutColor) {
+ setAvatar(cachedIcon);
+ mAvatarSymbol = avatarSymbol;
+ mLayoutColor = layoutColor;
+ mAvatarName = avatarName;
+ }
+ }
+
+ public void setLayoutColor(int layoutColor) {
+ mLayoutColor = layoutColor;
+ }
+
+ public void setMessages(List<MessagingMessage> group) {
+ // Let's now make sure all children are added and in the correct order
+ for (int messageIndex = 0; messageIndex < group.size(); messageIndex++) {
+ MessagingMessage message = group.get(messageIndex);
+ if (message.getGroup() != this) {
+ message.setMessagingGroup(this);
+ ViewParent parent = mMessageContainer.getParent();
+ if (parent instanceof ViewGroup) {
+ ((ViewGroup) parent).removeView(message);
+ }
+ mMessageContainer.addView(message, messageIndex);
+ }
+ if (messageIndex != mMessageContainer.indexOfChild(message)) {
+ mMessageContainer.removeView(message);
+ mMessageContainer.addView(message, messageIndex);
+ }
+ // Let's make sure the message color is correct
+ Drawable targetDrawable = message.getBackground();
+
+ if (targetDrawable != null) {
+ targetDrawable.mutate().setColorFilter(mMessageBackgroundFilter);
+ }
+ message.setTextColor(mTextColor);
+ }
+ }
+}
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
new file mode 100644
index 0000000..4fd7e9b
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -0,0 +1,397 @@
+/*
+ * Copyright (C) 2017 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.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+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.view.RemotableViewMethod;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.RemoteViews;
+import android.widget.TextView;
+
+import com.android.internal.R;
+import com.android.internal.graphics.ColorUtils;
+import com.android.internal.util.NotificationColorUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A custom-built layout for the Notification.MessagingStyle allows dynamic addition and removal
+ * messages and adapts the layout accordingly.
+ */
+@RemoteViews.RemoteView
+public class MessagingLayout extends FrameLayout {
+
+ private static final float COLOR_SHIFT_AMOUNT = 60;
+ private static final Consumer<MessagingMessage> REMOVE_MESSAGE
+ = MessagingMessage::removeMessage;
+ private List<MessagingMessage> mMessages = new ArrayList<>();
+ private List<MessagingMessage> mHistoricMessages = new ArrayList<>();
+ private MessagingLinearLayout mMessagingLinearLayout;
+ private View mContractedMessage;
+ private boolean mShowHistoricMessages;
+ private ArrayList<MessagingGroup> mGroups = new ArrayList<>();
+ private TextView mTitleView;
+ private int mLayoutColor;
+ private int mAvatarSize;
+ private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private Paint mTextPaint = new Paint();
+ private CharSequence mConversationTitle;
+ private Icon mLargeIcon;
+ private boolean mIsOneToOne;
+
+ public MessagingLayout(@NonNull Context context) {
+ super(context);
+ }
+
+ public MessagingLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MessagingLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public MessagingLayout(@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);
+ mTitleView = findViewById(R.id.title);
+ mAvatarSize = getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size);
+ mTextPaint.setTextAlign(Paint.Align.CENTER);
+ mTextPaint.setAntiAlias(true);
+ }
+
+ @RemotableViewMethod
+ public void setLargeIcon(Icon icon) {
+ mLargeIcon = icon;
+ }
+
+ @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);
+ mConversationTitle = null;
+ TextView headerText = findViewById(R.id.header_text);
+ if (headerText != null) {
+ mConversationTitle = headerText.getText();
+ }
+ bind(newMessages, newHistoricMessages);
+ }
+
+ private void bind(List<Notification.MessagingStyle.Message> newMessages,
+ List<Notification.MessagingStyle.Message> newHistoricMessages) {
+
+ List<MessagingMessage> historicMessages = createMessages(newHistoricMessages,
+ true /* isHistoric */);
+ List<MessagingMessage> messages = createMessages(newMessages, false /* isHistoric */);
+ addMessagesToGroups(historicMessages, messages);
+
+ // Let's remove the remaining messages
+ mMessages.forEach(REMOVE_MESSAGE);
+ mHistoricMessages.forEach(REMOVE_MESSAGE);
+
+ mMessages = messages;
+ mHistoricMessages = historicMessages;
+
+ updateContractedMessage();
+ updateHistoricMessageVisibility();
+ updateTitleAndNamesDisplay();
+ }
+
+ 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 (TextUtils.isEmpty(senderName)) {
+ continue;
+ }
+ boolean visible = !mIsOneToOne;
+ group.setSenderVisible(visible);
+ if ((visible || mLargeIcon == null) && !uniqueNames.containsKey(senderName)) {
+ char c = senderName.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, senderName);
+ }
+ }
+ }
+
+ // 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);
+ CharSequence senderName = group.getSenderName();
+ if (TextUtils.isEmpty(senderName) || (mIsOneToOne && mLargeIcon != null)) {
+ 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 (TextUtils.isEmpty(senderName)) {
+ continue;
+ }
+ if (mIsOneToOne && mLargeIcon != null) {
+ group.setAvatar(mLargeIcon);
+ } 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);
+ }
+ }
+ }
+
+ public Icon createAvatarSymbol(CharSequence senderName, String symbol, int layoutColor) {
+ 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.75f : mAvatarSize * 0.4f);
+ 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 = NotificationColorUtil.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 NotificationColorUtil.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;
+ }
+
+ private void addMessagesToGroups(List<MessagingMessage> historicMessages,
+ List<MessagingMessage> messages) {
+ // Let's first find our groups!
+ List<List<MessagingMessage>> groups = new ArrayList<>();
+ List<CharSequence> senders = new ArrayList<>();
+
+ // Lets first find the groups
+ findGroups(historicMessages, messages, groups, senders);
+
+ // Let's now create the views and reorder them accordingly
+ createGroupViews(groups, senders);
+ }
+
+ private void createGroupViews(List<List<MessagingMessage>> groups, List<CharSequence> senders) {
+ 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;
+ }
+ }
+ if (newGroup == null) {
+ newGroup = MessagingGroup.createGroup(mMessagingLinearLayout);
+ }
+ newGroup.setLayoutColor(mLayoutColor);
+ newGroup.setSender(senders.get(groupIndex));
+ mGroups.add(newGroup);
+
+ 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<CharSequence> senders) {
+ CharSequence currentSender = 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;
+ CharSequence sender = message.getMessage().getSender();
+ isNewGroup |= !TextUtils.equals(sender, currentSender);
+ if (isNewGroup) {
+ currentGroup = new ArrayList<>();
+ groups.add(currentGroup);
+ senders.add(sender);
+ currentSender = sender;
+ }
+ currentGroup.add(message);
+ }
+ }
+
+ private void updateContractedMessage() {
+ for (int i = mMessages.size() - 1; i >= 0; i--) {
+ MessagingMessage m = mMessages.get(i);
+ // Incoming messages have a non-empty sender.
+ if (!TextUtils.isEmpty(m.getMessage().getSender())) {
+ mContractedMessage = m;
+ return;
+ }
+ }
+ if (!mMessages.isEmpty()) {
+ // No incoming messages, fall back to outgoing message
+ mContractedMessage = mMessages.get(mMessages.size() - 1);
+ return;
+ }
+ mContractedMessage = null;
+ }
+
+ /**
+ * 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);
+ }
+ 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() {
+ for (int i = 0; i < mHistoricMessages.size(); i++) {
+ MessagingMessage existing = mHistoricMessages.get(i);
+ existing.setVisibility(mShowHistoricMessages ? VISIBLE : GONE);
+ }
+ }
+
+ public View getContractedMessage() {
+ return mContractedMessage;
+ }
+
+ public MessagingLinearLayout getMessagingLinearLayout() {
+ return mMessagingLinearLayout;
+ }
+}
diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java
index 70473a0..e050d45 100644
--- a/core/java/com/android/internal/widget/MessagingLinearLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java
@@ -79,7 +79,6 @@
a.recycle();
}
-
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// This is essentially a bottom-up linear layout that only adds children that fit entirely
@@ -95,7 +94,10 @@
|| getMeasuredHeight() != targetHeight
|| mLastMeasuredWidth != widthSize;
+ // Now that we know which views to take, fix up the indents and see what width we get.
+ int measuredWidth = mPaddingLeft + mPaddingRight;
final int count = getChildCount();
+ int totalHeight = getMeasuredHeight();
if (recalculateVisibility) {
// We only need to recalculate the view visibilities if the view wasn't measured already
// in this pass, otherwise we may drop messages here already since we are measured
@@ -107,7 +109,7 @@
lp.hide = true;
}
- int totalHeight = mPaddingTop + mPaddingBottom;
+ totalHeight = mPaddingTop + mPaddingBottom;
boolean first = true;
// Starting from the bottom: we measure every view as if it were the only one. If it still
@@ -119,14 +121,6 @@
}
final View child = getChildAt(i);
LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
- ImageFloatingTextView textChild = null;
- if (child instanceof ImageFloatingTextView) {
- // Pretend we need the image padding for all views, we don't know which
- // one will end up needing to do this (might end up not using all the space,
- // but calculating this exactly would be more expensive).
- textChild = (ImageFloatingTextView) child;
- textChild.setNumIndentLines(mIndentLines == 2 ? 3 : mIndentLines);
- }
int spacing = first ? 0 : mSpacing;
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight
@@ -136,67 +130,28 @@
int newHeight = Math.max(totalHeight, totalHeight + childHeight + lp.topMargin +
lp.bottomMargin + spacing);
first = false;
- boolean measuredTooSmall = false;
- if (textChild != null) {
- measuredTooSmall = childHeight < textChild.getLayoutHeight()
- + textChild.getPaddingTop() + textChild.getPaddingBottom();
+ int measureType = MessagingChild.MEASURED_NORMAL;
+ if (child instanceof MessagingChild) {
+ measureType = ((MessagingChild) child).getMeasuredType();
+ linesRemaining -= messagingChild.getConsumedLines();
}
-
- if (newHeight <= targetHeight && !measuredTooSmall) {
+ boolean isShortened = measureType == MessagingChild.MEASURED_SHORTENED;
+ boolean isTooSmall = measureType == MessagingChild.MEASURED_TOO_SMALL;
+ if (newHeight <= targetHeight && !isTooSmall) {
totalHeight = newHeight;
+ measuredWidth = Math.max(measuredWidth,
+ child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin
+ + mPaddingLeft + mPaddingRight);
lp.hide = false;
+ if (isShortened) {
+ break;
+ }
} else {
break;
}
}
}
- // Now that we know which views to take, fix up the indents and see what width we get.
- int measuredWidth = mPaddingLeft + mPaddingRight;
- int imageLines = mIndentLines;
- // Need to redo the height because it may change due to changing indents.
- int totalHeight = mPaddingTop + mPaddingBottom;
- boolean first = true;
- for (int i = 0; i < count; i++) {
- final View child = getChildAt(i);
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
- if (child.getVisibility() == GONE || lp.hide) {
- continue;
- }
-
- if (child instanceof ImageFloatingTextView) {
- ImageFloatingTextView textChild = (ImageFloatingTextView) child;
- if (imageLines == 2 && textChild.getLineCount() > 2) {
- // HACK: If we need indent for two lines, and they're coming from the same
- // view, we need extra spacing to compensate for the lack of margins,
- // so add an extra line of indent.
- imageLines = 3;
- }
- boolean changed = textChild.setNumIndentLines(Math.max(0, imageLines));
- if (changed || !recalculateVisibility) {
- final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
- mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
- lp.width);
- // we want to measure it at most as high as it is currently, otherwise we'll
- // drop later lines
- final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
- targetHeight - child.getMeasuredHeight(), lp.height);
-
- child.measure(childWidthMeasureSpec, childHeightMeasureSpec);;
- }
- imageLines -= textChild.getLineCount();
- }
-
- measuredWidth = Math.max(measuredWidth,
- child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin
- + mPaddingLeft + mPaddingRight);
- totalHeight = Math.max(totalHeight, totalHeight + child.getMeasuredHeight() +
- lp.topMargin + lp.bottomMargin + (first ? 0 : mSpacing));
- first = false;
- }
-
-
setMeasuredDimension(
resolveSize(Math.max(getSuggestedMinimumWidth(), measuredWidth),
widthMeasureSpec),
@@ -287,23 +242,18 @@
* Sets how many lines should be indented to avoid a floating image.
*/
@RemotableViewMethod
- public void setNumIndentLines(int numberLines) {
+ public boolean setNumIndentLines(int numberLines) {
+ boolean changed = numberLines != mIndentLines;
mIndentLines = numberLines;
+ return changed;
}
- /**
- * Set id of the child that's also visible in the contracted layout.
- */
- @RemotableViewMethod
- public void setContractedChildId(int contractedChildId) {
- mContractedChildId = contractedChildId;
- }
-
- /**
- * Get id of the child that's also visible in the contracted layout.
- */
- public int getContractedChildId() {
- return mContractedChildId;
+ public interface MessagingChild {
+ int MEASURED_NORMAL = 0;
+ int MEASURED_SHORTENED = 1;
+ int MEASURED_TOO_SMALL = 2;
+ int getMeasuredType();
+ int getConsumedLines();
}
public static class LayoutParams extends MarginLayoutParams {
diff --git a/core/java/com/android/internal/widget/MessagingMessage.java b/core/java/com/android/internal/widget/MessagingMessage.java
new file mode 100644
index 0000000..bedacf9
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingMessage.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2017 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.content.Context;
+import android.text.Layout;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Pools;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.RemoteViews;
+
+import com.android.internal.R;
+
+import java.util.Objects;
+
+/**
+ * A message of a {@link MessagingLayout}.
+ */
+@RemoteViews.RemoteView
+public class MessagingMessage extends ImageFloatingTextView implements
+ MessagingLinearLayout.MessagingChild {
+
+ private static Pools.SimplePool<MessagingMessage> sInstancePool
+ = new Pools.SynchronizedPool<>(10);
+ private Notification.MessagingStyle.Message mMessage;
+ private MessagingGroup mGroup;
+ private boolean mIsHistoric;
+
+ public MessagingMessage(@NonNull Context context) {
+ super(context);
+ }
+
+ public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ private void setMessage(Notification.MessagingStyle.Message message) {
+ mMessage = message;
+ setText(message.getText());
+ }
+
+ public Notification.MessagingStyle.Message getMessage() {
+ return mMessage;
+ }
+
+ boolean sameAs(Notification.MessagingStyle.Message message) {
+ if (!Objects.equals(message.getText(), mMessage.getText())) {
+ return false;
+ }
+ if (!Objects.equals(message.getSender(), mMessage.getSender())) {
+ return false;
+ }
+ if (!Objects.equals(message.getTimestamp(), mMessage.getTimestamp())) {
+ return false;
+ }
+ return true;
+ }
+
+ static MessagingMessage createMessage(MessagingLayout layout,
+ Notification.MessagingStyle.Message m) {
+ MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
+ MessagingMessage createdMessage = sInstancePool.acquire();
+ if (createdMessage == null) {
+ createdMessage = (MessagingMessage) LayoutInflater.from(layout.getContext()).inflate(
+ R.layout.notification_template_messaging_message, messagingLinearLayout,
+ false);
+ }
+ createdMessage.setMessage(m);
+ return createdMessage;
+ }
+
+ public void removeMessage() {
+ mGroup.removeMessage(this);
+ mGroup = null;
+ mMessage = null;
+ sInstancePool.release(this);
+ }
+
+ public void setMessagingGroup(MessagingGroup group) {
+ mGroup = group;
+ }
+
+ public static void dropCache() {
+ sInstancePool = new Pools.SynchronizedPool<>(10);
+ }
+
+ public void setIsHistoric(boolean isHistoric) {
+ mIsHistoric = isHistoric;
+ }
+
+ public MessagingGroup getGroup() {
+ return mGroup;
+ }
+
+ @Override
+ public int getMeasuredType() {
+ boolean measuredTooSmall = getMeasuredHeight()
+ < getLayoutHeight() + getPaddingTop() + getPaddingBottom();
+ if (measuredTooSmall) {
+ return MEASURED_TOO_SMALL;
+ } else {
+ Layout layout = getLayout();
+ if (layout == null) {
+ return MEASURED_TOO_SMALL;
+ }
+ if (layout.getEllipsisCount(layout.getLineCount() - 1) > 0) {
+ return MEASURED_SHORTENED;
+ } else {
+ return MEASURED_NORMAL;
+ }
+ }
+ }
+
+ @Override
+ public int getConsumedLines() {
+ return getLineCount();
+ }
+
+ public int getLayoutHeight() {
+ Layout layout = getLayout();
+ if (layout == null) {
+ return 0;
+ }
+ return layout.getHeight();
+ }
+
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+}
diff --git a/core/res/res/drawable/ic_reply_notification_large.xml b/core/res/res/drawable/ic_reply_notification_large.xml
new file mode 100644
index 0000000..e75afddf
--- /dev/null
+++ b/core/res/res/drawable/ic_reply_notification_large.xml
@@ -0,0 +1,29 @@
+<!--
+ ~ Copyright (C) 2017 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
+ -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:inset="8dp">
+ <vector android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24.0"
+ android:viewportWidth="24.0">
+ <path
+ android:fillColor="#FFFFFFFF"
+ android:pathData="M10.0,9.0L10.0,5.0l-7.0,7.0 7.0,7.0l0.0,-4.1c5.0,0.0 8.5,1.6 11.0,5.1 -1.0,-5.0 -4.0,-10.0 -11.0,-11.0z"/>
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0 0h24v24H0z"/>
+ </vector>
+</inset>
diff --git a/core/res/res/drawable/messaging_message_background.xml b/core/res/res/drawable/messaging_message_background.xml
new file mode 100644
index 0000000..8a2096a
--- /dev/null
+++ b/core/res/res/drawable/messaging_message_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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="rectangle"
+ android:tint="#14000000">
+ <corners android:radius="4dp" />
+ <padding android:bottom="6dp"
+ android:left="8dp"
+ android:right="8dp"
+ android:top="6dp" />
+</shape>
diff --git a/core/res/res/layout/notification_template_material_messaging.xml b/core/res/res/layout/notification_template_material_messaging.xml
index fd5154a..76b4c81 100644
--- a/core/res/res/layout/notification_template_material_messaging.xml
+++ b/core/res/res/layout/notification_template_material_messaging.xml
@@ -14,13 +14,17 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.android.internal.widget.MessagingLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:tag="messaging"
>
- <include layout="@layout/notification_template_header" />
+ <include layout="@layout/notification_template_header"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/notification_header_height"
+ android:layout_marginEnd="56dp"/>
<LinearLayout
android:id="@+id/notification_action_list_margin_target"
android:layout_width="match_parent"
@@ -50,31 +54,14 @@
android:id="@+id/notification_messaging"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:spacing="@dimen/notification_messaging_spacing" >
- <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text0"
- style="@style/Widget.Material.Notification.MessagingText"
- />
- <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text1"
- style="@style/Widget.Material.Notification.MessagingText"
- />
- <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text2"
- style="@style/Widget.Material.Notification.MessagingText"
- />
- <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text3"
- style="@style/Widget.Material.Notification.MessagingText"
- />
- <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text4"
- style="@style/Widget.Material.Notification.MessagingText"
- />
- <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text5"
- style="@style/Widget.Material.Notification.MessagingText"
- />
- <com.android.internal.widget.ImageFloatingTextView android:id="@+id/inbox_text6"
- style="@style/Widget.Material.Notification.MessagingText"
- />
- </com.android.internal.widget.MessagingLinearLayout>
+ android:layout_marginTop="2dp"
+ android:spacing="@dimen/notification_messaging_spacing" />
</LinearLayout>
</LinearLayout>
<include layout="@layout/notification_material_action_list" />
- <include layout="@layout/notification_template_right_icon" />
-</FrameLayout>
+ <include layout="@layout/notification_template_right_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="18dp"
+ android:layout_gravity="top|end"/>
+</com.android.internal.widget.MessagingLayout>
diff --git a/core/res/res/layout/notification_template_messaging_group.xml b/core/res/res/layout/notification_template_messaging_group.xml
new file mode 100644
index 0000000..5adf7a3
--- /dev/null
+++ b/core/res/res/layout/notification_template_messaging_group.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 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.MessagingGroup
+ 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: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="8dp"
+ android:scaleType="centerCrop"
+ android:importantForAccessibility="no" />
+ <LinearLayout
+ android:id="@+id/message_group_and_sender_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <com.android.internal.widget.MessagingLinearLayout
+ android:id="@+id/group_message_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:spacing="2dp"
+ android:layout_weight="1"/>
+ <com.android.internal.widget.ImageFloatingTextView
+ android:id="@+id/message_name"
+ style="@style/Widget.Material.Notification.MessagingName"
+ android:layout_width="wrap_content"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
+ android:paddingTop="2dp"
+ />
+ </LinearLayout>
+</com.android.internal.widget.MessagingGroup>
diff --git a/core/res/res/layout/notification_template_messaging_message.xml b/core/res/res/layout/notification_template_messaging_message.xml
new file mode 100644
index 0000000..ab6466c
--- /dev/null
+++ b/core/res/res/layout/notification_template_messaging_message.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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.MessagingMessage
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/message_text"
+ style="@style/Widget.Material.Notification.MessagingText"
+/>
diff --git a/core/res/res/layout/notification_template_right_icon.xml b/core/res/res/layout/notification_template_right_icon.xml
index d379256..8fb2887 100644
--- a/core/res/res/layout/notification_template_right_icon.xml
+++ b/core/res/res/layout/notification_template_right_icon.xml
@@ -19,12 +19,12 @@
android:id="@+id/right_icon_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:layout_marginTop="36dp"
android:layout_gravity="top|end">
<ImageView android:id="@+id/right_icon"
android:layout_width="@dimen/notification_right_icon_size"
android:layout_height="@dimen/notification_right_icon_size"
android:layout_gravity="top|end"
- android:layout_marginTop="36dp"
android:layout_marginEnd="@dimen/notification_content_margin_end"
android:scaleType="centerCrop"
android:importantForAccessibility="no" />
@@ -32,7 +32,7 @@
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_gravity="top|end"
- android:layout_marginTop="64dp"
+ android:layout_marginTop="28dp"
android:layout_marginEnd="12dp"
android:background="@drawable/notification_reply_background"
android:src="@drawable/ic_reply_notification"
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 947fcf1..946216c 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -605,6 +605,8 @@
<!-- The size of the right icon image when on low ram -->
<dimen name="notification_right_icon_size_low_ram">40dp</dimen>
+ <dimen name="messaging_avatar_size">24dp</dimen>
+
<!-- Max width/height of the autofill data set picker as a fraction of the screen width/height -->
<dimen name="autofill_dataset_picker_max_size">90%</dimen>
diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml
index cddf99a..2cd4dcb 100644
--- a/core/res/res/values/styles_material.xml
+++ b/core/res/res/values/styles_material.xml
@@ -505,8 +505,17 @@
<item name="layout_width">match_parent</item>
<item name="layout_height">wrap_content</item>
<item name="ellipsize">end</item>
- <item name="visibility">gone</item>
<item name="textAppearance">@style/TextAppearance.Material.Notification</item>
+ <item name="background">@drawable/messaging_message_background</item>
+ </style>
+
+ <style name="Widget.Material.Notification.MessagingName" parent="Widget.Material.Light.TextView">
+ <item name="layout_width">wrap_content</item>
+ <item name="layout_height">wrap_content</item>
+ <item name="ellipsize">end</item>
+ <item name="textAppearance">@style/TextAppearance.Material.Notification</item>
+ <item name="textColor">@color/notification_primary_text_color_light</item>
+ <item name="textSize">12sp</item>
</style>
<!-- Widget Styles -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 32758e8..abca5e5 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3105,6 +3105,15 @@
<java-symbol type="dimen" name="chooser_service_spacing" />
<java-symbol type="bool" name="config_showSysuiShutdown" />
+ <java-symbol type="layout" name="notification_template_messaging_message" />
+ <java-symbol type="layout" name="notification_template_messaging_group" />
+ <java-symbol type="id" name="message_text" />
+ <java-symbol type="id" name="message_name" />
+ <java-symbol type="id" name="message_icon" />
+ <java-symbol type="id" name="group_message_container" />
+ <java-symbol type="drawable" name="ic_reply_notification_large" />
+ <java-symbol type="dimen" name="messaging_avatar_size" />
+
<java-symbol type="integer" name="config_stableDeviceDisplayWidth" />
<java-symbol type="integer" name="config_stableDeviceDisplayHeight" />
<java-symbol type="bool" name="config_display_no_service_when_sim_unready" />
diff --git a/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java
index dfe8511..3919fdd 100644
--- a/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java
@@ -23,11 +23,11 @@
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
+import android.text.Layout;
import android.view.LayoutInflater;
import android.view.View.MeasureSpec;
import com.android.frameworks.coretests.R;
-import com.google.common.base.Function;
import org.junit.Before;
import org.junit.Test;
@@ -52,33 +52,28 @@
@Test
public void testSingleChild() {
- FakeImageFloatingTextView child = fakeChild((i) -> 3);
+ FakeImageFloatingTextView child = fakeChild(3);
- mView.setNumIndentLines(2);
mView.addView(child);
mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
- assertEquals(3, child.getNumIndentLines());
assertFalse(child.isHidden());
assertEquals(150, mView.getMeasuredHeight());
}
@Test
public void testLargeSmall() {
- FakeImageFloatingTextView child1 = fakeChild((i) -> 3);
- FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
+ FakeImageFloatingTextView child1 = fakeChild(3);
+ FakeImageFloatingTextView child2 = fakeChild(1);
- mView.setNumIndentLines(2);
mView.addView(child1);
mView.addView(child2);
mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
- assertEquals(3, child1.getNumIndentLines());
- assertEquals(0, child2.getNumIndentLines());
assertFalse("child1 should not be hidden", child1.isHidden());
assertFalse("child2 should not be hidden", child2.isHidden());
assertEquals(205, mView.getMeasuredHeight());
@@ -86,18 +81,15 @@
@Test
public void testSmallSmall() {
- FakeImageFloatingTextView child1 = fakeChild((i) -> 1);
- FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
+ FakeImageFloatingTextView child1 = fakeChild(1);
+ FakeImageFloatingTextView child2 = fakeChild(1);
- mView.setNumIndentLines(2);
mView.addView(child1);
mView.addView(child2);
mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
- assertEquals(2, child1.getNumIndentLines());
- assertEquals(1, child2.getNumIndentLines());
assertFalse("child1 should not be hidden", child1.isHidden());
assertFalse("child2 should not be hidden", child2.isHidden());
assertEquals(105, mView.getMeasuredHeight());
@@ -105,17 +97,15 @@
@Test
public void testLargeLarge() {
- FakeImageFloatingTextView child1 = fakeChild((i) -> 7);
- FakeImageFloatingTextView child2 = fakeChild((i) -> 7);
+ FakeImageFloatingTextView child1 = fakeChild(7);
+ FakeImageFloatingTextView child2 = fakeChild(7);
- mView.setNumIndentLines(2);
mView.addView(child1);
mView.addView(child2);
mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
- assertEquals(3, child2.getNumIndentLines());
assertTrue("child1 should be hidden", child1.isHidden());
assertFalse("child2 should not be hidden", child2.isHidden());
assertEquals(350, mView.getMeasuredHeight());
@@ -123,10 +113,9 @@
@Test
public void testLargeSmall_largeWrapsWith3indentbutNotFullHeight_andHitsMax() {
- FakeImageFloatingTextView child1 = fakeChild((i) -> i > 2 ? 7 : 6);
- FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
+ FakeImageFloatingTextView child1 = fakeChild(7);
+ FakeImageFloatingTextView child2 = fakeChild(1);
- mView.setNumIndentLines(2);
mView.addView(child1);
mView.addView(child2);
@@ -135,51 +124,18 @@
assertFalse("child1 should not be hidden", child1.isHidden());
assertFalse("child2 should not be hidden", child2.isHidden());
- assertEquals(355, mView.getMeasuredHeight());
- assertEquals(3, child1.getNumIndentLines());
- assertEquals(0, child2.getNumIndentLines());
+ assertEquals(355, mView.getMeasuredHeight());;
}
- @Test
- public void testLargeSmall_largeWrapsWith3indentbutnot3() {
- FakeImageFloatingTextView child1 = fakeChild((i) -> i > 2 ? 4 : 3);
- FakeImageFloatingTextView child2 = fakeChild((i) -> 1);
-
- mView.setNumIndentLines(2);
- mView.addView(child1);
- mView.addView(child2);
-
- mView.measure(WIDTH_SPEC, HEIGHT_SPEC);
- mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
-
- assertFalse("child1 should not be hidden", child1.isHidden());
- assertFalse("child2 should not be hidden", child2.isHidden());
- assertEquals(255, mView.getMeasuredHeight());
- assertEquals(3, child1.getNumIndentLines());
- assertEquals(0, child2.getNumIndentLines());
- }
-
- private class FakeImageFloatingTextView extends ImageFloatingTextView {
+ private class FakeImageFloatingTextView extends MessagingMessage {
public static final int LINE_HEIGHT = 50;
- private final Function<Integer, Integer> mLinesForIndent;
- private int mNumIndentLines;
+ private final int mNumLines;
public FakeImageFloatingTextView(Context context,
- Function<Integer, Integer> linesForIndent) {
+ int linesForIndent) {
super(context, null, 0, 0);
- mLinesForIndent = linesForIndent;
- }
-
- @Override
- public boolean setNumIndentLines(int lines) {
- boolean changed = (mNumIndentLines != lines);
- mNumIndentLines = lines;
- return changed;
- }
-
- public int getNumIndentLines() {
- return mNumIndentLines;
+ mNumLines = linesForIndent;
}
@Override
@@ -195,6 +151,20 @@
heightMeasureSpec)));
}
+ public int getMeasuredType() {
+ boolean measuredTooSmall = getMeasuredHeight()
+ < getLayoutHeight() + getPaddingTop() + getPaddingBottom();
+ if (measuredTooSmall) {
+ return MEASURED_TOO_SMALL;
+ } else {
+ if (getMeasuredHeight() == getDesiredHeight()) {
+ return MEASURED_NORMAL;
+ } else {
+ return MEASURED_SHORTENED;
+ }
+ }
+ }
+
private int clampToMultiplesOfLineHeight(int size) {
if (size <= LINE_HEIGHT) {
return size;
@@ -204,7 +174,7 @@
@Override
public int getLineCount() {
- return mLinesForIndent.apply(mNumIndentLines);
+ return mNumLines;
}
public int getDesiredHeight() {
@@ -229,7 +199,7 @@
}
}
- private FakeImageFloatingTextView fakeChild(Function<Integer,Integer> linesForIndent) {
- return new FakeImageFloatingTextView(mContext, linesForIndent);
+ private FakeImageFloatingTextView fakeChild(int numLines) {
+ return new FakeImageFloatingTextView(mContext, numLines);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
index f6ee1ca..d610492 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationMessagingTemplateViewWrapper.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification;
+import com.android.internal.widget.MessagingLayout;
import com.android.internal.widget.MessagingLinearLayout;
import com.android.systemui.statusbar.ExpandableNotificationRow;
import com.android.systemui.statusbar.TransformableView;
@@ -32,41 +33,18 @@
*/
public class NotificationMessagingTemplateViewWrapper extends NotificationTemplateViewWrapper {
+ private MessagingLayout mMessagingLayout;
private View mContractedMessage;
- private ArrayList<View> mHistoricMessages = new ArrayList<View>();
protected NotificationMessagingTemplateViewWrapper(Context ctx, View view,
ExpandableNotificationRow row) {
super(ctx, view, row);
+ mMessagingLayout = (MessagingLayout) view;
}
private void resolveViews() {
mContractedMessage = null;
-
- View container = mView.findViewById(com.android.internal.R.id.notification_messaging);
- if (container instanceof MessagingLinearLayout
- && ((MessagingLinearLayout) container).getChildCount() > 0) {
- MessagingLinearLayout messagingContainer = (MessagingLinearLayout) container;
-
- int childCount = messagingContainer.getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = messagingContainer.getChildAt(i);
-
- if (child.getVisibility() == View.GONE
- && child instanceof TextView
- && !TextUtils.isEmpty(((TextView) child).getText())) {
- mHistoricMessages.add(child);
- }
-
- // Only consider the first visible child - transforming to a position other than the
- // first looks bad because we have to move across other messages that are fading in.
- if (child.getId() == messagingContainer.getContractedChildId()) {
- mContractedMessage = child;
- } else if (child.getVisibility() == View.VISIBLE) {
- break;
- }
- }
- }
+ mContractedMessage = mMessagingLayout.getContractedMessage();
}
@Override
@@ -89,8 +67,6 @@
@Override
public void setRemoteInputVisible(boolean visible) {
- for (int i = 0; i < mHistoricMessages.size(); i++) {
- mHistoricMessages.get(i).setVisibility(visible ? View.VISIBLE : View.GONE);
- }
+ mMessagingLayout.showHistoricMessages(visible);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index fa34d4a..5b1e261 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -146,6 +146,8 @@
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.NotificationMessagingUtil;
import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.MessagingGroup;
+import com.android.internal.widget.MessagingMessage;
import com.android.keyguard.KeyguardHostView.OnDismissAction;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
@@ -272,7 +274,7 @@
public static final boolean ENABLE_CHILD_NOTIFICATIONS
= SystemProperties.getBoolean("debug.child_notifs", true);
public static final boolean FORCE_REMOTE_INPUT_HISTORY =
- SystemProperties.getBoolean("debug.force_remoteinput_history", false);
+ SystemProperties.getBoolean("debug.force_remoteinput_history", true);
private static final boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
protected static final int MSG_HIDE_RECENT_APPS = 1020;
@@ -1230,6 +1232,8 @@
}
public void onDensityOrFontScaleChanged() {
+ MessagingMessage.dropCache();
+ MessagingGroup.dropCache();
// start old BaseStatusBar.onDensityOrFontScaleChanged().
if (!KeyguardUpdateMonitor.getInstance(mContext).isSwitchingUser()) {
updateNotificationsOnDensityOrFontScaleChanged();
@@ -1683,8 +1687,9 @@
clearCurrentMediaNotification();
updateMediaMetaData(true, true);
}
- if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputController.isSpinning(key)) {
- Entry entry = mNotificationData.get(key);
+ Entry entry = mNotificationData.get(key);
+ if (FORCE_REMOTE_INPUT_HISTORY && mRemoteInputController.isSpinning(key)
+ && entry.row != null && !entry.row.isDismissed()) {
StatusBarNotification sbn = entry.notification;
Notification.Builder b = Notification.Builder
@@ -1720,6 +1725,7 @@
deferRemoval = false;
}
if (updated) {
+ Log.w(TAG, "Keeping notification around after sending remote input "+ entry.key);
mKeysKeptForRemoteInput.add(entry.key);
return;
}
@@ -1729,7 +1735,6 @@
mHeadsUpEntriesToRemoveOnSwitch.add(mHeadsUpManager.getEntry(key));
return;
}
- Entry entry = mNotificationData.get(key);
if (entry != null && mRemoteInputController.isRemoteInputActive(entry)
&& (entry.row != null && !entry.row.isDismissed())) {