summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/Notification.java10
-rw-r--r--core/java/com/android/internal/widget/CompactMessagingLayout.java263
-rw-r--r--core/res/res/layout/notification_template_material_messaging_compact_heads_up.xml12
-rw-r--r--core/res/res/values/dimens.xml6
-rw-r--r--core/res/res/values/symbols.xml3
5 files changed, 292 insertions, 2 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 7ea309193612..d8f03b165a00 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -9282,9 +9282,19 @@ public class Notification implements Parcelable
contentView.setViewVisibility(R.id.header_text_divider, View.GONE);
if (conversationIcon != null) {
contentView.setViewVisibility(R.id.icon, View.GONE);
+ contentView.setViewVisibility(R.id.conversation_face_pile, View.GONE);
contentView.setViewVisibility(R.id.conversation_icon, View.VISIBLE);
contentView.setBoolean(R.id.conversation_icon, "setApplyCircularCrop", true);
contentView.setImageViewIcon(R.id.conversation_icon, conversationIcon);
+ } else if (mIsGroupConversation) {
+ contentView.setViewVisibility(R.id.icon, View.GONE);
+ contentView.setViewVisibility(R.id.conversation_icon, View.GONE);
+ contentView.setInt(R.id.status_bar_latest_event_content,
+ "setNotificationBackgroundColor", mBuilder.getBackgroundColor(p));
+ contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
+ mBuilder.getSmallIconColor(p));
+ contentView.setBundle(R.id.status_bar_latest_event_content, "setGroupFacePile",
+ mBuilder.mN.extras);
}
if (remoteInputAction != null) {
diff --git a/core/java/com/android/internal/widget/CompactMessagingLayout.java b/core/java/com/android/internal/widget/CompactMessagingLayout.java
new file mode 100644
index 000000000000..1e2c01ae4be9
--- /dev/null
+++ b/core/java/com/android/internal/widget/CompactMessagingLayout.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2024 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.app.Notification;
+import android.app.Notification.MessagingStyle;
+import android.app.Person;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.RemotableViewMethod;
+import android.view.View;
+import android.view.ViewStub;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.RemoteViews;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A custom-built layout for the compact Heads Up of Notification.MessagingStyle .
+ */
+@RemoteViews.RemoteView
+public class CompactMessagingLayout extends FrameLayout {
+
+ private final PeopleHelper mPeopleHelper = new PeopleHelper();
+
+ private ViewStub mConversationFacePileViewStub;
+
+ private int mNotificationBackgroundColor;
+ private int mFacePileSize;
+ private int mFacePileAvatarSize;
+ private int mFacePileProtectionWidth;
+ private int mLayoutColor;
+
+ public CompactMessagingLayout(@NonNull Context context) {
+ super(context);
+ }
+
+ public CompactMessagingLayout(@NonNull Context context,
+ @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CompactMessagingLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public CompactMessagingLayout(@NonNull Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mPeopleHelper.init(getContext());
+ mConversationFacePileViewStub = requireViewById(R.id.conversation_face_pile);
+ mFacePileSize = getResources()
+ .getDimensionPixelSize(R.dimen.conversation_compact_face_pile_size);
+ mFacePileAvatarSize = getResources()
+ .getDimensionPixelSize(R.dimen.conversation_compact_face_pile_avatar_size);
+ mFacePileProtectionWidth = getResources().getDimensionPixelSize(
+ R.dimen.conversation_compact_face_pile_protection_width);
+ }
+
+ /**
+ * Set conversation data
+ *
+ * @param extras Bundle contains conversation data
+ */
+ @RemotableViewMethod(asyncImpl = "setGroupFacePileAsync")
+ public void setGroupFacePile(Bundle extras) {
+ // NO-OP
+ }
+
+ /**
+ * async version of {@link ConversationLayout#setLayoutColor}
+ */
+ @RemotableViewMethod
+ public Runnable setLayoutColorAsync(int color) {
+ mLayoutColor = color;
+ return NotificationRunnables.NOOP;
+ }
+
+ @RemotableViewMethod(asyncImpl = "setLayoutColorAsync")
+ public void setLayoutColor(int color) {
+ mLayoutColor = color;
+ }
+
+ /**
+ * @param color the color of the notification background
+ */
+ @RemotableViewMethod
+ public void setNotificationBackgroundColor(int color) {
+ mNotificationBackgroundColor = color;
+ }
+
+ /**
+ * async version of {@link CompactMessagingLayout#setGroupFacePile}
+ * setGroupFacePile!
+ */
+ public Runnable setGroupFacePileAsync(Bundle extras) {
+ final Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
+ final List<Notification.MessagingStyle.Message> newMessages =
+ Notification.MessagingStyle.Message.getMessagesFromBundleArray(messages);
+ final Parcelable[] histMessages =
+ extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES);
+ final List<Notification.MessagingStyle.Message> newHistoricMessages =
+ Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages);
+ final Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON, Person.class);
+
+ final List<List<MessagingStyle.Message>> groups = groupMessages(newMessages,
+ newHistoricMessages);
+ final PeopleHelper.NameToPrefixMap nameToPrefixMap =
+ mPeopleHelper.mapUniqueNamesToPrefixWithGroupList(groups);
+ final int layoutColor = mLayoutColor;
+ // Find last two person's icon to show them in the face pile.
+ Icon secondLastIcon = null;
+ Icon lastIcon = null;
+ CharSequence lastKey = null;
+ final CharSequence userKey = getPersonKey(user);
+ for (int i = groups.size() - 1; i >= 0; i--) {
+ final MessagingStyle.Message message = groups.get(i).get(0);
+ final Person sender =
+ message.getSenderPerson() != null ? message.getSenderPerson() : user;
+ final CharSequence senderKey = getPersonKey(sender);
+ final boolean notUser = senderKey != userKey;
+ final boolean notIncluded = senderKey != lastKey;
+
+ if ((notUser && notIncluded) || (i == 0 && lastKey == null)) {
+ final Icon icon = getSenderIcon(sender, nameToPrefixMap, layoutColor);
+ if (lastIcon == null) {
+ lastIcon = icon;
+ lastKey = senderKey;
+ } else {
+ secondLastIcon = icon;
+ break;
+ }
+ }
+ }
+
+ if (lastIcon == null) {
+ lastIcon = getSenderIcon(null, null, layoutColor);
+ }
+
+ if (secondLastIcon == null) {
+ secondLastIcon = getSenderIcon(null, null, layoutColor);
+ }
+ final Drawable secondLastIconDrawable = secondLastIcon.loadDrawable(getContext());
+ final Drawable lastIconDrawable = lastIcon.loadDrawable(getContext());
+ return () -> {
+ final View conversationFacePile = mConversationFacePileViewStub.inflate();
+ conversationFacePile.setVisibility(VISIBLE);
+
+ final ImageView facePileBottomBg = conversationFacePile.requireViewById(
+ com.android.internal.R.id.conversation_face_pile_bottom_background);
+ final ImageView facePileTop = conversationFacePile.requireViewById(
+ com.android.internal.R.id.conversation_face_pile_top);
+ final ImageView facePileBottom = conversationFacePile.requireViewById(
+ com.android.internal.R.id.conversation_face_pile_bottom);
+
+ facePileTop.setImageDrawable(secondLastIconDrawable);
+ facePileBottom.setImageDrawable(lastIconDrawable);
+ facePileBottomBg.setImageTintList(ColorStateList.valueOf(mNotificationBackgroundColor));
+ setSize(conversationFacePile, mFacePileSize);
+ setSize(facePileBottom, mFacePileAvatarSize);
+ setSize(facePileTop, mFacePileAvatarSize);
+ setSize(facePileBottomBg, mFacePileAvatarSize + 2 * mFacePileProtectionWidth);
+ };
+ }
+
+ @NonNull
+ private Icon getSenderIcon(@Nullable Person sender,
+ @Nullable PeopleHelper.NameToPrefixMap uniqueNames,
+ int layoutColor) {
+ if (sender == null) {
+ return mPeopleHelper.createAvatarSymbol(/* name = */ "", /* symbol = */ "",
+ layoutColor);
+ }
+
+ if (sender.getIcon() != null) {
+ return sender.getIcon();
+ }
+
+ final CharSequence senderName = sender.getName();
+ if (!TextUtils.isEmpty(senderName)) {
+ final String symbol = uniqueNames != null ? uniqueNames.getPrefix(senderName) : "";
+ return mPeopleHelper.createAvatarSymbol(senderName, symbol, layoutColor);
+ }
+
+ return mPeopleHelper.createAvatarSymbol(/* name = */ "", /* symbol = */ "", layoutColor);
+ }
+
+
+ /**
+ * Groups the given messages by their sender.
+ */
+ private static List<List<MessagingStyle.Message>> groupMessages(
+ List<MessagingStyle.Message> messages,
+ List<MessagingStyle.Message> historicMessages
+ ) {
+ if (messages.isEmpty() && historicMessages.isEmpty()) return List.of();
+
+ ArrayList<MessagingStyle.Message> currentGroup = null;
+ CharSequence currentSenderKey = null;
+ final ArrayList<List<MessagingStyle.Message>> groups = new ArrayList<>();
+ final int histSize = historicMessages.size();
+
+ for (int i = 0; i < histSize + messages.size(); i++) {
+ final MessagingStyle.Message message = i < histSize ? historicMessages.get(i)
+ : messages.get(i - histSize);
+ if (message == null) continue;
+
+ final CharSequence senderKey = getPersonKey(message.getSenderPerson());
+ final boolean isNewGroup = currentGroup == null || senderKey != currentSenderKey;
+ if (isNewGroup) {
+ currentGroup = new ArrayList<>();
+ groups.add(currentGroup);
+ currentSenderKey = senderKey;
+ }
+ currentGroup.add(message);
+ }
+ return groups;
+ }
+
+ private static CharSequence getPersonKey(@Nullable Person person) {
+ return person == null ? null : person.getKey() == null ? person.getName() : person.getKey();
+ }
+
+ private static void setSize(View view, int size) {
+ final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) view.getLayoutParams();
+ lp.width = size;
+ lp.height = size;
+ view.setLayoutParams(lp);
+ }
+}
diff --git a/core/res/res/layout/notification_template_material_messaging_compact_heads_up.xml b/core/res/res/layout/notification_template_material_messaging_compact_heads_up.xml
index 3b288d7038e1..82920bad95cd 100644
--- a/core/res/res/layout/notification_template_material_messaging_compact_heads_up.xml
+++ b/core/res/res/layout/notification_template_material_messaging_compact_heads_up.xml
@@ -13,7 +13,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-<FrameLayout
+<com.android.internal.widget.CompactMessagingLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
@@ -45,6 +45,14 @@
android:scaleType="centerCrop"
android:importantForAccessibility="no"
/>
+ <ViewStub
+ android:layout="@layout/conversation_face_pile_layout"
+ android:layout_gravity="center_vertical|start"
+ android:layout_width="@dimen/conversation_compact_face_pile_size"
+ android:layout_height="@dimen/conversation_compact_face_pile_size"
+ android:layout_marginStart="@dimen/notification_icon_circle_start"
+ android:id="@+id/conversation_face_pile"
+ />
<FrameLayout
android:id="@+id/alternate_expand_target"
android:layout_width="@dimen/notification_content_margin_start"
@@ -101,4 +109,4 @@
/>
</FrameLayout>
</LinearLayout>
-</FrameLayout>
+</com.android.internal.widget.CompactMessagingLayout>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 52ce9936787f..b885e03bc098 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -846,6 +846,12 @@
<dimen name="conversation_face_pile_protection_width">2dp</dimen>
<!-- The width of the protection of the face pile layout when expanded-->
<dimen name="conversation_face_pile_protection_width_expanded">@dimen/conversation_face_pile_protection_width</dimen>
+ <!-- size of the compact face pile -->
+ <dimen name="conversation_compact_face_pile_size">24dp</dimen>
+ <!-- size of the face pile avatar -->
+ <dimen name="conversation_compact_face_pile_avatar_size">17dp</dimen>
+ <!-- size of the face pile protection -->
+ <dimen name="conversation_compact_face_pile_protection_width">1dp</dimen>
<!-- The padding of the expanded message container-->
<dimen name="expanded_group_conversation_message_padding">32dp</dimen>
<!-- The stroke width of the ring used to visually mark a conversation as important -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 6c6764a3175c..5a3eaeb8327b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -4543,6 +4543,9 @@
<java-symbol type="dimen" name="conversation_avatar_size_group_expanded" />
<java-symbol type="dimen" name="conversation_face_pile_avatar_size" />
<java-symbol type="dimen" name="conversation_face_pile_avatar_size_group_expanded" />
+ <java-symbol type="dimen" name="conversation_compact_face_pile_size" />
+ <java-symbol type="dimen" name="conversation_compact_face_pile_avatar_size" />
+ <java-symbol type="dimen" name="conversation_compact_face_pile_protection_width" />
<java-symbol type="dimen" name="conversation_face_pile_protection_width" />
<java-symbol type="dimen" name="conversation_face_pile_protection_width_expanded" />
<java-symbol type="dimen" name="conversation_badge_protrusion_group_expanded" />