Merge "Treat groups of conversations as a conversation" into rvc-dev

This commit is contained in:
Steve Elliott
2020-03-05 22:59:32 +00:00
committed by Android (Google) Code Review
8 changed files with 185 additions and 89 deletions

View File

@@ -27,6 +27,9 @@ import com.android.systemui.statusbar.notification.NotificationFilter
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_HEADS_UP
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_PEOPLE
@@ -72,11 +75,14 @@ open class NotificationRankingManager @Inject constructor(
val aRank = a.ranking.rank
val bRank = b.ranking.rank
val aIsPeople = a.isPeopleNotification()
val bIsPeople = b.isPeopleNotification()
val aPersonType = a.getPeopleNotificationType()
val bPersonType = b.getPeopleNotificationType()
val aIsImportantPeople = a.isImportantPeopleNotification()
val bIsImportantPeople = b.isImportantPeopleNotification()
val aIsPeople = aPersonType == TYPE_PERSON
val bIsPeople = bPersonType == TYPE_PERSON
val aIsImportantPeople = aPersonType == TYPE_IMPORTANT_PERSON
val bIsImportantPeople = bPersonType == TYPE_IMPORTANT_PERSON
val aMedia = isImportantMedia(a)
val bMedia = isImportantMedia(b)
@@ -165,7 +171,7 @@ open class NotificationRankingManager @Inject constructor(
) {
if (usePeopleFiltering && isHeadsUp) {
entry.bucket = BUCKET_HEADS_UP
} else if (usePeopleFiltering && entry.isPeopleNotification()) {
} else if (usePeopleFiltering && entry.getPeopleNotificationType() != TYPE_NON_PERSON) {
entry.bucket = BUCKET_PEOPLE
} else if (isHeadsUp || isMedia || isSystemMax || entry.isHighPriority()) {
entry.bucket = BUCKET_ALERTING
@@ -198,11 +204,8 @@ open class NotificationRankingManager @Inject constructor(
}
}
private fun NotificationEntry.isPeopleNotification() =
peopleNotificationIdentifier.isPeopleNotification(sbn, ranking)
private fun NotificationEntry.isImportantPeopleNotification() =
peopleNotificationIdentifier.isImportantPeopleNotification(sbn, ranking)
private fun NotificationEntry.getPeopleNotificationType() =
peopleNotificationIdentifier.getPeopleNotificationType(sbn, ranking)
private fun NotificationEntry.isHighPriority() =
highPriorityProvider.isHighPriority(this)

View File

@@ -103,8 +103,8 @@ public class HighPriorityProvider {
}
private boolean isPeopleNotification(NotificationEntry entry) {
return mPeopleNotificationIdentifier.isPeopleNotification(
entry.getSbn(), entry.getRanking());
return mPeopleNotificationIdentifier.getPeopleNotificationType(
entry.getSbn(), entry.getRanking()) != PeopleNotificationIdentifier.TYPE_NON_PERSON;
}
private boolean hasUserSetImportance(NotificationEntry entry) {

View File

@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification.people
import android.app.PendingIntent
import android.graphics.drawable.Drawable
/**
@@ -45,10 +44,11 @@ data class PeopleHubModel(val people: Collection<PersonModel>)
/** `Model` for a single "Person" in PeopleHub. */
data class PersonModel(
val key: PersonKey,
val userId: Int,
// TODO: these should live in the ViewModel
val name: CharSequence,
val avatar: Drawable,
val clickRunnable: Runnable,
val userId: Int
val clickRunnable: Runnable
)
/** Unique identifier for a Person in PeopleHub. */

View File

@@ -20,9 +20,7 @@ import android.app.Notification
import android.content.Context
import android.content.pm.LauncherApps
import android.content.pm.PackageManager
import android.content.pm.ShortcutInfo
import android.content.pm.UserInfo
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.os.UserManager
import android.service.notification.NotificationListenerService
@@ -45,6 +43,7 @@ import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.notification.NotificationEntryListener
import com.android.systemui.statusbar.notification.NotificationEntryManager
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
import com.android.systemui.statusbar.policy.ExtensionController
import java.util.ArrayDeque
import java.util.concurrent.Executor
@@ -79,7 +78,7 @@ class NotificationPersonExtractorPluginBoundary @Inject constructor(
override fun extractPerson(sbn: StatusBarNotification) =
plugin?.extractPerson(sbn)?.run {
PersonModel(key, name, avatar, clickRunnable, sbn.user.identifier)
PersonModel(key, sbn.user.identifier, name, avatar, clickRunnable)
}
override fun extractPersonKey(sbn: StatusBarNotification) = plugin?.extractPersonKey(sbn)
@@ -90,26 +89,35 @@ class NotificationPersonExtractorPluginBoundary @Inject constructor(
@Singleton
class PeopleHubDataSourceImpl @Inject constructor(
private val notificationEntryManager: NotificationEntryManager,
private val extractor: NotificationPersonExtractor,
private val userManager: UserManager,
private val launcherApps: LauncherApps,
private val packageManager: PackageManager,
private val c: Context,
private val notificationListener: NotificationListener,
@Background private val bgExecutor: Executor,
@Main private val mainExecutor: Executor,
private val notifLockscreenUserMgr: NotificationLockscreenUserManager,
private val peopleNotificationIdentifier: PeopleNotificationIdentifier
) : DataSource<PeopleHubModel> {
private val notificationEntryManager: NotificationEntryManager,
private val extractor: NotificationPersonExtractor,
private val userManager: UserManager,
launcherApps: LauncherApps,
packageManager: PackageManager,
context: Context,
private val notificationListener: NotificationListener,
@Background private val bgExecutor: Executor,
@Main private val mainExecutor: Executor,
private val notifLockscreenUserMgr: NotificationLockscreenUserManager,
private val peopleNotificationIdentifier: PeopleNotificationIdentifier
) : DataSource<PeopleHubModel> {
private var userChangeSubscription: Subscription? = null
private val dataListeners = mutableListOf<DataListener<PeopleHubModel>>()
private val peopleHubManagerForUser = SparseArray<PeopleHubManager>()
val context: Context = c.applicationContext
val iconFactory = ConversationIconFactory(context, launcherApps, packageManager,
IconDrawableFactory.newInstance(context), context.resources.getDimensionPixelSize(
R.dimen.notification_guts_conversation_icon_size))
private val iconFactory = run {
val appContext = context.applicationContext
ConversationIconFactory(
appContext,
launcherApps,
packageManager,
IconDrawableFactory.newInstance(appContext),
appContext.resources.getDimensionPixelSize(
R.dimen.notification_guts_conversation_icon_size
)
)
}
private val notificationEntryListener = object : NotificationEntryListener {
override fun onEntryInflated(entry: NotificationEntry) = addVisibleEntry(entry)
@@ -206,7 +214,8 @@ class PeopleHubDataSourceImpl @Inject constructor(
}
private fun NotificationEntry.extractPerson(): PersonModel? {
if (!peopleNotificationIdentifier.isPeopleNotification(sbn, ranking)) {
val type = peopleNotificationIdentifier.getPeopleNotificationType(sbn, ranking)
if (type == TYPE_NON_PERSON) {
return null
}
val clickRunnable = Runnable { notificationListener.unsnoozeNotification(key) }
@@ -215,23 +224,34 @@ class PeopleHubDataSourceImpl @Inject constructor(
?: extras.getString(Notification.EXTRA_CONVERSATION_TITLE)
?: extras.getString(Notification.EXTRA_TITLE)
?: return null
val drawable = ranking.shortcutInfo?.getIcon(iconFactory, sbn, ranking)
?: iconFactory.getConversationDrawable(extractAvatarFromRow(this), sbn.packageName,
sbn.uid, ranking.channel.isImportantConversation)
return PersonModel(key, name, drawable, clickRunnable, sbn.user.identifier)
val drawable = ranking.getIcon(iconFactory, sbn)
?: iconFactory.getConversationDrawable(
extractAvatarFromRow(this),
sbn.packageName,
sbn.uid,
ranking.channel.isImportantConversation
)
return PersonModel(key, sbn.user.identifier, name, drawable, clickRunnable)
}
private fun ShortcutInfo.getIcon(iconFactory: ConversationIconFactory,
sbn: StatusBarNotification,
ranking: NotificationListenerService.Ranking): Drawable? {
return iconFactory.getConversationDrawable(ranking.shortcutInfo, sbn.packageName, sbn.uid,
ranking.channel.isImportantConversation)
}
private fun NotificationListenerService.Ranking.getIcon(
iconFactory: ConversationIconFactory,
sbn: StatusBarNotification
): Drawable? =
shortcutInfo?.let { shortcutInfo ->
iconFactory.getConversationDrawable(
shortcutInfo,
sbn.packageName,
sbn.uid,
channel.isImportantConversation
)
}
private fun NotificationEntry.extractPersonKey(): PersonKey? =
// TODO migrate to shortcut id when snoozing is conversation wide
if (peopleNotificationIdentifier.isPeopleNotification(sbn, ranking)) key else null
private fun NotificationEntry.extractPersonKey(): PersonKey? {
// TODO migrate to shortcut id when snoozing is conversation wide
val type = peopleNotificationIdentifier.getPeopleNotificationType(sbn, ranking)
return if (type != TYPE_NON_PERSON) key else null
}
}
private fun NotificationLockscreenUserManager.registerListener(
@@ -303,4 +323,3 @@ fun extractAvatarFromRow(entry: NotificationEntry): Drawable? =
?.drawable
}
?.firstOrNull()

View File

@@ -16,24 +16,97 @@
package com.android.systemui.statusbar.notification.people
import android.annotation.IntDef
import android.service.notification.NotificationListenerService.Ranking
import android.service.notification.StatusBarNotification
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
import com.android.systemui.statusbar.phone.NotificationGroupManager
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.max
interface PeopleNotificationIdentifier {
fun isPeopleNotification(sbn: StatusBarNotification, ranking: Ranking): Boolean
fun isImportantPeopleNotification(sbn: StatusBarNotification, ranking: Ranking): Boolean
/**
* Identifies if the given notification can be classified as a "People" notification.
*
* @return [TYPE_NON_PERSON] if not a people notification, [TYPE_PERSON] if a standard people
* notification, and [TYPE_IMPORTANT_PERSON] if an "important" people notification.
*/
@PeopleNotificationType
fun getPeopleNotificationType(sbn: StatusBarNotification, ranking: Ranking): Int
companion object {
@Retention(AnnotationRetention.SOURCE)
@IntDef(prefix = ["TYPE_"], value = [TYPE_NON_PERSON, TYPE_PERSON, TYPE_IMPORTANT_PERSON])
annotation class PeopleNotificationType
const val TYPE_NON_PERSON = 0
const val TYPE_PERSON = 1
const val TYPE_IMPORTANT_PERSON = 2
}
}
@Singleton
class PeopleNotificationIdentifierImpl @Inject constructor(
private val personExtractor: NotificationPersonExtractor
private val personExtractor: NotificationPersonExtractor,
private val groupManager: NotificationGroupManager
) : PeopleNotificationIdentifier {
override fun isPeopleNotification(sbn: StatusBarNotification, ranking: Ranking) =
ranking.isConversation || personExtractor.isPersonNotification(sbn)
@PeopleNotificationType
override fun getPeopleNotificationType(sbn: StatusBarNotification, ranking: Ranking): Int =
when (val type = ranking.personTypeInfo) {
TYPE_IMPORTANT_PERSON -> TYPE_IMPORTANT_PERSON
else -> {
when (val type = upperBound(type, extractPersonTypeInfo(sbn))) {
TYPE_IMPORTANT_PERSON -> TYPE_IMPORTANT_PERSON
else -> upperBound(type, getPeopleTypeOfSummary(sbn))
}
}
}
override fun isImportantPeopleNotification(sbn: StatusBarNotification, ranking: Ranking) =
isPeopleNotification(sbn, ranking) && ranking.channel.isImportantConversation
}
/**
* Given two [PeopleNotificationType]s, determine the upper bound. Used to constrain a
* notification to a type given multiple signals, i.e. notification groups, where each child
* has a [PeopleNotificationType] that is used to constrain the summary.
*/
@PeopleNotificationType
private fun upperBound(
@PeopleNotificationType type: Int,
@PeopleNotificationType other: Int
): Int =
max(type, other)
private val Ranking.personTypeInfo
get() = when {
channel.isImportantConversation -> TYPE_IMPORTANT_PERSON
isConversation -> TYPE_PERSON
else -> TYPE_NON_PERSON
}
private fun extractPersonTypeInfo(sbn: StatusBarNotification) =
if (personExtractor.isPersonNotification(sbn)) TYPE_PERSON else TYPE_NON_PERSON
private fun getPeopleTypeOfSummary(statusBarNotification: StatusBarNotification): Int {
if (!groupManager.isSummaryOfGroup(statusBarNotification)) {
return TYPE_NON_PERSON
}
val childTypes = groupManager.getLogicalChildren(statusBarNotification)
?.asSequence()
?.map { getPeopleNotificationType(it.sbn, it.ranking) }
?: return TYPE_NON_PERSON
var groupType = TYPE_NON_PERSON
for (childType in childTypes) {
groupType = upperBound(groupType, childType)
if (groupType == TYPE_IMPORTANT_PERSON)
break
}
return groupType
}
}

View File

@@ -20,6 +20,9 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MIN;
import static com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.TYPE_NON_PERSON;
import static com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.TYPE_PERSON;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
@@ -60,8 +63,9 @@ public class HighPriorityProviderTest extends SysuiTestCase {
final NotificationEntry entry = new NotificationEntryBuilder()
.setImportance(IMPORTANCE_HIGH)
.build();
when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getRanking()))
.thenReturn(false);
when(mPeopleNotificationIdentifier
.getPeopleNotificationType(entry.getSbn(), entry.getRanking()))
.thenReturn(TYPE_NON_PERSON);
// THEN it has high priority
assertTrue(mHighPriorityProvider.isHighPriority(entry));
@@ -76,8 +80,9 @@ public class HighPriorityProviderTest extends SysuiTestCase {
.setNotification(notification)
.setImportance(IMPORTANCE_LOW)
.build();
when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getRanking()))
.thenReturn(true);
when(mPeopleNotificationIdentifier
.getPeopleNotificationType(entry.getSbn(), entry.getRanking()))
.thenReturn(TYPE_PERSON);
// THEN it has high priority
assertTrue(mHighPriorityProvider.isHighPriority(entry));
@@ -92,8 +97,9 @@ public class HighPriorityProviderTest extends SysuiTestCase {
final NotificationEntry entry = new NotificationEntryBuilder()
.setNotification(notification)
.build();
when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getRanking()))
.thenReturn(false);
when(mPeopleNotificationIdentifier
.getPeopleNotificationType(entry.getSbn(), entry.getRanking()))
.thenReturn(TYPE_NON_PERSON);
// THEN it has high priority
assertTrue(mHighPriorityProvider.isHighPriority(entry));
@@ -109,8 +115,9 @@ public class HighPriorityProviderTest extends SysuiTestCase {
.setNotification(notification)
.setImportance(IMPORTANCE_LOW)
.build();
when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getRanking()))
.thenReturn(false);
when(mPeopleNotificationIdentifier
.getPeopleNotificationType(entry.getSbn(), entry.getRanking()))
.thenReturn(TYPE_NON_PERSON);
// THEN it has high priority
assertTrue(mHighPriorityProvider.isHighPriority(entry));
@@ -126,8 +133,9 @@ public class HighPriorityProviderTest extends SysuiTestCase {
.setNotification(notification)
.setImportance(IMPORTANCE_MIN)
.build();
when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getRanking()))
.thenReturn(false);
when(mPeopleNotificationIdentifier
.getPeopleNotificationType(entry.getSbn(), entry.getRanking()))
.thenReturn(TYPE_NON_PERSON);
// THEN it does NOT have high priority
assertFalse(mHighPriorityProvider.isHighPriority(entry));
@@ -149,8 +157,9 @@ public class HighPriorityProviderTest extends SysuiTestCase {
.setNotification(notification)
.setChannel(channel)
.build();
when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getRanking()))
.thenReturn(true);
when(mPeopleNotificationIdentifier
.getPeopleNotificationType(entry.getSbn(), entry.getRanking()))
.thenReturn(TYPE_PERSON);
// THEN it does NOT have high priority
assertFalse(mHighPriorityProvider.isHighPriority(entry));

View File

@@ -32,6 +32,8 @@ import com.android.systemui.statusbar.notification.NotificationFilter
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_SILENT
@@ -166,10 +168,8 @@ class NotificationRankingManagerTest : SysuiTestCase() {
.setOverrideGroupKey("")
.build()
whenever(personNotificationIdentifier.isImportantPeopleNotification(a.sbn, a.ranking))
.thenReturn(true)
whenever(personNotificationIdentifier.isPeopleNotification(a.sbn, a.ranking))
.thenReturn(true)
whenever(personNotificationIdentifier.getPeopleNotificationType(a.sbn, a.ranking))
.thenReturn(TYPE_IMPORTANT_PERSON)
val bN = Notification.Builder(mContext, "test")
.setStyle(Notification.MessagingStyle(""))
@@ -188,10 +188,8 @@ class NotificationRankingManagerTest : SysuiTestCase() {
whenever(it.isHeadsUp).thenReturn(true)
}
whenever(personNotificationIdentifier.isImportantPeopleNotification(a.sbn, a.ranking))
.thenReturn(false)
whenever(personNotificationIdentifier.isPeopleNotification(a.sbn, a.ranking))
.thenReturn(false)
whenever(personNotificationIdentifier.getPeopleNotificationType(a.sbn, a.ranking))
.thenReturn(TYPE_PERSON)
assertEquals(listOf(b, a), rankingManager.updateRanking(null, listOf(a, b), "test"))
}
@@ -213,10 +211,8 @@ class NotificationRankingManagerTest : SysuiTestCase() {
.setUser(mContext.user)
.setOverrideGroupKey("")
.build()
whenever(personNotificationIdentifier.isImportantPeopleNotification(a.sbn, a.ranking))
.thenReturn(false)
whenever(personNotificationIdentifier.isPeopleNotification(a.sbn, a.ranking))
.thenReturn(true)
whenever(personNotificationIdentifier.getPeopleNotificationType(a.sbn, a.ranking))
.thenReturn(TYPE_PERSON)
val bN = Notification.Builder(mContext, "test")
.setStyle(Notification.MessagingStyle(""))
@@ -231,10 +227,8 @@ class NotificationRankingManagerTest : SysuiTestCase() {
.setUser(mContext.user)
.setOverrideGroupKey("")
.build()
whenever(personNotificationIdentifier.isImportantPeopleNotification(b.sbn, b.ranking))
.thenReturn(true)
whenever(personNotificationIdentifier.isPeopleNotification(b.sbn, b.ranking))
.thenReturn(true)
whenever(personNotificationIdentifier.getPeopleNotificationType(b.sbn, b.ranking))
.thenReturn(TYPE_IMPORTANT_PERSON)
assertEquals(
listOf(b, a),

View File

@@ -16,8 +16,6 @@
package com.android.systemui.statusbar.notification.people
import android.app.PendingIntent
import android.content.Intent
import android.graphics.drawable.Drawable
import android.testing.AndroidTestingRunner
import android.view.View
@@ -151,7 +149,7 @@ private fun fakePersonModel(
clickRunnable: Runnable,
userId: Int = 0
): PersonModel =
PersonModel(id, name, mock(Drawable::class.java), clickRunnable, userId)
PersonModel(id, userId, name, mock(Drawable::class.java), clickRunnable)
private fun fakePersonViewModel(name: CharSequence): PersonViewModel =
PersonViewModel(name, mock(Drawable::class.java), mock({}.javaClass))