Merge changes Ie5bfc2f6,I38e7dc8c,I9308a15a into rvc-dev am: 4605ca9daa am: 33449a1a70
Change-Id: I58b798384785d423588389e8365f862e7e630815
This commit is contained in:
@@ -1232,6 +1232,10 @@ public class Notification implements Parcelable
|
|||||||
/** @hide */
|
/** @hide */
|
||||||
public static final String EXTRA_CONVERSATION_ICON = "android.conversationIcon";
|
public static final String EXTRA_CONVERSATION_ICON = "android.conversationIcon";
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
public static final String EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT =
|
||||||
|
"android.conversationUnreadMessageCount";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link #extras} key: an array of {@link android.app.Notification.MessagingStyle.Message}
|
* {@link #extras} key: an array of {@link android.app.Notification.MessagingStyle.Message}
|
||||||
* bundles provided by a
|
* bundles provided by a
|
||||||
@@ -7102,6 +7106,7 @@ public class Notification implements Parcelable
|
|||||||
List<Message> mHistoricMessages = new ArrayList<>();
|
List<Message> mHistoricMessages = new ArrayList<>();
|
||||||
boolean mIsGroupConversation;
|
boolean mIsGroupConversation;
|
||||||
@ConversationType int mConversationType = CONVERSATION_TYPE_LEGACY;
|
@ConversationType int mConversationType = CONVERSATION_TYPE_LEGACY;
|
||||||
|
int mUnreadMessageCount;
|
||||||
|
|
||||||
MessagingStyle() {
|
MessagingStyle() {
|
||||||
}
|
}
|
||||||
@@ -7247,6 +7252,17 @@ public class Notification implements Parcelable
|
|||||||
return mConversationType;
|
return mConversationType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
public int getUnreadMessageCount() {
|
||||||
|
return mUnreadMessageCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
public MessagingStyle setUnreadMessageCount(int unreadMessageCount) {
|
||||||
|
mUnreadMessageCount = unreadMessageCount;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a message for display by this notification. Convenience call for a simple
|
* Adds a message for display by this notification. Convenience call for a simple
|
||||||
* {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}.
|
* {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}.
|
||||||
@@ -7401,6 +7417,7 @@ public class Notification implements Parcelable
|
|||||||
if (mShortcutIcon != null) {
|
if (mShortcutIcon != null) {
|
||||||
extras.putParcelable(EXTRA_CONVERSATION_ICON, mShortcutIcon);
|
extras.putParcelable(EXTRA_CONVERSATION_ICON, mShortcutIcon);
|
||||||
}
|
}
|
||||||
|
extras.putInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT, mUnreadMessageCount);
|
||||||
|
|
||||||
fixTitleAndTextExtras(extras);
|
fixTitleAndTextExtras(extras);
|
||||||
extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation);
|
extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation);
|
||||||
@@ -7452,6 +7469,7 @@ public class Notification implements Parcelable
|
|||||||
Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
|
Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
|
||||||
mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
|
mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
|
||||||
mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION);
|
mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION);
|
||||||
|
mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -7601,6 +7619,7 @@ public class Notification implements Parcelable
|
|||||||
: mBuilder.getMessagingLayoutResource(),
|
: mBuilder.getMessagingLayoutResource(),
|
||||||
p,
|
p,
|
||||||
bindResult);
|
bindResult);
|
||||||
|
|
||||||
addExtras(mBuilder.mN.extras);
|
addExtras(mBuilder.mN.extras);
|
||||||
if (!isConversationLayout) {
|
if (!isConversationLayout) {
|
||||||
// also update the end margin if there is an image
|
// also update the end margin if there is an image
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ import com.android.internal.util.ContrastColorUtil;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@@ -151,6 +152,7 @@ public class ConversationLayout extends FrameLayout
|
|||||||
private int mFacePileProtectionWidth;
|
private int mFacePileProtectionWidth;
|
||||||
private int mFacePileProtectionWidthExpanded;
|
private int mFacePileProtectionWidthExpanded;
|
||||||
private boolean mImportantConversation;
|
private boolean mImportantConversation;
|
||||||
|
private TextView mUnreadBadge;
|
||||||
|
|
||||||
public ConversationLayout(@NonNull Context context) {
|
public ConversationLayout(@NonNull Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
@@ -277,6 +279,7 @@ public class ConversationLayout extends FrameLayout
|
|||||||
mAppName.setOnVisibilityChangedListener((visibility) -> {
|
mAppName.setOnVisibilityChangedListener((visibility) -> {
|
||||||
onAppNameVisibilityChanged();
|
onAppNameVisibilityChanged();
|
||||||
});
|
});
|
||||||
|
mUnreadBadge = findViewById(R.id.conversation_unread_count);
|
||||||
mConversationContentStart = getResources().getDimensionPixelSize(
|
mConversationContentStart = getResources().getDimensionPixelSize(
|
||||||
R.dimen.conversation_content_start);
|
R.dimen.conversation_content_start);
|
||||||
mInternalButtonPadding
|
mInternalButtonPadding
|
||||||
@@ -354,7 +357,6 @@ public class ConversationLayout extends FrameLayout
|
|||||||
// mUser now set (would be nice to avoid the side effect but WHATEVER)
|
// mUser now set (would be nice to avoid the side effect but WHATEVER)
|
||||||
setUser(extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON));
|
setUser(extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON));
|
||||||
|
|
||||||
|
|
||||||
// Append remote input history to newMessages (again, side effect is lame but WHATEVS)
|
// Append remote input history to newMessages (again, side effect is lame but WHATEVS)
|
||||||
RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[])
|
RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[])
|
||||||
extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
|
extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
|
||||||
@@ -362,9 +364,11 @@ public class ConversationLayout extends FrameLayout
|
|||||||
|
|
||||||
boolean showSpinner =
|
boolean showSpinner =
|
||||||
extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
|
extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
|
||||||
|
|
||||||
// bind it, baby
|
// bind it, baby
|
||||||
bind(newMessages, newHistoricMessages, showSpinner);
|
bind(newMessages, newHistoricMessages, showSpinner);
|
||||||
|
|
||||||
|
int unreadCount = extras.getInt(Notification.EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
|
||||||
|
setUnreadCount(unreadCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -372,6 +376,18 @@ public class ConversationLayout extends FrameLayout
|
|||||||
mImageResolver = resolver;
|
mImageResolver = resolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @hide */
|
||||||
|
public void setUnreadCount(int unreadCount) {
|
||||||
|
mUnreadBadge.setVisibility(mIsCollapsed && unreadCount > 1 ? VISIBLE : GONE);
|
||||||
|
CharSequence text = unreadCount >= 100
|
||||||
|
? getResources().getString(R.string.unread_convo_overflow, 99)
|
||||||
|
: String.format(Locale.getDefault(), "%d", unreadCount);
|
||||||
|
mUnreadBadge.setText(text);
|
||||||
|
mUnreadBadge.setBackgroundTintList(ColorStateList.valueOf(mLayoutColor));
|
||||||
|
boolean needDarkText = ColorUtils.calculateLuminance(mLayoutColor) > 0.5f;
|
||||||
|
mUnreadBadge.setTextColor(needDarkText ? Color.BLACK : Color.WHITE);
|
||||||
|
}
|
||||||
|
|
||||||
private void addRemoteInputHistoryToMessages(
|
private void addRemoteInputHistoryToMessages(
|
||||||
List<Notification.MessagingStyle.Message> newMessages,
|
List<Notification.MessagingStyle.Message> newMessages,
|
||||||
RemoteInputHistoryItem[] remoteInputHistory) {
|
RemoteInputHistoryItem[] remoteInputHistory) {
|
||||||
|
|||||||
19
core/res/res/drawable/conversation_unread_bg.xml
Normal file
19
core/res/res/drawable/conversation_unread_bg.xml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!--
|
||||||
|
~ 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">
|
||||||
|
<corners android:radius="20sp" />
|
||||||
|
<solid android:color="@android:color/white" />
|
||||||
|
</shape>
|
||||||
@@ -199,10 +199,8 @@
|
|||||||
android:clipChildren="false"
|
android:clipChildren="false"
|
||||||
/>
|
/>
|
||||||
</com.android.internal.widget.RemeasuringLinearLayout>
|
</com.android.internal.widget.RemeasuringLinearLayout>
|
||||||
<!-- Unread Count -->
|
|
||||||
<!-- <TextView /> -->
|
|
||||||
|
|
||||||
<!-- This is where the expand button will be placed when collapsed-->
|
<!-- This is where the expand button container will be placed when collapsed-->
|
||||||
</com.android.internal.widget.RemeasuringLinearLayout>
|
</com.android.internal.widget.RemeasuringLinearLayout>
|
||||||
|
|
||||||
<include layout="@layout/notification_template_smart_reply_container"
|
<include layout="@layout/notification_template_smart_reply_container"
|
||||||
@@ -238,6 +236,21 @@
|
|||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
android:clipChildren="false"
|
android:clipChildren="false"
|
||||||
/>
|
/>
|
||||||
|
<!-- Unread Count -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/conversation_unread_count"
|
||||||
|
android:layout_width="33sp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="11dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="2dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:textAppearance="@style/TextAppearance.DeviceDefault.Notification"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:background="@drawable/conversation_unread_bg"
|
||||||
|
/>
|
||||||
<com.android.internal.widget.NotificationExpandButton
|
<com.android.internal.widget.NotificationExpandButton
|
||||||
android:id="@+id/expand_button"
|
android:id="@+id/expand_button"
|
||||||
android:layout_width="@dimen/notification_header_expand_icon_size"
|
android:layout_width="@dimen/notification_header_expand_icon_size"
|
||||||
@@ -246,6 +259,6 @@
|
|||||||
android:drawable="@drawable/ic_expand_notification"
|
android:drawable="@drawable/ic_expand_notification"
|
||||||
android:clickable="false"
|
android:clickable="false"
|
||||||
android:importantForAccessibility="no"
|
android:importantForAccessibility="no"
|
||||||
/>
|
/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</com.android.internal.widget.ConversationLayout>
|
</com.android.internal.widget.ConversationLayout>
|
||||||
|
|||||||
@@ -5442,6 +5442,9 @@
|
|||||||
<!-- Conversation Title fallback if the there is no name provided in a group chat conversation [CHAR LIMIT=40]-->
|
<!-- 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>
|
<string name="conversation_title_fallback_group_chat">Group Conversation</string>
|
||||||
|
|
||||||
|
<!-- Number of unread messages displayed on a conversation notification, when greater-than-or-equal-to 100 [CHAR LIMIT=3]-->
|
||||||
|
<string name="unread_convo_overflow"><xliff:g id="max_unread_count" example="99">%1$d</xliff:g>+</string>
|
||||||
|
|
||||||
<!-- ResolverActivity - profile tabs -->
|
<!-- 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] -->
|
<!-- 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>
|
<string name="resolver_personal_tab">Personal</string>
|
||||||
|
|||||||
@@ -3899,6 +3899,8 @@
|
|||||||
<java-symbol type="dimen" name="button_padding_horizontal_material" />
|
<java-symbol type="dimen" name="button_padding_horizontal_material" />
|
||||||
<java-symbol type="dimen" name="button_inset_horizontal_material" />
|
<java-symbol type="dimen" name="button_inset_horizontal_material" />
|
||||||
<java-symbol type="layout" name="conversation_face_pile_layout" />
|
<java-symbol type="layout" name="conversation_face_pile_layout" />
|
||||||
|
<java-symbol type="id" name="conversation_unread_count" />
|
||||||
|
<java-symbol type="string" name="unread_convo_overflow" />
|
||||||
|
|
||||||
<!-- Intent resolver and share sheet -->
|
<!-- Intent resolver and share sheet -->
|
||||||
<java-symbol type="string" name="resolver_personal_tab" />
|
<java-symbol type="string" name="resolver_personal_tab" />
|
||||||
|
|||||||
@@ -38,10 +38,12 @@ public class NotificationUiAdjustment {
|
|||||||
public final String key;
|
public final String key;
|
||||||
public final List<Notification.Action> smartActions;
|
public final List<Notification.Action> smartActions;
|
||||||
public final List<CharSequence> smartReplies;
|
public final List<CharSequence> smartReplies;
|
||||||
|
public final boolean isConversation;
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
NotificationUiAdjustment(
|
NotificationUiAdjustment(
|
||||||
String key, List<Notification.Action> smartActions, List<CharSequence> smartReplies) {
|
String key, List<Notification.Action> smartActions, List<CharSequence> smartReplies,
|
||||||
|
boolean isConversation) {
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.smartActions = smartActions == null
|
this.smartActions = smartActions == null
|
||||||
? Collections.emptyList()
|
? Collections.emptyList()
|
||||||
@@ -49,12 +51,14 @@ public class NotificationUiAdjustment {
|
|||||||
this.smartReplies = smartReplies == null
|
this.smartReplies = smartReplies == null
|
||||||
? Collections.emptyList()
|
? Collections.emptyList()
|
||||||
: smartReplies;
|
: smartReplies;
|
||||||
|
this.isConversation = isConversation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static NotificationUiAdjustment extractFromNotificationEntry(
|
public static NotificationUiAdjustment extractFromNotificationEntry(
|
||||||
NotificationEntry entry) {
|
NotificationEntry entry) {
|
||||||
return new NotificationUiAdjustment(
|
return new NotificationUiAdjustment(
|
||||||
entry.getKey(), entry.getSmartActions(), entry.getSmartReplies());
|
entry.getKey(), entry.getSmartActions(), entry.getSmartReplies(),
|
||||||
|
entry.getRanking().isConversation());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean needReinflate(
|
public static boolean needReinflate(
|
||||||
@@ -63,6 +67,9 @@ public class NotificationUiAdjustment {
|
|||||||
if (oldAdjustment == newAdjustment) {
|
if (oldAdjustment == newAdjustment) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (oldAdjustment.isConversation != newAdjustment.isConversation) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (areDifferent(oldAdjustment.smartActions, newAdjustment.smartActions)) {
|
if (areDifferent(oldAdjustment.smartActions, newAdjustment.smartActions)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
|
|
||||||
import android.app.Notification
|
|
||||||
import android.content.pm.LauncherApps
|
|
||||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class ConversationNotificationProcessor @Inject constructor(
|
|
||||||
private val launcherApps: LauncherApps
|
|
||||||
) {
|
|
||||||
fun processNotification(entry: NotificationEntry, recoveredBuilder: Notification.Builder) {
|
|
||||||
val messagingStyle = recoveredBuilder.style as? Notification.MessagingStyle ?: return
|
|
||||||
messagingStyle.conversationType =
|
|
||||||
if (entry.ranking.channel.isImportantConversation)
|
|
||||||
Notification.MessagingStyle.CONVERSATION_TYPE_IMPORTANT
|
|
||||||
else
|
|
||||||
Notification.MessagingStyle.CONVERSATION_TYPE_NORMAL
|
|
||||||
entry.ranking.shortcutInfo?.let { shortcutInfo ->
|
|
||||||
messagingStyle.shortcutIcon = launcherApps.getShortcutIcon(shortcutInfo)
|
|
||||||
shortcutInfo.shortLabel?.let { shortLabel ->
|
|
||||||
messagingStyle.conversationTitle = shortLabel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.LauncherApps
|
||||||
|
import android.service.notification.NotificationListenerService.Ranking
|
||||||
|
import android.service.notification.NotificationListenerService.RankingMap
|
||||||
|
import com.android.internal.statusbar.NotificationVisibility
|
||||||
|
import com.android.internal.widget.ConversationLayout
|
||||||
|
import com.android.systemui.statusbar.notification.collection.NotificationEntry
|
||||||
|
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
|
||||||
|
import com.android.systemui.statusbar.notification.row.NotificationContentView
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
/** Populates additional information in conversation notifications */
|
||||||
|
class ConversationNotificationProcessor @Inject constructor(
|
||||||
|
private val launcherApps: LauncherApps,
|
||||||
|
private val conversationNotificationManager: ConversationNotificationManager
|
||||||
|
) {
|
||||||
|
fun processNotification(entry: NotificationEntry, recoveredBuilder: Notification.Builder) {
|
||||||
|
val messagingStyle = recoveredBuilder.style as? Notification.MessagingStyle ?: return
|
||||||
|
messagingStyle.conversationType =
|
||||||
|
if (entry.ranking.channel.isImportantConversation)
|
||||||
|
Notification.MessagingStyle.CONVERSATION_TYPE_IMPORTANT
|
||||||
|
else
|
||||||
|
Notification.MessagingStyle.CONVERSATION_TYPE_NORMAL
|
||||||
|
entry.ranking.shortcutInfo?.let { shortcutInfo ->
|
||||||
|
messagingStyle.shortcutIcon = launcherApps.getShortcutIcon(shortcutInfo)
|
||||||
|
shortcutInfo.shortLabel?.let { shortLabel ->
|
||||||
|
messagingStyle.conversationTitle = shortLabel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
messagingStyle.unreadMessageCount =
|
||||||
|
conversationNotificationManager.getUnreadCount(entry, recoveredBuilder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tracks state related to conversation notifications, and updates the UI of existing notifications
|
||||||
|
* when necessary.
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
class ConversationNotificationManager @Inject constructor(
|
||||||
|
private val notificationEntryManager: NotificationEntryManager,
|
||||||
|
private val context: Context
|
||||||
|
) {
|
||||||
|
// Need this state to be thread safe, since it's accessed from the ui thread
|
||||||
|
// (NotificationEntryListener) and a bg thread (NotificationContentInflater)
|
||||||
|
private val states = ConcurrentHashMap<String, ConversationState>()
|
||||||
|
|
||||||
|
private var notifPanelCollapsed = true
|
||||||
|
|
||||||
|
init {
|
||||||
|
notificationEntryManager.addNotificationEntryListener(object : NotificationEntryListener {
|
||||||
|
|
||||||
|
override fun onNotificationRankingUpdated(rankingMap: RankingMap) {
|
||||||
|
fun getLayouts(view: NotificationContentView) =
|
||||||
|
sequenceOf(view.contractedChild, view.expandedChild, view.headsUpChild)
|
||||||
|
val ranking = Ranking()
|
||||||
|
states.keys.asSequence()
|
||||||
|
.mapNotNull { notificationEntryManager.getActiveNotificationUnfiltered(it) }
|
||||||
|
.forEach { entry ->
|
||||||
|
if (rankingMap.getRanking(entry.sbn.key, ranking) &&
|
||||||
|
ranking.isConversation) {
|
||||||
|
val important = ranking.channel.isImportantConversation
|
||||||
|
entry.row?.layouts?.asSequence()
|
||||||
|
?.flatMap(::getLayouts)
|
||||||
|
?.mapNotNull { it as? ConversationLayout }
|
||||||
|
?.forEach { it.setIsImportantConversation(important) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEntryInflated(entry: NotificationEntry) {
|
||||||
|
if (!entry.ranking.isConversation) return
|
||||||
|
fun updateCount(isExpanded: Boolean) {
|
||||||
|
if (isExpanded && !notifPanelCollapsed) {
|
||||||
|
resetCount(entry.key)
|
||||||
|
entry.row?.let(::resetBadgeUi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entry.row?.setOnExpansionChangedListener(::updateCount)
|
||||||
|
updateCount(entry.row?.isExpanded == true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEntryReinflated(entry: NotificationEntry) = onEntryInflated(entry)
|
||||||
|
|
||||||
|
override fun onEntryRemoved(
|
||||||
|
entry: NotificationEntry,
|
||||||
|
visibility: NotificationVisibility?,
|
||||||
|
removedByUser: Boolean,
|
||||||
|
reason: Int
|
||||||
|
) = removeTrackedEntry(entry)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getUnreadCount(entry: NotificationEntry, recoveredBuilder: Notification.Builder): Int =
|
||||||
|
states.compute(entry.key) { _, state ->
|
||||||
|
val newCount = state?.run {
|
||||||
|
val old = Notification.Builder.recoverBuilder(context, notification)
|
||||||
|
val increment = Notification
|
||||||
|
.areStyledNotificationsVisiblyDifferent(old, recoveredBuilder)
|
||||||
|
if (increment) unreadCount + 1 else unreadCount
|
||||||
|
} ?: 1
|
||||||
|
ConversationState(newCount, entry.sbn.notification)
|
||||||
|
}!!.unreadCount
|
||||||
|
|
||||||
|
fun onNotificationPanelExpandStateChanged(isCollapsed: Boolean) {
|
||||||
|
notifPanelCollapsed = isCollapsed
|
||||||
|
if (isCollapsed) return
|
||||||
|
|
||||||
|
// When the notification panel is expanded, reset the counters of any expanded
|
||||||
|
// conversations
|
||||||
|
val expanded = states
|
||||||
|
.asSequence()
|
||||||
|
.mapNotNull { (key, _) ->
|
||||||
|
notificationEntryManager.getActiveNotificationUnfiltered(key)
|
||||||
|
?.let { entry ->
|
||||||
|
if (entry.row?.isExpanded == true) key to entry
|
||||||
|
else null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toMap()
|
||||||
|
states.replaceAll { key, state ->
|
||||||
|
if (expanded.contains(key)) state.copy(unreadCount = 0)
|
||||||
|
else state
|
||||||
|
}
|
||||||
|
// Update UI separate from the replaceAll call, since ConcurrentHashMap may re-run the
|
||||||
|
// lambda if threads are in contention.
|
||||||
|
expanded.values.asSequence().mapNotNull { it.row }.forEach(::resetBadgeUi)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetCount(key: String) {
|
||||||
|
states.compute(key) { _, state -> state?.copy(unreadCount = 0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeTrackedEntry(entry: NotificationEntry) {
|
||||||
|
states.remove(entry.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun resetBadgeUi(row: ExpandableNotificationRow): Unit =
|
||||||
|
(row.layouts?.asSequence() ?: emptySequence())
|
||||||
|
.mapNotNull { layout -> layout.contractedChild as? ConversationLayout }
|
||||||
|
.forEach { convoLayout -> convoLayout.setUnreadCount(0) }
|
||||||
|
|
||||||
|
private data class ConversationState(val unreadCount: Int, val notification: Notification)
|
||||||
|
}
|
||||||
@@ -107,6 +107,7 @@ import com.android.systemui.statusbar.policy.InflatedSmartReplies.SmartRepliesAn
|
|||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.BooleanSupplier;
|
import java.util.function.BooleanSupplier;
|
||||||
@@ -136,7 +137,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
|
|||||||
*/
|
*/
|
||||||
public interface LayoutListener {
|
public interface LayoutListener {
|
||||||
void onLayout();
|
void onLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Listens for changes to the expansion state of this row. */
|
||||||
|
public interface OnExpansionChangedListener {
|
||||||
|
void onExpansionChanged(boolean isExpanded);
|
||||||
}
|
}
|
||||||
|
|
||||||
private StatusBarStateController mStatusbarStateController;
|
private StatusBarStateController mStatusbarStateController;
|
||||||
@@ -323,6 +328,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
|
|||||||
private boolean mWasChildInGroupWhenRemoved;
|
private boolean mWasChildInGroupWhenRemoved;
|
||||||
private NotificationInlineImageResolver mImageResolver;
|
private NotificationInlineImageResolver mImageResolver;
|
||||||
private NotificationMediaManager mMediaManager;
|
private NotificationMediaManager mMediaManager;
|
||||||
|
@Nullable private OnExpansionChangedListener mExpansionChangedListener;
|
||||||
|
|
||||||
private SystemNotificationAsyncTask mSystemNotificationAsyncTask =
|
private SystemNotificationAsyncTask mSystemNotificationAsyncTask =
|
||||||
new SystemNotificationAsyncTask();
|
new SystemNotificationAsyncTask();
|
||||||
@@ -351,6 +357,10 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
|
|||||||
return isSystemNotification;
|
return isSystemNotification;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public NotificationContentView[] getLayouts() {
|
||||||
|
return Arrays.copyOf(mLayouts, mLayouts.length);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isGroupExpansionChanging() {
|
public boolean isGroupExpansionChanging() {
|
||||||
if (isChildInGroup()) {
|
if (isChildInGroup()) {
|
||||||
@@ -2911,9 +2921,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
|
|||||||
if (mIsSummaryWithChildren) {
|
if (mIsSummaryWithChildren) {
|
||||||
mChildrenContainer.onExpansionChanged();
|
mChildrenContainer.onExpansionChanged();
|
||||||
}
|
}
|
||||||
|
if (mExpansionChangedListener != null) {
|
||||||
|
mExpansionChangedListener.onExpansionChanged(nowExpanded);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setOnExpansionChangedListener(@Nullable OnExpansionChangedListener listener) {
|
||||||
|
mExpansionChangedListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
|
public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
|
||||||
super.onInitializeAccessibilityNodeInfoInternal(info);
|
super.onInitializeAccessibilityNodeInfoInternal(info);
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController;
|
|||||||
import com.android.systemui.statusbar.VibratorHelper;
|
import com.android.systemui.statusbar.VibratorHelper;
|
||||||
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
|
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
|
||||||
import com.android.systemui.statusbar.notification.AnimatableProperty;
|
import com.android.systemui.statusbar.notification.AnimatableProperty;
|
||||||
|
import com.android.systemui.statusbar.notification.ConversationNotificationManager;
|
||||||
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
|
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
|
||||||
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||||
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
|
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
|
||||||
@@ -238,6 +239,7 @@ public class NotificationPanelViewController extends PanelViewController {
|
|||||||
private final PulseExpansionHandler mPulseExpansionHandler;
|
private final PulseExpansionHandler mPulseExpansionHandler;
|
||||||
private final KeyguardBypassController mKeyguardBypassController;
|
private final KeyguardBypassController mKeyguardBypassController;
|
||||||
private final KeyguardUpdateMonitor mUpdateMonitor;
|
private final KeyguardUpdateMonitor mUpdateMonitor;
|
||||||
|
private final ConversationNotificationManager mConversationNotificationManager;
|
||||||
|
|
||||||
private KeyguardAffordanceHelper mAffordanceHelper;
|
private KeyguardAffordanceHelper mAffordanceHelper;
|
||||||
private KeyguardUserSwitcher mKeyguardUserSwitcher;
|
private KeyguardUserSwitcher mKeyguardUserSwitcher;
|
||||||
@@ -451,7 +453,8 @@ public class NotificationPanelViewController extends PanelViewController {
|
|||||||
ActivityManager activityManager, ZenModeController zenModeController,
|
ActivityManager activityManager, ZenModeController zenModeController,
|
||||||
ConfigurationController configurationController,
|
ConfigurationController configurationController,
|
||||||
FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
|
FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
|
||||||
StatusBarTouchableRegionManager statusBarTouchableRegionManager) {
|
StatusBarTouchableRegionManager statusBarTouchableRegionManager,
|
||||||
|
ConversationNotificationManager conversationNotificationManager) {
|
||||||
super(view, falsingManager, dozeLog, keyguardStateController,
|
super(view, falsingManager, dozeLog, keyguardStateController,
|
||||||
(SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
|
(SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
|
||||||
latencyTracker, flingAnimationUtilsBuilder, statusBarTouchableRegionManager);
|
latencyTracker, flingAnimationUtilsBuilder, statusBarTouchableRegionManager);
|
||||||
@@ -509,6 +512,7 @@ public class NotificationPanelViewController extends PanelViewController {
|
|||||||
mShadeController = shadeController;
|
mShadeController = shadeController;
|
||||||
mLockscreenUserManager = notificationLockscreenUserManager;
|
mLockscreenUserManager = notificationLockscreenUserManager;
|
||||||
mEntryManager = notificationEntryManager;
|
mEntryManager = notificationEntryManager;
|
||||||
|
mConversationNotificationManager = conversationNotificationManager;
|
||||||
|
|
||||||
mView.setBackgroundColor(Color.TRANSPARENT);
|
mView.setBackgroundColor(Color.TRANSPARENT);
|
||||||
OnAttachStateChangeListener onAttachStateChangeListener = new OnAttachStateChangeListener();
|
OnAttachStateChangeListener onAttachStateChangeListener = new OnAttachStateChangeListener();
|
||||||
@@ -2143,6 +2147,7 @@ public class NotificationPanelViewController extends PanelViewController {
|
|||||||
super.onExpandingFinished();
|
super.onExpandingFinished();
|
||||||
mNotificationStackScroller.onExpansionStopped();
|
mNotificationStackScroller.onExpansionStopped();
|
||||||
mHeadsUpManager.onExpandingFinished();
|
mHeadsUpManager.onExpandingFinished();
|
||||||
|
mConversationNotificationManager.onNotificationPanelExpandStateChanged(isFullyCollapsed());
|
||||||
mIsExpanding = false;
|
mIsExpanding = false;
|
||||||
if (isFullyCollapsed()) {
|
if (isFullyCollapsed()) {
|
||||||
DejankUtils.postAfterTraversal(new Runnable() {
|
DejankUtils.postAfterTraversal(new Runnable() {
|
||||||
|
|||||||
@@ -188,6 +188,30 @@ public class NotificationUiAdjustmentTest extends SysuiTestCase {
|
|||||||
.isFalse();
|
.isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void needReinflate_bothConversation() {
|
||||||
|
assertThat(NotificationUiAdjustment.needReinflate(
|
||||||
|
createUiAdjustmentForConversation("first", true),
|
||||||
|
createUiAdjustmentForConversation("first", true)))
|
||||||
|
.isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void needReinflate_neitherConversation() {
|
||||||
|
assertThat(NotificationUiAdjustment.needReinflate(
|
||||||
|
createUiAdjustmentForConversation("first", false),
|
||||||
|
createUiAdjustmentForConversation("first", false)))
|
||||||
|
.isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void needReinflate_differentIsConversation() {
|
||||||
|
assertThat(NotificationUiAdjustment.needReinflate(
|
||||||
|
createUiAdjustmentForConversation("first", false),
|
||||||
|
createUiAdjustmentForConversation("first", true)))
|
||||||
|
.isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
private Notification.Action.Builder createActionBuilder(
|
private Notification.Action.Builder createActionBuilder(
|
||||||
String title, int drawableRes, PendingIntent pendingIntent) {
|
String title, int drawableRes, PendingIntent pendingIntent) {
|
||||||
return new Notification.Action.Builder(
|
return new Notification.Action.Builder(
|
||||||
@@ -200,11 +224,16 @@ public class NotificationUiAdjustmentTest extends SysuiTestCase {
|
|||||||
|
|
||||||
private NotificationUiAdjustment createUiAdjustmentFromSmartActions(
|
private NotificationUiAdjustment createUiAdjustmentFromSmartActions(
|
||||||
String key, List<Notification.Action> actions) {
|
String key, List<Notification.Action> actions) {
|
||||||
return new NotificationUiAdjustment(key, actions, null);
|
return new NotificationUiAdjustment(key, actions, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private NotificationUiAdjustment createUiAdjustmentFromSmartReplies(
|
private NotificationUiAdjustment createUiAdjustmentFromSmartReplies(
|
||||||
String key, CharSequence[] replies) {
|
String key, CharSequence[] replies) {
|
||||||
return new NotificationUiAdjustment(key, null, Arrays.asList(replies));
|
return new NotificationUiAdjustment(key, null, Arrays.asList(replies), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NotificationUiAdjustment createUiAdjustmentForConversation(
|
||||||
|
String key, boolean isConversation) {
|
||||||
|
return new NotificationUiAdjustment(key, null, null, isConversation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ import com.android.systemui.statusbar.PulseExpansionHandler;
|
|||||||
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
|
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
|
||||||
import com.android.systemui.statusbar.SysuiStatusBarStateController;
|
import com.android.systemui.statusbar.SysuiStatusBarStateController;
|
||||||
import com.android.systemui.statusbar.VibratorHelper;
|
import com.android.systemui.statusbar.VibratorHelper;
|
||||||
|
import com.android.systemui.statusbar.notification.ConversationNotificationManager;
|
||||||
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
|
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
|
||||||
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||||
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
|
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
|
||||||
@@ -169,6 +170,8 @@ public class NotificationPanelViewTest extends SysuiTestCase {
|
|||||||
private ZenModeController mZenModeController;
|
private ZenModeController mZenModeController;
|
||||||
@Mock
|
@Mock
|
||||||
private ConfigurationController mConfigurationController;
|
private ConfigurationController mConfigurationController;
|
||||||
|
@Mock
|
||||||
|
private ConversationNotificationManager mConversationNotificationManager;
|
||||||
private FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
|
private FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
|
||||||
|
|
||||||
private NotificationPanelViewController mNotificationPanelViewController;
|
private NotificationPanelViewController mNotificationPanelViewController;
|
||||||
@@ -223,7 +226,8 @@ public class NotificationPanelViewTest extends SysuiTestCase {
|
|||||||
mDozeParameters, mCommandQueue, mVibratorHelper,
|
mDozeParameters, mCommandQueue, mVibratorHelper,
|
||||||
mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
|
mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
|
||||||
mMetricsLogger, mActivityManager, mZenModeController, mConfigurationController,
|
mMetricsLogger, mActivityManager, mZenModeController, mConfigurationController,
|
||||||
mFlingAnimationUtilsBuilder, mStatusBarTouchableRegionManager);
|
mFlingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
|
||||||
|
mConversationNotificationManager);
|
||||||
mNotificationPanelViewController.initDependencies(mStatusBar, mGroupManager,
|
mNotificationPanelViewController.initDependencies(mStatusBar, mGroupManager,
|
||||||
mNotificationShelf, mNotificationAreaController, mScrimController);
|
mNotificationShelf, mNotificationAreaController, mScrimController);
|
||||||
mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
|
mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
|
||||||
|
|||||||
Reference in New Issue
Block a user