diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt index fb42c424f603a..fad0e49b36376 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.collection +import android.app.Notification import android.app.NotificationManager.IMPORTANCE_HIGH import android.app.NotificationManager.IMPORTANCE_MIN import android.service.notification.NotificationListenerService.Ranking @@ -25,6 +26,7 @@ import com.android.systemui.statusbar.NotificationMediaManager import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger import com.android.systemui.statusbar.notification.NotificationFilter import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager +import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy 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_NON_PERSON @@ -33,11 +35,11 @@ import com.android.systemui.statusbar.notification.stack.BUCKET_FOREGROUND_SERVI import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT import com.android.systemui.statusbar.notification.stack.PriorityBucket -import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy import com.android.systemui.statusbar.policy.HeadsUpManager import dagger.Lazy import java.util.Objects import javax.inject.Inject +import kotlin.Comparator private const val TAG = "NotifRankingManager" @@ -77,6 +79,9 @@ open class NotificationRankingManager @Inject constructor( val aIsFsn = a.isColorizedForegroundService() val bIsFsn = b.isColorizedForegroundService() + val aCall = a.isImportantCall() + val bCall = b.isImportantCall() + val aPersonType = a.getPeopleNotificationType() val bPersonType = b.getPeopleNotificationType() @@ -96,6 +101,7 @@ open class NotificationRankingManager @Inject constructor( // Provide consistent ranking with headsUpManager aHeadsUp -> headsUpManager.compare(a, b) aIsFsn != bIsFsn -> if (aIsFsn) -1 else 1 + aCall != bCall -> if (aCall) -1 else 1 usePeopleFiltering && aPersonType != bPersonType -> peopleNotificationIdentifier.compareTo(aPersonType, bPersonType) // Upsort current media notification. @@ -150,11 +156,12 @@ open class NotificationRankingManager @Inject constructor( @PriorityBucket private fun getBucketForEntry(entry: NotificationEntry): Int { + val isImportantCall = entry.isImportantCall() val isHeadsUp = entry.isRowHeadsUp val isMedia = entry.isImportantMedia() val isSystemMax = entry.isSystemMax() return when { - entry.isColorizedForegroundService() -> BUCKET_FOREGROUND_SERVICE + entry.isColorizedForegroundService() || isImportantCall -> BUCKET_FOREGROUND_SERVICE usePeopleFiltering && entry.isConversation() -> BUCKET_PEOPLE isHeadsUp || isMedia || isSystemMax || entry.isHighPriority() -> BUCKET_ALERTING else -> BUCKET_SILENT @@ -186,7 +193,7 @@ open class NotificationRankingManager @Inject constructor( } private fun NotificationEntry.isImportantMedia() = - key == mediaManager.mediaNotificationKey && ranking.importance > IMPORTANCE_MIN + key == mediaManager.mediaNotificationKey && importance > IMPORTANCE_MIN private fun NotificationEntry.isConversation() = getPeopleNotificationType() != TYPE_NON_PERSON @@ -204,6 +211,10 @@ private fun NotificationEntry.isSystemMax() = private fun StatusBarNotification.isSystemNotification() = "android" == packageName || "com.android.systemui" == packageName +private fun NotificationEntry.isImportantCall() = + sbn.notification.extras?.getString(Notification.EXTRA_TEMPLATE) == + "android.app.Notification\$CallStyle" && importance > IMPORTANCE_MIN + private fun NotificationEntry.isColorizedForegroundService() = sbn.notification.run { isForegroundService && isColorized && importance > IMPORTANCE_MIN } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt index c832fe481f740..bdd4a01a97663 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt @@ -21,6 +21,8 @@ import android.app.NotificationChannel import android.app.NotificationManager.IMPORTANCE_DEFAULT import android.app.NotificationManager.IMPORTANCE_HIGH import android.app.NotificationManager.IMPORTANCE_LOW +import android.app.PendingIntent +import android.app.Person import android.os.SystemClock import android.service.notification.NotificationListenerService.RankingMap import android.testing.AndroidTestingRunner @@ -408,6 +410,74 @@ class NotificationRankingManagerTest : SysuiTestCase() { assertThat(b.bucket).isEqualTo(BUCKET_FOREGROUND_SERVICE) } + @Test + fun testSort_importantCall() { + whenever(sectionsManager.isFilteringEnabled()).thenReturn(true) + + val a = NotificationEntryBuilder() + .setImportance(IMPORTANCE_HIGH) + .setPkg("pkg") + .setOpPkg("pkg") + .setTag("tag") + .setNotification( + Notification.Builder(mContext, "test") + .build()) + .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT)) + .setUser(mContext.getUser()) + .setOverrideGroupKey("") + .build() + + val b = NotificationEntryBuilder() + .setImportance(IMPORTANCE_DEFAULT) // high priority + .setPkg("pkg2") + .setOpPkg("pkg2") + .setTag("tag") + .setNotification(mock(Notification::class.java).also { notif -> + whenever(notif.isForegroundService).thenReturn(true) + whenever(notif.isColorized).thenReturn(true) + }) + .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT)) + .setUser(mContext.getUser()) + .setOverrideGroupKey("") + .build() + + val cN = Notification.Builder(mContext, "test") + .setStyle(Notification.MessagingStyle("")) + .build() + val c = NotificationEntryBuilder() + .setImportance(IMPORTANCE_HIGH) + .setPkg("pkg") + .setOpPkg("pkg") + .setTag("tag") + .setNotification(cN) + .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT)) + .setUser(mContext.user) + .setOverrideGroupKey("") + .build() + + val dN = Notification.Builder(mContext, "test") + .setStyle(Notification.CallStyle.forOngoingCall( + Person.Builder().setName("caller").build(), + mock(PendingIntent::class.java))) + .build() + val d = NotificationEntryBuilder() + .setImportance(IMPORTANCE_DEFAULT) // high priority + .setPkg("pkg2") + .setOpPkg("pkg2") + .setTag("tag") + .setNotification(dN) + .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT)) + .setUser(mContext.user) + .setOverrideGroupKey("") + .build() + whenever(personNotificationIdentifier.getPeopleNotificationType(a)) + .thenReturn(TYPE_IMPORTANT_PERSON) + + assertThat(rankingManager.updateRanking(null, listOf(a, b, c, d), "test")) + .containsExactly(b, d, c, a) + assertThat(d.bucket).isEqualTo(BUCKET_FOREGROUND_SERVICE) + } + internal class TestableNotificationRankingManager( mediaManager: Lazy, groupManager: NotificationGroupManagerLegacy, diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java index 29b5e81447f80..78219bcb90110 100644 --- a/services/core/java/com/android/server/notification/NotificationComparator.java +++ b/services/core/java/com/android/server/notification/NotificationComparator.java @@ -147,15 +147,16 @@ public class NotificationComparator } private boolean isImportantOngoing(NotificationRecord record) { - if (!isOngoing(record)) { - return false; - } - if (record.getImportance() < NotificationManager.IMPORTANCE_LOW) { return false; } - - return isCall(record) || isMediaNotification(record); + if (isCallStyle(record)) { + return true; + } + if (!isOngoing(record)) { + return false; + } + return isCallCategory(record) || isMediaNotification(record); } protected boolean isImportantPeople(NotificationRecord record) { @@ -181,11 +182,16 @@ public class NotificationComparator return record.getNotification().hasMediaSession(); } - private boolean isCall(NotificationRecord record) { + private boolean isCallCategory(NotificationRecord record) { return record.isCategory(Notification.CATEGORY_CALL) && isDefaultPhoneApp(record.getSbn().getPackageName()); } + private boolean isCallStyle(NotificationRecord record) { + return "android.app.Notification$CallStyle".equals( + record.getNotification().extras.getString(Notification.EXTRA_TEMPLATE)); + } + private boolean isDefaultPhoneApp(String pkg) { if (mDefaultPhoneApp == null) { final TelecomManager telecomm = diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 1c50d41e45d30..a71722c93d48a 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -6570,6 +6570,17 @@ public class NotificationManagerService extends SystemService { } } + if ("android.app.Notification$CallStyle".equals( + n.extras.getString(Notification.EXTRA_TEMPLATE))) { + boolean isForegroundService = (n.flags & FLAG_FOREGROUND_SERVICE) != 0; + boolean hasFullScreenIntent = n.fullScreenIntent != null; + if (!isForegroundService && !hasFullScreenIntent) { + throw new IllegalArgumentException(r.getKey() + " Not posted." + + " CallStyle notifications must either be for a foreground Service or" + + " use a fullScreenIntent."); + } + } + // snoozed apps if (mSnoozeHelper.isSnoozed(userId, pkg, r.getKey())) { MetricsLogger.action(r.getLogMaker() diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java index 9433bf28e2374..6a8f602a8350e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java @@ -22,12 +22,15 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Person; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -72,6 +75,7 @@ public class NotificationComparatorTest extends UiServiceTestCase { private NotificationRecord mRecordMinCallNonInterruptive; private NotificationRecord mRecordMinCall; private NotificationRecord mRecordHighCall; + private NotificationRecord mRecordHighCallStyle; private NotificationRecord mRecordEmail; private NotificationRecord mRecordInlineReply; private NotificationRecord mRecordSms; @@ -90,9 +94,12 @@ public class NotificationComparatorTest extends UiServiceTestCase { int userId = UserHandle.myUserId(); when(mContext.getResources()).thenReturn(getContext().getResources()); + when(mContext.getTheme()).thenReturn(getContext().getTheme()); when(mContext.getContentResolver()).thenReturn(getContext().getContentResolver()); when(mContext.getPackageManager()).thenReturn(mPm); when(mContext.getSystemService(eq(Context.TELECOM_SERVICE))).thenReturn(mTm); + when(mContext.getString(anyInt())).thenCallRealMethod(); + when(mContext.getColor(anyInt())).thenCallRealMethod(); when(mTm.getDefaultDialerPackage()).thenReturn(callPkg); final ApplicationInfo legacy = new ApplicationInfo(); legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1; @@ -137,6 +144,19 @@ public class NotificationComparatorTest extends UiServiceTestCase { new UserHandle(userId), "", 1999), getDefaultChannel()); mRecordHighCall.setSystemImportance(NotificationManager.IMPORTANCE_HIGH); + Notification nHighCallStyle = new Notification.Builder(mContext, TEST_CHANNEL_ID) + .setStyle(Notification.CallStyle.forOngoingCall( + new Person.Builder().setName("caller").build(), + mock(PendingIntent.class) + )) + .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) + .build(); + mRecordHighCallStyle = new NotificationRecord(mContext, new StatusBarNotification(callPkg, + callPkg, 1, "highCallStyle", callUid, callUid, nHighCallStyle, + new UserHandle(userId), "", 2000), getDefaultChannel()); + mRecordHighCallStyle.setSystemImportance(NotificationManager.IMPORTANCE_HIGH); + mRecordHighCallStyle.setInterruptive(true); + Notification n4 = new Notification.Builder(mContext, TEST_CHANNEL_ID) .setStyle(new Notification.MessagingStyle("sender!")).build(); mRecordInlineReply = new NotificationRecord(mContext, new StatusBarNotification(pkg2, @@ -236,6 +256,7 @@ public class NotificationComparatorTest extends UiServiceTestCase { final List expected = new ArrayList<>(); expected.add(mRecordColorizedCall); expected.add(mRecordColorized); + expected.add(mRecordHighCallStyle); expected.add(mRecordHighCall); expected.add(mRecordInlineReply); if (mRecordSms != null) {