Loading services/core/java/com/android/server/notification/NotificationManagerService.java +100 −63 Original line number Diff line number Diff line Loading @@ -421,6 +421,7 @@ public class NotificationManagerService extends SystemService { private IActivityManager mAm; private ActivityTaskManagerInternal mAtm; private ActivityManager mActivityManager; private ActivityManagerInternal mAmi; private IPackageManager mPackageManager; private PackageManager mPackageManagerClient; AudioManager mAudioManager; Loading Loading @@ -1897,7 +1898,7 @@ public class NotificationManagerService extends SystemService { DevicePolicyManagerInternal dpm, IUriGrantsManager ugm, UriGrantsManagerInternal ugmInternal, AppOpsManager appOps, UserManager userManager, NotificationHistoryManager historyManager, StatsManager statsManager, TelephonyManager telephonyManager) { TelephonyManager telephonyManager, ActivityManagerInternal ami) { mHandler = handler; Resources resources = getContext().getResources(); mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(), Loading @@ -1919,6 +1920,7 @@ public class NotificationManagerService extends SystemService { mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); mCompanionManager = companionManager; mActivityManager = activityManager; mAmi = ami; mDeviceIdleManager = getContext().getSystemService(DeviceIdleManager.class); mDpm = dpm; mUm = userManager; Loading Loading @@ -2197,7 +2199,8 @@ public class NotificationManagerService extends SystemService { new NotificationHistoryManager(getContext(), handler), mStatsManager = (StatsManager) getContext().getSystemService( Context.STATS_MANAGER), getContext().getSystemService(TelephonyManager.class)); getContext().getSystemService(TelephonyManager.class), LocalServices.getService(ActivityManagerInternal.class)); publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL); Loading Loading @@ -5276,13 +5279,13 @@ public class NotificationManagerService extends SystemService { notificationRecord.getIsAppImportanceLocked()); summaries.put(pkg, summarySbn.getKey()); } } if (summaryRecord != null && checkDisqualifyingFeatures(userId, MY_UID, summaryRecord.getSbn().getId(), summaryRecord.getSbn().getTag(), summaryRecord, true)) { mHandler.post(new EnqueueNotificationRunnable(userId, summaryRecord, isAppForeground)); } } } private String disableNotificationEffects(NotificationRecord record) { if (mDisableNotificationEffects) { Loading Loading @@ -6019,13 +6022,17 @@ public class NotificationManagerService extends SystemService { + " cannot post for pkg " + targetPkg + " in user " + userId); } public boolean hasFlag(final int flags, final int flag) { return (flags & flag) != 0; } /** * Checks if a notification can be posted. checks rate limiter, snooze helper, and blocking. * * Has side effects. */ private boolean checkDisqualifyingFeatures(int userId, int uid, int id, String tag, boolean checkDisqualifyingFeatures(int userId, int uid, int id, String tag, NotificationRecord r, boolean isAutogroup) { Notification n = r.getNotification(); final String pkg = r.getSbn().getPackageName(); final boolean isSystemNotification = isUidSystemOrPhone(uid) || ("android".equals(pkg)); Loading @@ -6034,7 +6041,6 @@ public class NotificationManagerService extends SystemService { // Limit the number of notifications that any given package except the android // package or a registered listener can enqueue. Prevents DOS attacks and deals with leaks. if (!isSystemNotification && !isNotificationFromListener) { synchronized (mNotificationLock) { final int callingUid = Binder.getCallingUid(); if (mNotificationsByKey.get(r.getSbn().getKey()) == null && isCallerInstantApp(callingUid, userId)) { Loading Loading @@ -6065,7 +6071,7 @@ public class NotificationManagerService extends SystemService { } // limit the number of non-fgs outstanding notificationrecords an app can have if (!r.getNotification().isForegroundService()) { if (!n.isForegroundService()) { int count = getNotificationCountLocked(pkg, userId, id, tag); if (count >= MAX_PACKAGE_NOTIFICATIONS) { mUsageStats.registerOverCountQuota(pkg); Loading @@ -6075,9 +6081,41 @@ public class NotificationManagerService extends SystemService { } } } // bubble or inline reply that's immutable? if (n.getBubbleMetadata() != null && n.getBubbleMetadata().getIntent() != null && hasFlag(mAmi.getPendingIntentFlags( n.getBubbleMetadata().getIntent().getTarget()), PendingIntent.FLAG_IMMUTABLE)) { throw new IllegalArgumentException(r.getKey() + " Not posted." + " PendingIntents attached to bubbles must be mutable"); } if (n.actions != null) { for (Notification.Action action : n.actions) { if ((action.getRemoteInputs() != null || action.getDataOnlyRemoteInputs() != null) && hasFlag(mAmi.getPendingIntentFlags(action.actionIntent.getTarget()), PendingIntent.FLAG_IMMUTABLE)) { throw new IllegalArgumentException(r.getKey() + " Not posted." + " PendingIntents attached to actions with remote" + " inputs must be mutable"); } } } if (r.getSystemGeneratedSmartActions() != null) { for (Notification.Action action : r.getSystemGeneratedSmartActions()) { if ((action.getRemoteInputs() != null || action.getDataOnlyRemoteInputs() != null) && hasFlag(mAmi.getPendingIntentFlags(action.actionIntent.getTarget()), PendingIntent.FLAG_IMMUTABLE)) { throw new IllegalArgumentException(r.getKey() + " Not posted." + " PendingIntents attached to contextual actions with remote inputs" + " must be mutable"); } } } synchronized (mNotificationLock) { // snoozed apps if (mSnoozeHelper.isSnoozed(userId, pkg, r.getKey())) { MetricsLogger.action(r.getLogMaker() Loading @@ -6099,7 +6137,6 @@ public class NotificationManagerService extends SystemService { if (isBlocked(r, mUsageStats)) { return false; } } return true; } Loading services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +169 −4 Original line number Diff line number Diff line Loading @@ -43,6 +43,9 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.app.PendingIntent.FLAG_MUTABLE; import static android.app.PendingIntent.FLAG_ONE_SHOT; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.content.pm.PackageManager.PERMISSION_DENIED; Loading Loading @@ -110,6 +113,7 @@ import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.IIntentSender; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; Loading Loading @@ -248,6 +252,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Resources mResources; @Mock RankingHandler mRankingHandler; @Mock ActivityManagerInternal mAmi; @Mock IIntentSender pi1; private static final int MAX_POST_DELAY = 1000; Loading Loading @@ -392,7 +401,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { DeviceIdleInternal deviceIdleInternal = mock(DeviceIdleInternal.class); when(deviceIdleInternal.getNotificationAllowlistDuration()).thenReturn(3000L); ActivityManagerInternal activityManagerInternal = mock(ActivityManagerInternal.class); LocalServices.removeServiceForTest(UriGrantsManagerInternal.class); LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal); Loading @@ -403,7 +411,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { LocalServices.removeServiceForTest(DeviceIdleInternal.class); LocalServices.addService(DeviceIdleInternal.class, deviceIdleInternal); LocalServices.removeServiceForTest(ActivityManagerInternal.class); LocalServices.addService(ActivityManagerInternal.class, activityManagerInternal); LocalServices.addService(ActivityManagerInternal.class, mAmi); doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any()); Loading Loading @@ -477,7 +485,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mGroupHelper, mAm, mAtm, mAppUsageStats, mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal, mAppOpsManager, mUm, mHistoryManager, mStatsManager, mock(TelephonyManager.class)); mock(TelephonyManager.class), mAmi); mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); mService.setAudioManager(mAudioManager); Loading Loading @@ -674,7 +682,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } Notification.Builder nb = new Notification.Builder(mContext, channel.getId()) .setContentTitle("foo") .setSmallIcon(android.R.drawable.sym_def_app_icon); .setSmallIcon(android.R.drawable.sym_def_app_icon) .addAction(new Notification.Action.Builder(null, "test", null).build()); if (extender != null) { nb.extend(extender); } Loading Loading @@ -810,6 +819,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { PendingIntent pendingIntent = mock(PendingIntent.class); Intent intent = mock(Intent.class); when(pendingIntent.getIntent()).thenReturn(intent); when(pendingIntent.getTarget()).thenReturn(pi1); ActivityInfo info = new ActivityInfo(); info.resizeMode = RESIZE_MODE_RESIZEABLE; Loading Loading @@ -7134,4 +7144,159 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { inOrder.verify(parent).recordDismissalSentiment(anyInt()); inOrder.verify(child).recordDismissalSentiment(anyInt()); } @Test public void testImmutableBubbleIntent() throws Exception { when(mAmi.getPendingIntentFlags(pi1)) .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT); NotificationRecord r = generateMessageBubbleNotifRecord(true, mTestNotificationChannel, 7, "testImmutableBubbleIntent", null, false); try { mBinderService.enqueueNotificationWithTag(PKG, PKG, r.getSbn().getTag(), r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId()); waitForIdle(); fail("Allowed a bubble with an immutable intent to be posted"); } catch (IllegalArgumentException e) { // good } } @Test public void testMutableBubbleIntent() throws Exception { when(mAmi.getPendingIntentFlags(pi1)) .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT); NotificationRecord r = generateMessageBubbleNotifRecord(true, mTestNotificationChannel, 7, "testMutableBubbleIntent", null, false); mBinderService.enqueueNotificationWithTag(PKG, PKG, r.getSbn().getTag(), r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId()); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(r.getSbn().getPackageName()); assertEquals(1, notifs.length); } @Test public void testImmutableDirectReplyActionIntent() throws Exception { when(mAmi.getPendingIntentFlags(any(IIntentSender.class))) .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT); NotificationRecord r = generateMessageBubbleNotifRecord(false, mTestNotificationChannel, 7, "testImmutableDirectReplyActionIntent", null, false); try { mBinderService.enqueueNotificationWithTag(PKG, PKG, r.getSbn().getTag(), r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId()); waitForIdle(); fail("Allowed a direct reply with an immutable intent to be posted"); } catch (IllegalArgumentException e) { // good } } @Test public void testMutableDirectReplyActionIntent() throws Exception { when(mAmi.getPendingIntentFlags(any(IIntentSender.class))) .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT); NotificationRecord r = generateMessageBubbleNotifRecord(false, mTestNotificationChannel, 7, "testMutableDirectReplyActionIntent", null, false); mBinderService.enqueueNotificationWithTag(PKG, PKG, r.getSbn().getTag(), r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId()); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(r.getSbn().getPackageName()); assertEquals(1, notifs.length); } @Test public void testImmutableDirectReplyContextualActionIntent() throws Exception { when(mAmi.getPendingIntentFlags(any(IIntentSender.class))) .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT); when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); ArrayList<Notification.Action> extraAction = new ArrayList<>(); RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build(); PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0); Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon); Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply", inputIntent).addRemoteInput(remoteInput) .build(); extraAction.add(replyAction); Bundle signals = new Bundle(); signals.putParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, extraAction); Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser()); r.addAdjustment(adjustment); r.applyAdjustments(); try { mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(), r.getSbn().getTag(), r,false); fail("Allowed a contextual direct reply with an immutable intent to be posted"); } catch (IllegalArgumentException e) { // good } } @Test public void testMutableDirectReplyContextualActionIntent() throws Exception { when(mAmi.getPendingIntentFlags(any(IIntentSender.class))) .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT); when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); ArrayList<Notification.Action> extraAction = new ArrayList<>(); RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build(); PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0); Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon); Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply", inputIntent).addRemoteInput(remoteInput) .build(); extraAction.add(replyAction); Bundle signals = new Bundle(); signals.putParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, extraAction); Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser()); r.addAdjustment(adjustment); r.applyAdjustments(); mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(), r.getSbn().getTag(), r,false); } @Test public void testImmutableActionIntent() throws Exception { when(mAmi.getPendingIntentFlags(any(IIntentSender.class))) .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT); NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); mBinderService.enqueueNotificationWithTag(PKG, PKG, r.getSbn().getTag(), r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId()); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(r.getSbn().getPackageName()); assertEquals(1, notifs.length); } @Test public void testImmutableContextualActionIntent() throws Exception { when(mAmi.getPendingIntentFlags(any(IIntentSender.class))) .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT); when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); ArrayList<Notification.Action> extraAction = new ArrayList<>(); extraAction.add(new Notification.Action(0, "hello", null)); Bundle signals = new Bundle(); signals.putParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, extraAction); Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser()); r.addAdjustment(adjustment); r.applyAdjustments(); mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(), r.getSbn().getTag(), r,false); } } services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java +3 −1 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.IUriGrantsManager; Loading Loading @@ -154,7 +155,8 @@ public class RoleObserverTest extends UiServiceTestCase { mock(DevicePolicyManagerInternal.class), mock(IUriGrantsManager.class), mock(UriGrantsManagerInternal.class), mock(AppOpsManager.class), mUm, mock(NotificationHistoryManager.class), mock(StatsManager.class), mock(TelephonyManager.class)); mock(StatsManager.class), mock(TelephonyManager.class), mock(ActivityManagerInternal.class)); } catch (SecurityException e) { if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { throw e; Loading Loading
services/core/java/com/android/server/notification/NotificationManagerService.java +100 −63 Original line number Diff line number Diff line Loading @@ -421,6 +421,7 @@ public class NotificationManagerService extends SystemService { private IActivityManager mAm; private ActivityTaskManagerInternal mAtm; private ActivityManager mActivityManager; private ActivityManagerInternal mAmi; private IPackageManager mPackageManager; private PackageManager mPackageManagerClient; AudioManager mAudioManager; Loading Loading @@ -1897,7 +1898,7 @@ public class NotificationManagerService extends SystemService { DevicePolicyManagerInternal dpm, IUriGrantsManager ugm, UriGrantsManagerInternal ugmInternal, AppOpsManager appOps, UserManager userManager, NotificationHistoryManager historyManager, StatsManager statsManager, TelephonyManager telephonyManager) { TelephonyManager telephonyManager, ActivityManagerInternal ami) { mHandler = handler; Resources resources = getContext().getResources(); mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(), Loading @@ -1919,6 +1920,7 @@ public class NotificationManagerService extends SystemService { mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); mCompanionManager = companionManager; mActivityManager = activityManager; mAmi = ami; mDeviceIdleManager = getContext().getSystemService(DeviceIdleManager.class); mDpm = dpm; mUm = userManager; Loading Loading @@ -2197,7 +2199,8 @@ public class NotificationManagerService extends SystemService { new NotificationHistoryManager(getContext(), handler), mStatsManager = (StatsManager) getContext().getSystemService( Context.STATS_MANAGER), getContext().getSystemService(TelephonyManager.class)); getContext().getSystemService(TelephonyManager.class), LocalServices.getService(ActivityManagerInternal.class)); publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL); Loading Loading @@ -5276,13 +5279,13 @@ public class NotificationManagerService extends SystemService { notificationRecord.getIsAppImportanceLocked()); summaries.put(pkg, summarySbn.getKey()); } } if (summaryRecord != null && checkDisqualifyingFeatures(userId, MY_UID, summaryRecord.getSbn().getId(), summaryRecord.getSbn().getTag(), summaryRecord, true)) { mHandler.post(new EnqueueNotificationRunnable(userId, summaryRecord, isAppForeground)); } } } private String disableNotificationEffects(NotificationRecord record) { if (mDisableNotificationEffects) { Loading Loading @@ -6019,13 +6022,17 @@ public class NotificationManagerService extends SystemService { + " cannot post for pkg " + targetPkg + " in user " + userId); } public boolean hasFlag(final int flags, final int flag) { return (flags & flag) != 0; } /** * Checks if a notification can be posted. checks rate limiter, snooze helper, and blocking. * * Has side effects. */ private boolean checkDisqualifyingFeatures(int userId, int uid, int id, String tag, boolean checkDisqualifyingFeatures(int userId, int uid, int id, String tag, NotificationRecord r, boolean isAutogroup) { Notification n = r.getNotification(); final String pkg = r.getSbn().getPackageName(); final boolean isSystemNotification = isUidSystemOrPhone(uid) || ("android".equals(pkg)); Loading @@ -6034,7 +6041,6 @@ public class NotificationManagerService extends SystemService { // Limit the number of notifications that any given package except the android // package or a registered listener can enqueue. Prevents DOS attacks and deals with leaks. if (!isSystemNotification && !isNotificationFromListener) { synchronized (mNotificationLock) { final int callingUid = Binder.getCallingUid(); if (mNotificationsByKey.get(r.getSbn().getKey()) == null && isCallerInstantApp(callingUid, userId)) { Loading Loading @@ -6065,7 +6071,7 @@ public class NotificationManagerService extends SystemService { } // limit the number of non-fgs outstanding notificationrecords an app can have if (!r.getNotification().isForegroundService()) { if (!n.isForegroundService()) { int count = getNotificationCountLocked(pkg, userId, id, tag); if (count >= MAX_PACKAGE_NOTIFICATIONS) { mUsageStats.registerOverCountQuota(pkg); Loading @@ -6075,9 +6081,41 @@ public class NotificationManagerService extends SystemService { } } } // bubble or inline reply that's immutable? if (n.getBubbleMetadata() != null && n.getBubbleMetadata().getIntent() != null && hasFlag(mAmi.getPendingIntentFlags( n.getBubbleMetadata().getIntent().getTarget()), PendingIntent.FLAG_IMMUTABLE)) { throw new IllegalArgumentException(r.getKey() + " Not posted." + " PendingIntents attached to bubbles must be mutable"); } if (n.actions != null) { for (Notification.Action action : n.actions) { if ((action.getRemoteInputs() != null || action.getDataOnlyRemoteInputs() != null) && hasFlag(mAmi.getPendingIntentFlags(action.actionIntent.getTarget()), PendingIntent.FLAG_IMMUTABLE)) { throw new IllegalArgumentException(r.getKey() + " Not posted." + " PendingIntents attached to actions with remote" + " inputs must be mutable"); } } } if (r.getSystemGeneratedSmartActions() != null) { for (Notification.Action action : r.getSystemGeneratedSmartActions()) { if ((action.getRemoteInputs() != null || action.getDataOnlyRemoteInputs() != null) && hasFlag(mAmi.getPendingIntentFlags(action.actionIntent.getTarget()), PendingIntent.FLAG_IMMUTABLE)) { throw new IllegalArgumentException(r.getKey() + " Not posted." + " PendingIntents attached to contextual actions with remote inputs" + " must be mutable"); } } } synchronized (mNotificationLock) { // snoozed apps if (mSnoozeHelper.isSnoozed(userId, pkg, r.getKey())) { MetricsLogger.action(r.getLogMaker() Loading @@ -6099,7 +6137,6 @@ public class NotificationManagerService extends SystemService { if (isBlocked(r, mUsageStats)) { return false; } } return true; } Loading
services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +169 −4 Original line number Diff line number Diff line Loading @@ -43,6 +43,9 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR; import static android.app.PendingIntent.FLAG_IMMUTABLE; import static android.app.PendingIntent.FLAG_MUTABLE; import static android.app.PendingIntent.FLAG_ONE_SHOT; import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.content.pm.PackageManager.PERMISSION_DENIED; Loading Loading @@ -110,6 +113,7 @@ import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.IIntentSender; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; Loading Loading @@ -248,6 +252,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { Resources mResources; @Mock RankingHandler mRankingHandler; @Mock ActivityManagerInternal mAmi; @Mock IIntentSender pi1; private static final int MAX_POST_DELAY = 1000; Loading Loading @@ -392,7 +401,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { DeviceIdleInternal deviceIdleInternal = mock(DeviceIdleInternal.class); when(deviceIdleInternal.getNotificationAllowlistDuration()).thenReturn(3000L); ActivityManagerInternal activityManagerInternal = mock(ActivityManagerInternal.class); LocalServices.removeServiceForTest(UriGrantsManagerInternal.class); LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal); Loading @@ -403,7 +411,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { LocalServices.removeServiceForTest(DeviceIdleInternal.class); LocalServices.addService(DeviceIdleInternal.class, deviceIdleInternal); LocalServices.removeServiceForTest(ActivityManagerInternal.class); LocalServices.addService(ActivityManagerInternal.class, activityManagerInternal); LocalServices.addService(ActivityManagerInternal.class, mAmi); doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any()); Loading Loading @@ -477,7 +485,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mGroupHelper, mAm, mAtm, mAppUsageStats, mock(DevicePolicyManagerInternal.class), mUgm, mUgmInternal, mAppOpsManager, mUm, mHistoryManager, mStatsManager, mock(TelephonyManager.class)); mock(TelephonyManager.class), mAmi); mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); mService.setAudioManager(mAudioManager); Loading Loading @@ -674,7 +682,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } Notification.Builder nb = new Notification.Builder(mContext, channel.getId()) .setContentTitle("foo") .setSmallIcon(android.R.drawable.sym_def_app_icon); .setSmallIcon(android.R.drawable.sym_def_app_icon) .addAction(new Notification.Action.Builder(null, "test", null).build()); if (extender != null) { nb.extend(extender); } Loading Loading @@ -810,6 +819,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { PendingIntent pendingIntent = mock(PendingIntent.class); Intent intent = mock(Intent.class); when(pendingIntent.getIntent()).thenReturn(intent); when(pendingIntent.getTarget()).thenReturn(pi1); ActivityInfo info = new ActivityInfo(); info.resizeMode = RESIZE_MODE_RESIZEABLE; Loading Loading @@ -7134,4 +7144,159 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { inOrder.verify(parent).recordDismissalSentiment(anyInt()); inOrder.verify(child).recordDismissalSentiment(anyInt()); } @Test public void testImmutableBubbleIntent() throws Exception { when(mAmi.getPendingIntentFlags(pi1)) .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT); NotificationRecord r = generateMessageBubbleNotifRecord(true, mTestNotificationChannel, 7, "testImmutableBubbleIntent", null, false); try { mBinderService.enqueueNotificationWithTag(PKG, PKG, r.getSbn().getTag(), r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId()); waitForIdle(); fail("Allowed a bubble with an immutable intent to be posted"); } catch (IllegalArgumentException e) { // good } } @Test public void testMutableBubbleIntent() throws Exception { when(mAmi.getPendingIntentFlags(pi1)) .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT); NotificationRecord r = generateMessageBubbleNotifRecord(true, mTestNotificationChannel, 7, "testMutableBubbleIntent", null, false); mBinderService.enqueueNotificationWithTag(PKG, PKG, r.getSbn().getTag(), r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId()); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(r.getSbn().getPackageName()); assertEquals(1, notifs.length); } @Test public void testImmutableDirectReplyActionIntent() throws Exception { when(mAmi.getPendingIntentFlags(any(IIntentSender.class))) .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT); NotificationRecord r = generateMessageBubbleNotifRecord(false, mTestNotificationChannel, 7, "testImmutableDirectReplyActionIntent", null, false); try { mBinderService.enqueueNotificationWithTag(PKG, PKG, r.getSbn().getTag(), r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId()); waitForIdle(); fail("Allowed a direct reply with an immutable intent to be posted"); } catch (IllegalArgumentException e) { // good } } @Test public void testMutableDirectReplyActionIntent() throws Exception { when(mAmi.getPendingIntentFlags(any(IIntentSender.class))) .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT); NotificationRecord r = generateMessageBubbleNotifRecord(false, mTestNotificationChannel, 7, "testMutableDirectReplyActionIntent", null, false); mBinderService.enqueueNotificationWithTag(PKG, PKG, r.getSbn().getTag(), r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId()); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(r.getSbn().getPackageName()); assertEquals(1, notifs.length); } @Test public void testImmutableDirectReplyContextualActionIntent() throws Exception { when(mAmi.getPendingIntentFlags(any(IIntentSender.class))) .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT); when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); ArrayList<Notification.Action> extraAction = new ArrayList<>(); RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build(); PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0); Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon); Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply", inputIntent).addRemoteInput(remoteInput) .build(); extraAction.add(replyAction); Bundle signals = new Bundle(); signals.putParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, extraAction); Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser()); r.addAdjustment(adjustment); r.applyAdjustments(); try { mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(), r.getSbn().getTag(), r,false); fail("Allowed a contextual direct reply with an immutable intent to be posted"); } catch (IllegalArgumentException e) { // good } } @Test public void testMutableDirectReplyContextualActionIntent() throws Exception { when(mAmi.getPendingIntentFlags(any(IIntentSender.class))) .thenReturn(FLAG_MUTABLE | FLAG_ONE_SHOT); when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); ArrayList<Notification.Action> extraAction = new ArrayList<>(); RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build(); PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0); Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon); Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply", inputIntent).addRemoteInput(remoteInput) .build(); extraAction.add(replyAction); Bundle signals = new Bundle(); signals.putParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, extraAction); Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser()); r.addAdjustment(adjustment); r.applyAdjustments(); mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(), r.getSbn().getTag(), r,false); } @Test public void testImmutableActionIntent() throws Exception { when(mAmi.getPendingIntentFlags(any(IIntentSender.class))) .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT); NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); mBinderService.enqueueNotificationWithTag(PKG, PKG, r.getSbn().getTag(), r.getSbn().getId(), r.getNotification(), r.getSbn().getUserId()); waitForIdle(); StatusBarNotification[] notifs = mBinderService.getActiveNotifications(r.getSbn().getPackageName()); assertEquals(1, notifs.length); } @Test public void testImmutableContextualActionIntent() throws Exception { when(mAmi.getPendingIntentFlags(any(IIntentSender.class))) .thenReturn(FLAG_IMMUTABLE | FLAG_ONE_SHOT); when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true); NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); ArrayList<Notification.Action> extraAction = new ArrayList<>(); extraAction.add(new Notification.Action(0, "hello", null)); Bundle signals = new Bundle(); signals.putParcelableArrayList(Adjustment.KEY_CONTEXTUAL_ACTIONS, extraAction); Adjustment adjustment = new Adjustment(r.getSbn().getPackageName(), r.getKey(), signals, "", r.getUser()); r.addAdjustment(adjustment); r.applyAdjustments(); mService.checkDisqualifyingFeatures(r.getUserId(), r.getUid(), r.getSbn().getId(), r.getSbn().getTag(), r,false); } }
services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java +3 −1 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.AppOpsManager; import android.app.IActivityManager; import android.app.IUriGrantsManager; Loading Loading @@ -154,7 +155,8 @@ public class RoleObserverTest extends UiServiceTestCase { mock(DevicePolicyManagerInternal.class), mock(IUriGrantsManager.class), mock(UriGrantsManagerInternal.class), mock(AppOpsManager.class), mUm, mock(NotificationHistoryManager.class), mock(StatsManager.class), mock(TelephonyManager.class)); mock(StatsManager.class), mock(TelephonyManager.class), mock(ActivityManagerInternal.class)); } catch (SecurityException e) { if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { throw e; Loading