diff options
7 files changed, 128 insertions, 0 deletions
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index f14e1f63cdf6..ec0954d5590a 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -239,4 +239,7 @@ interface IStatusBarService /** Unbundle a categorized notification */ void unbundleNotification(String key); + + /** Rebundle an (un)categorized notification */ + void rebundleNotification(String key); } diff --git a/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt b/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt index 25d1c377ecbd..7ed736158a53 100644 --- a/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt +++ b/packages/SystemUI/tests/utils/src/android/internal/statusbar/FakeStatusBarService.kt @@ -435,6 +435,8 @@ class FakeStatusBarService : IStatusBarService.Stub() { override fun unbundleNotification(key: String) {} + override fun rebundleNotification(key: String) {} + companion object { const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY const val SECONDARY_DISPLAY_ID = 2 diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java index 7cbbe2938fd5..5a425057ea89 100644 --- a/services/core/java/com/android/server/notification/NotificationDelegate.java +++ b/services/core/java/com/android/server/notification/NotificationDelegate.java @@ -107,4 +107,9 @@ public interface NotificationDelegate { * @param key the notification key */ void unbundleNotification(String key); + /** + * Called when the notification should be rebundled. + * @param key the notification key + */ + void rebundleNotification(String key); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 87930fd421ee..341038f878d9 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1888,6 +1888,36 @@ public class NotificationManagerService extends SystemService { } } } + + @Override + public void rebundleNotification(String key) { + if (!(notificationClassification() && notificationRegroupOnClassification())) { + return; + } + synchronized (mNotificationLock) { + NotificationRecord r = mNotificationsByKey.get(key); + if (r == null) { + return; + } + + if (DBG) { + Slog.v(TAG, "rebundleNotification: " + r); + } + + if (r.getBundleType() != Adjustment.TYPE_OTHER) { + final Bundle classifBundle = new Bundle(); + classifBundle.putInt(KEY_TYPE, r.getBundleType()); + Adjustment adj = new Adjustment(r.getSbn().getPackageName(), r.getKey(), + classifBundle, "rebundle", r.getUserId()); + applyAdjustmentLocked(r, adj, /* isPosted= */ true); + mRankingHandler.requestSort(); + } else { + if (DBG) { + Slog.w(TAG, "Can't rebundle. No valid bundle type for: " + r); + } + } + } + } }; NotificationManagerPrivate mNotificationManagerPrivate = new NotificationManagerPrivate() { @@ -7134,6 +7164,7 @@ public class NotificationManagerService extends SystemService { adjustments.putParcelable(KEY_TYPE, newChannel); logClassificationChannelAdjustmentReceived(r, isPosted, classification); + r.setBundleType(classification); } } r.addAdjustment(adjustment); diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 0bb3c6a067e3..81af0d8a6d80 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -222,6 +222,9 @@ public final class NotificationRecord { // lifetime extended. private boolean mCanceledAfterLifetimeExtension = false; + // type of the bundle if the notification was classified + private @Adjustment.Types int mBundleType = Adjustment.TYPE_OTHER; + public NotificationRecord(Context context, StatusBarNotification sbn, NotificationChannel channel) { this.sbn = sbn; @@ -467,6 +470,10 @@ public final class NotificationRecord { } } + if (android.service.notification.Flags.notificationClassification()) { + mBundleType = previous.mBundleType; + } + // Don't copy importance information or mGlobalSortKey, recompute them. } @@ -1629,6 +1636,14 @@ public final class NotificationRecord { mCanceledAfterLifetimeExtension = canceledAfterLifetimeExtension; } + public @Adjustment.Types int getBundleType() { + return mBundleType; + } + + public void setBundleType(@Adjustment.Types int bundleType) { + mBundleType = bundleType; + } + /** * Whether this notification is a conversation notification. */ diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 4ed5f90f2852..a19a3422af06 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -2199,6 +2199,19 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D }); } + /** + * Called when the notification should be rebundled. + * @param key the notification key + */ + @Override + public void rebundleNotification(String key) { + enforceStatusBarService(); + enforceValidCallingUser(); + Binder.withCleanCallingIdentity(() -> { + mNotificationDelegate.rebundleNotification(key); + }); + } + @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 8a39e0ecdc07..e43b28bb9404 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -17921,4 +17921,63 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mGroupHelper, times(1)).onNotificationUnbundled(eq(r1), eq(hasOriginalSummary)); } + @Test + @EnableFlags({FLAG_NOTIFICATION_CLASSIFICATION, + FLAG_NOTIFICATION_FORCE_GROUPING, + FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION}) + public void testRebundleNotification_restoresBundleChannel() throws Exception { + NotificationManagerService.WorkerHandler handler = mock( + NotificationManagerService.WorkerHandler.class); + mService.setHandler(handler); + when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); + when(mAssistants.isServiceTokenValidLocked(any())).thenReturn(true); + when(mAssistants.isAdjustmentKeyTypeAllowed(anyInt())).thenReturn(true); + when(mAssistants.isTypeAdjustmentAllowedForPackage(anyString(), anyInt())).thenReturn(true); + + // Post a single notification + final boolean hasOriginalSummary = false; + final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); + final String keyToUnbundle = r.getKey(); + mService.addNotification(r); + + // Classify notification into the NEWS bundle + Bundle signals = new Bundle(); + signals.putInt(Adjustment.KEY_TYPE, Adjustment.TYPE_NEWS); + Adjustment adjustment = new Adjustment( + r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); + mBinderService.applyAdjustmentFromAssistant(null, adjustment); + waitForIdle(); + r.applyAdjustments(); + // Check that the NotificationRecord channel is updated + assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID); + assertThat(r.getBundleType()).isEqualTo(Adjustment.TYPE_NEWS); + + // Unbundle the notification + mService.mNotificationDelegate.unbundleNotification(keyToUnbundle); + + // Check that the original channel was restored + assertThat(r.getChannel().getId()).isEqualTo(TEST_CHANNEL_ID); + assertThat(r.getBundleType()).isEqualTo(Adjustment.TYPE_NEWS); + verify(mGroupHelper, times(1)).onNotificationUnbundled(eq(r), eq(hasOriginalSummary)); + + Mockito.reset(mRankingHandler); + Mockito.reset(mGroupHelper); + + // Rebundle the notification + mService.mNotificationDelegate.rebundleNotification(keyToUnbundle); + + // Actually apply the adjustments + doAnswer(invocationOnMock -> { + ((NotificationRecord) invocationOnMock.getArguments()[0]).applyAdjustments(); + ((NotificationRecord) invocationOnMock.getArguments()[0]).calculateImportance(); + return null; + }).when(mRankingHelper).extractSignals(any(NotificationRecord.class)); + mService.handleRankingSort(); + verify(handler, times(1)).scheduleSendRankingUpdate(); + + // Check that the bundle channel was restored + verify(mRankingHandler, times(1)).requestSort(); + assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID); + } + } |