Merge "Validate pending intents"
This commit is contained in:
committed by
Android (Google) Code Review
commit
4c1deb59b7
@@ -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;
|
||||
@@ -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(),
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -5239,8 +5242,8 @@ public class NotificationManagerService extends SystemService {
|
||||
if (!summaries.containsKey(pkg)) {
|
||||
// Add summary
|
||||
final ApplicationInfo appInfo =
|
||||
adjustedSbn.getNotification().extras.getParcelable(
|
||||
Notification.EXTRA_BUILDER_APPLICATION_INFO);
|
||||
adjustedSbn.getNotification().extras.getParcelable(
|
||||
Notification.EXTRA_BUILDER_APPLICATION_INFO);
|
||||
final Bundle extras = new Bundle();
|
||||
extras.putParcelable(Notification.EXTRA_BUILDER_APPLICATION_INFO, appInfo);
|
||||
final String channelId = notificationRecord.getChannel().getId();
|
||||
@@ -5276,11 +5279,11 @@ 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));
|
||||
if (summaryRecord != null && checkDisqualifyingFeatures(userId, MY_UID,
|
||||
summaryRecord.getSbn().getId(), summaryRecord.getSbn().getTag(), summaryRecord,
|
||||
true)) {
|
||||
mHandler.post(new EnqueueNotificationRunnable(userId, summaryRecord, isAppForeground));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
@@ -6034,71 +6041,101 @@ 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)) {
|
||||
// Ephemeral apps have some special constraints for notifications.
|
||||
// They are not allowed to create new notifications however they are allowed to
|
||||
// update notifications created by the system (e.g. a foreground service
|
||||
// notification).
|
||||
throw new SecurityException("Instant app " + pkg
|
||||
+ " cannot create notifications");
|
||||
}
|
||||
final int callingUid = Binder.getCallingUid();
|
||||
if (mNotificationsByKey.get(r.getSbn().getKey()) == null
|
||||
&& isCallerInstantApp(callingUid, userId)) {
|
||||
// Ephemeral apps have some special constraints for notifications.
|
||||
// They are not allowed to create new notifications however they are allowed to
|
||||
// update notifications created by the system (e.g. a foreground service
|
||||
// notification).
|
||||
throw new SecurityException("Instant app " + pkg
|
||||
+ " cannot create notifications");
|
||||
}
|
||||
|
||||
// rate limit updates that aren't completed progress notifications
|
||||
if (mNotificationsByKey.get(r.getSbn().getKey()) != null
|
||||
&& !r.getNotification().hasCompletedProgress()
|
||||
&& !isAutogroup) {
|
||||
// rate limit updates that aren't completed progress notifications
|
||||
if (mNotificationsByKey.get(r.getSbn().getKey()) != null
|
||||
&& !r.getNotification().hasCompletedProgress()
|
||||
&& !isAutogroup) {
|
||||
|
||||
final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
|
||||
if (appEnqueueRate > mMaxPackageEnqueueRate) {
|
||||
mUsageStats.registerOverRateQuota(pkg);
|
||||
final long now = SystemClock.elapsedRealtime();
|
||||
if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) {
|
||||
Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate
|
||||
+ ". Shedding " + r.getSbn().getKey() + ". package=" + pkg);
|
||||
mLastOverRateLogTime = now;
|
||||
}
|
||||
return false;
|
||||
final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
|
||||
if (appEnqueueRate > mMaxPackageEnqueueRate) {
|
||||
mUsageStats.registerOverRateQuota(pkg);
|
||||
final long now = SystemClock.elapsedRealtime();
|
||||
if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) {
|
||||
Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate
|
||||
+ ". Shedding " + r.getSbn().getKey() + ". package=" + pkg);
|
||||
mLastOverRateLogTime = now;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// limit the number of non-fgs outstanding notificationrecords an app can have
|
||||
if (!r.getNotification().isForegroundService()) {
|
||||
int count = getNotificationCountLocked(pkg, userId, id, tag);
|
||||
if (count >= MAX_PACKAGE_NOTIFICATIONS) {
|
||||
mUsageStats.registerOverCountQuota(pkg);
|
||||
Slog.e(TAG, "Package has already posted or enqueued " + count
|
||||
+ " notifications. Not showing more. package=" + pkg);
|
||||
return false;
|
||||
}
|
||||
// limit the number of non-fgs outstanding notificationrecords an app can have
|
||||
if (!n.isForegroundService()) {
|
||||
int count = getNotificationCountLocked(pkg, userId, id, tag);
|
||||
if (count >= MAX_PACKAGE_NOTIFICATIONS) {
|
||||
mUsageStats.registerOverCountQuota(pkg);
|
||||
Slog.e(TAG, "Package has already posted or enqueued " + count
|
||||
+ " notifications. Not showing more. package=" + pkg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
synchronized (mNotificationLock) {
|
||||
// snoozed apps
|
||||
if (mSnoozeHelper.isSnoozed(userId, pkg, r.getKey())) {
|
||||
MetricsLogger.action(r.getLogMaker()
|
||||
.setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
|
||||
.setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED));
|
||||
mNotificationRecordLogger.log(
|
||||
NotificationRecordLogger.NotificationEvent.NOTIFICATION_NOT_POSTED_SNOOZED,
|
||||
r);
|
||||
if (DBG) {
|
||||
Slog.d(TAG, "Ignored enqueue for snoozed notification " + r.getKey());
|
||||
// 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");
|
||||
}
|
||||
mSnoozeHelper.update(userId, r);
|
||||
handleSavePolicyFile();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// snoozed apps
|
||||
if (mSnoozeHelper.isSnoozed(userId, pkg, r.getKey())) {
|
||||
MetricsLogger.action(r.getLogMaker()
|
||||
.setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
|
||||
.setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED));
|
||||
mNotificationRecordLogger.log(
|
||||
NotificationRecordLogger.NotificationEvent.NOTIFICATION_NOT_POSTED_SNOOZED,
|
||||
r);
|
||||
if (DBG) {
|
||||
Slog.d(TAG, "Ignored enqueue for snoozed notification " + r.getKey());
|
||||
}
|
||||
mSnoozeHelper.update(userId, r);
|
||||
handleSavePolicyFile();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// blocked apps
|
||||
if (isBlocked(r, mUsageStats)) {
|
||||
return false;
|
||||
}
|
||||
// blocked apps
|
||||
if (isBlocked(r, mUsageStats)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user