diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 6e4098646b274..8393caa33e315 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1055,11 +1055,10 @@ public class Notification implements Parcelable /** * {@link #extras} key: A * {@link android.content.ContentUris content URI} pointing to an image that can be displayed - * in the background when the notification is selected. The URI must point to an image stream - * suitable for passing into + * in the background when the notification is selected. Used on television platforms. + * The URI must point to an image stream suitable for passing into * {@link android.graphics.BitmapFactory#decodeStream(java.io.InputStream) - * BitmapFactory.decodeStream}; all other content types will be ignored. The content provider - * URI used for this purpose must require no permissions to read the image data. + * BitmapFactory.decodeStream}; all other content types will be ignored. */ public static final String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri"; diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 538623f26a959..b10e60870721a 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -24,6 +24,7 @@ import android.annotation.TestApi; import android.app.Notification.Builder; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.pm.ParceledListSlice; import android.graphics.drawable.Icon; import android.net.Uri; @@ -350,6 +351,14 @@ public class NotificationManager { * the same tag and id has already been posted by your application and has not yet been * canceled, it will be replaced by the updated information. * + * All {@link android.service.notification.NotificationListenerService listener services} will + * be granted {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} access to any {@link Uri uris} + * provided on this notification or the + * {@link NotificationChannel} this notification is posted to using + * {@link Context#grantUriPermission(String, Uri, int)}. Permission will be revoked when the + * notification is canceled, or you can revoke permissions with + * {@link Context#revokeUriPermission(Uri, int)}. + * * @param tag A string identifier for this notification. May be {@code null}. * @param id An identifier for this notification. The pair (tag, id) must be unique * within your application. @@ -370,11 +379,13 @@ public class NotificationManager { String pkg = mContext.getPackageName(); // Fix the notification as best we can. Notification.addFieldsFromContext(mContext, notification); + if (notification.sound != null) { notification.sound = notification.sound.getCanonicalUri(); if (StrictMode.vmFileUriExposureEnabled()) { notification.sound.checkFileUriExposed("Notification.sound"); } + } fixLegacySmallIcon(notification, pkg); if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) { @@ -385,6 +396,7 @@ public class NotificationManager { } if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")"); notification.reduceImageSizes(mContext); + ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); boolean isLowRam = am.isLowRamDevice(); final Notification copy = Builder.maybeCloneStrippedForDelivery(notification, isLowRam); diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index fd435f952c109..b7842d5365b53 100644 --- a/services/core/java/com/android/server/notification/ManagedServices.java +++ b/services/core/java/com/android/server/notification/ManagedServices.java @@ -376,9 +376,7 @@ abstract public class ManagedServices { protected void upgradeXml(final int xmlVersion, final int userId) {} private void loadAllowedComponentsFromSettings() { - - UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); - for (UserInfo user : userManager.getUsers()) { + for (UserInfo user : mUm.getUsers()) { final ContentResolver cr = mContext.getContentResolver(); addApprovedList(Settings.Secure.getStringForUser( cr, @@ -482,7 +480,9 @@ abstract public class ManagedServices { for (int i = 0; i < allowedByType.size(); i++) { final ArraySet allowed = allowedByType.valueAt(i); allowedPackages.addAll( - allowed.stream().map(this::getPackageName).collect(Collectors.toList())); + allowed.stream().map(this::getPackageName). + filter(value -> !TextUtils.isEmpty(value)) + .collect(Collectors.toList())); } return allowedPackages; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index e7eed03a5bde4..ada002c0c5b07 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -25,7 +25,9 @@ import static android.app.NotificationManager.IMPORTANCE_NONE; import static android.content.pm.PackageManager.FEATURE_LEANBACK; import static android.content.pm.PackageManager.FEATURE_TELEVISION; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.os.UserHandle.USER_ALL; import static android.os.UserHandle.USER_NULL; +import static android.os.UserHandle.USER_SYSTEM; import static android.service.notification.NotificationListenerService .HINT_HOST_DISABLE_CALL_EFFECTS; import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS; @@ -88,6 +90,7 @@ import android.app.usage.UsageStatsManagerInternal; import android.companion.ICompanionDeviceManager; import android.content.BroadcastReceiver; import android.content.ComponentName; +import android.content.ContentProvider; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -115,6 +118,7 @@ import android.os.IDeviceIdleController; import android.os.IInterface; import android.os.Looper; import android.os.Message; +import android.os.Parcelable; import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; @@ -206,6 +210,7 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; @@ -288,6 +293,7 @@ public class NotificationManagerService extends SystemService { private ICompanionDeviceManager mCompanionManager; private AccessibilityManager mAccessibilityManager; private IDeviceIdleController mDeviceIdleController; + private IBinder mPermissionOwner; final IBinder mForegroundToken = new Binder(); private WorkerHandler mHandler; @@ -524,7 +530,7 @@ public class NotificationManagerService extends SystemService { } catch (FileNotFoundException e) { // No data yet // Load default managed services approvals - readDefaultApprovedServices(UserHandle.USER_SYSTEM); + readDefaultApprovedServices(USER_SYSTEM); } catch (IOException e) { Log.wtf(TAG, "Unable to read notification policy", e); } catch (NumberFormatException e) { @@ -974,7 +980,7 @@ public class NotificationManagerService extends SystemService { final int enabled = mPackageManager.getApplicationEnabledSetting( pkgName, changeUserId != UserHandle.USER_ALL ? changeUserId : - UserHandle.USER_SYSTEM); + USER_SYSTEM); if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED || enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) { cancelNotifications = false; @@ -1061,6 +1067,7 @@ public class NotificationManagerService extends SystemService { } } else if (action.equals(Intent.ACTION_USER_REMOVED)) { final int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_NULL); + mUserProfiles.updateCache(context); mZenModeHelper.onUserRemoved(user); mRankingHelper.onUserRemoved(user); mListeners.onUserRemoved(user); @@ -1268,7 +1275,7 @@ public class NotificationManagerService extends SystemService { NotificationAssistants notificationAssistants, ConditionProviders conditionProviders, ICompanionDeviceManager companionManager, SnoozeHelper snoozeHelper, NotificationUsageStats usageStats, AtomicFile policyFile, - ActivityManager activityManager, GroupHelper groupHelper) { + ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am) { Resources resources = getContext().getResources(); mMaxPackageEnqueueRate = Settings.Global.getFloat(getContext().getContentResolver(), Settings.Global.MAX_NOTIFICATION_ENQUEUE_RATE, @@ -1276,7 +1283,7 @@ public class NotificationManagerService extends SystemService { mAccessibilityManager = (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); - mAm = ActivityManager.getService(); + mAm = am; mPackageManager = packageManager; mPackageManagerClient = packageManagerClient; mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE); @@ -1287,6 +1294,11 @@ public class NotificationManagerService extends SystemService { mActivityManager = activityManager; mDeviceIdleController = IDeviceIdleController.Stub.asInterface( ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); + try { + mPermissionOwner = mAm.newUriPermissionOwner("notification"); + } catch (RemoteException e) { + Slog.w(TAG, "AM dead", e); + } mHandler = new WorkerHandler(looper); mRankingThread.start(); @@ -1415,7 +1427,7 @@ public class NotificationManagerService extends SystemService { null, snoozeHelper, new NotificationUsageStats(getContext()), new AtomicFile(new File(systemDir, "notification_policy.xml"), "notification-policy"), (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE), - getGroupHelper()); + getGroupHelper(), ActivityManager.getService()); // register for various Intents IntentFilter filter = new IntentFilter(); @@ -1749,7 +1761,7 @@ public class NotificationManagerService extends SystemService { protected void reportSeen(NotificationRecord r) { final int userId = r.sbn.getUserId(); mAppUsageStats.reportEvent(r.sbn.getPackageName(), - userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM + userId == UserHandle.USER_ALL ? USER_SYSTEM : userId, UsageEvents.Event.NOTIFICATION_SEEN); } @@ -2894,7 +2906,7 @@ public class NotificationManagerService extends SystemService { checkCallerIsSystem(); if (DBG) Slog.d(TAG, "getBackupPayload u=" + user); //TODO: http://b/22388012 - if (user != UserHandle.USER_SYSTEM) { + if (user != USER_SYSTEM) { Slog.w(TAG, "getBackupPayload: cannot backup policy for user " + user); return null; } @@ -2920,7 +2932,7 @@ public class NotificationManagerService extends SystemService { return; } //TODO: http://b/22388012 - if (user != UserHandle.USER_SYSTEM) { + if (user != USER_SYSTEM) { Slog.w(TAG, "applyRestore: cannot restore policy for user " + user); return; } @@ -3678,7 +3690,7 @@ public class NotificationManagerService extends SystemService { sbn.getNotification().flags = (r.mOriginalFlags & ~Notification.FLAG_FOREGROUND_SERVICE); mRankingHelper.sort(mNotificationList); - mListeners.notifyPostedLocked(sbn, sbn /* oldSbn */); + mListeners.notifyPostedLocked(r, sbn /* oldSbn */); } }; @@ -3707,7 +3719,7 @@ public class NotificationManagerService extends SystemService { try { final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser( pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING, - (userId == UserHandle.USER_ALL) ? UserHandle.USER_SYSTEM : userId); + (userId == UserHandle.USER_ALL) ? USER_SYSTEM : userId); Notification.addFieldsFromContext(ai, notification); int canColorize = mPackageManagerClient.checkPermission( @@ -4126,6 +4138,8 @@ public class NotificationManagerService extends SystemService { // Make sure we don't lose the foreground service state. notification.flags |= old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE; + // revoke uri permissions for changed uris + revokeUriPermissions(r, old); r.isUpdate = true; } @@ -4147,7 +4161,7 @@ public class NotificationManagerService extends SystemService { if (notification.getSmallIcon() != null) { StatusBarNotification oldSbn = (old != null) ? old.sbn : null; - mListeners.notifyPostedLocked(n, oldSbn); + mListeners.notifyPostedLocked(r, oldSbn); if (oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup())) { mHandler.post(new Runnable() { @Override @@ -4912,6 +4926,9 @@ public class NotificationManagerService extends SystemService { r.recordDismissalSurface(NotificationStats.DISMISSAL_OTHER); } + // Revoke permissions + revokeUriPermissions(null, r); + // tell the app if (sendDelete) { if (r.getNotification().deleteIntent != null) { @@ -5009,6 +5026,30 @@ public class NotificationManagerService extends SystemService { r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now), listenerName); } + void revokeUriPermissions(NotificationRecord newRecord, NotificationRecord oldRecord) { + Set oldUris = oldRecord.getNotificationUris(); + Set newUris = newRecord == null ? new HashSet<>() : newRecord.getNotificationUris(); + oldUris.removeAll(newUris); + + long ident = Binder.clearCallingIdentity(); + try { + for (Uri uri : oldUris) { + if (uri != null) { + int notiUserId = oldRecord.getUserId(); + int sourceUserId = notiUserId == USER_ALL ? USER_SYSTEM + : ContentProvider.getUserIdFromUri(uri, notiUserId); + uri = ContentProvider.getUriWithoutUserId(uri); + mAm.revokeUriPermissionFromOwner(mPermissionOwner, + uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId); + } + } + } catch (RemoteException e) { + Log.e(TAG, "Count not revoke uri permissions", e); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + /** * Cancels a notification ONLY if it has all of the {@code mustHaveFlags} * and none of the {@code mustNotHaveFlags}. @@ -5851,10 +5892,13 @@ public class NotificationManagerService extends SystemService { * but isn't anymore. */ @GuardedBy("mNotificationLock") - public void notifyPostedLocked(StatusBarNotification sbn, StatusBarNotification oldSbn) { + public void notifyPostedLocked(NotificationRecord r, StatusBarNotification oldSbn) { // Lazily initialized snapshots of the notification. + StatusBarNotification sbn = r.sbn; TrimCache trimCache = new TrimCache(sbn); + Set uris = r.getNotificationUris(); + for (final ManagedServiceInfo info : getServices()) { boolean sbnVisible = isVisibleToListener(sbn, info); boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info) : false; @@ -5877,6 +5921,9 @@ public class NotificationManagerService extends SystemService { continue; } + grantUriPermissions(uris, sbn.getUserId(), info.component.getPackageName(), + info.userid); + final StatusBarNotification sbnToPost = trimCache.ForListener(info); mHandler.post(new Runnable() { @Override @@ -5887,6 +5934,28 @@ public class NotificationManagerService extends SystemService { } } + private void grantUriPermissions(Set uris, int notiUserId, String listenerPkg, + int listenerUserId) { + long ident = Binder.clearCallingIdentity(); + try { + for (Uri uri : uris) { + if (uri != null) { + int sourceUserId = notiUserId == USER_ALL ? USER_SYSTEM + : ContentProvider.getUserIdFromUri(uri, notiUserId); + uri = ContentProvider.getUriWithoutUserId(uri); + mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), + listenerPkg, + uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, + listenerUserId == USER_ALL ? USER_SYSTEM : listenerUserId); + } + } + } catch (RemoteException e) { + Log.e(TAG, "Count not grant uri permission to " + listenerPkg, e); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + /** * asynchronously notify all listeners about a removed notification */ diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 23b9743aaf78d..1ad8c74a831c8 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -38,6 +38,7 @@ import android.metrics.LogMaker; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.os.Parcelable; import android.os.UserHandle; import android.provider.Settings; import android.service.notification.Adjustment; @@ -47,6 +48,7 @@ import android.service.notification.NotificationStats; import android.service.notification.SnoozeCriterion; import android.service.notification.StatusBarNotification; import android.text.TextUtils; +import android.util.ArraySet; import android.util.Log; import android.util.Slog; import android.util.TimeUtils; @@ -64,6 +66,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; +import java.util.Set; /** * Holds data about notifications that should not be shared with the @@ -929,6 +932,42 @@ public final class NotificationRecord { mStats.setViewedSettings(); } + public Set getNotificationUris() { + Notification notification = getNotification(); + Set uris = new ArraySet<>(); + + if (notification.sound != null) { + uris.add(notification.sound); + } + if (notification.getChannelId() != null) { + NotificationChannel channel = getChannel(); + if (channel != null && channel.getSound() != null) { + uris.add(channel.getSound()); + } + } + if (notification.extras.containsKey(Notification.EXTRA_AUDIO_CONTENTS_URI)) { + uris.add(notification.extras.getParcelable(Notification.EXTRA_AUDIO_CONTENTS_URI)); + } + if (notification.extras.containsKey(Notification.EXTRA_BACKGROUND_IMAGE_URI)) { + uris.add(notification.extras.getParcelable(Notification.EXTRA_BACKGROUND_IMAGE_URI)); + } + if (Notification.MessagingStyle.class.equals(notification.getNotificationStyle())) { + Parcelable[] newMessages = + notification.extras.getParcelableArray(Notification.EXTRA_MESSAGES); + List messages + = Notification.MessagingStyle.Message.getMessagesFromBundleArray(newMessages); + Parcelable[] histMessages = + notification.extras.getParcelableArray(Notification.EXTRA_HISTORIC_MESSAGES); + messages.addAll( + Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages)); + for (Notification.MessagingStyle.Message message : messages) { + uris.add(message.getDataUri()); + } + } + + return uris; + } + public LogMaker getLogMaker(long now) { if (mLogMaker == null) { // initialize fields that only change on update (so a new record) 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 cd324259ce561..d6f61cd12765b 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -38,6 +38,7 @@ import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -49,8 +50,10 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.ActivityManager; +import android.app.IActivityManager; import android.app.INotificationManager; import android.app.Notification; +import android.app.Notification.MessagingStyle.Message; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.app.NotificationManager; @@ -64,9 +67,11 @@ import android.content.pm.PackageManager; import android.content.pm.ParceledListSlice; import android.graphics.Color; import android.media.AudioManager; +import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Bundle; +import android.os.IBinder; import android.os.Process; import android.os.UserHandle; import android.provider.Settings.Secure; @@ -149,6 +154,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Mock private ICompanionDeviceManager mCompanionMgr; @Mock SnoozeHelper mSnoozeHelper; @Mock GroupHelper mGroupHelper; + @Mock + IBinder mPermOwner; + @Mock + IActivityManager mAm; // Use a Testable subclass so we can simulate calls from the system without failing. private static class TestableNotificationManagerService extends NotificationManagerService { @@ -208,6 +217,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mockLightsManager.getLight(anyInt())).thenReturn(mock(Light.class)); when(mAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL); when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false); + when(mAm.newUriPermissionOwner(anyString())).thenReturn(mPermOwner); // write to a test file; the system file isn't readable from tests mFile = new File(mContext.getCacheDir(), "test.xml"); @@ -238,7 +248,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mPackageManager, mPackageManagerClient, mockLightsManager, mListeners, mAssistants, mConditionProviders, mCompanionMgr, mSnoozeHelper, mUsageStats, mPolicyFile, mActivityManager, - mGroupHelper); + mGroupHelper, mAm); } catch (SecurityException e) { if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { throw e; @@ -592,6 +602,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.getActiveNotifications(PKG); assertEquals(0, notifs.length); assertEquals(0, mService.getNotificationRecordCount()); + verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner( + any(), any(), anyInt(), anyInt()); } @Test @@ -610,6 +622,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { ArgumentCaptor captor = ArgumentCaptor.forClass(NotificationStats.class); verify(mListeners, times(1)).notifyRemovedLocked(any(), anyInt(), captor.capture()); assertEquals(NotificationStats.DISMISSAL_OTHER, captor.getValue().getDismissalSurface()); + verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner(any(), any(), anyInt(), anyInt()); } @Test @@ -624,6 +637,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.getActiveNotifications(sbn.getPackageName()); assertEquals(0, notifs.length); assertEquals(0, mService.getNotificationRecordCount()); + verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner(any(), any(), anyInt(), anyInt()); } @Test @@ -637,6 +651,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.getActiveNotifications(sbn.getPackageName()); assertEquals(0, notifs.length); assertEquals(0, mService.getNotificationRecordCount()); + verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner(any(), any(), anyInt(), anyInt()); } @Test @@ -658,6 +673,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { ArgumentCaptor captor = ArgumentCaptor.forClass(NotificationStats.class); verify(mListeners, times(1)).notifyRemovedLocked(any(), anyInt(), captor.capture()); assertEquals(NotificationStats.DISMISSAL_OTHER, captor.getValue().getDismissalSurface()); + verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner(any(), any(), anyInt(), anyInt()); } @Test @@ -676,6 +692,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.cancelAllNotifications(PKG, parent.sbn.getUserId()); waitForIdle(); assertEquals(0, mService.getNotificationRecordCount()); + verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner(any(), any(), anyInt(), anyInt()); } @Test @@ -689,6 +706,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { waitForIdle(); assertEquals(0, mService.getNotificationRecordCount()); + verify(mAm, atLeastOnce()).revokeUriPermissionFromOwner(any(), any(), anyInt(), anyInt()); } @Test @@ -2447,4 +2465,64 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.getBackupPayload(1); assertEquals(1, mService.countSystemChecks - systemChecks); } + + @Test + public void revokeUriPermissions_update() throws Exception { + NotificationChannel c = new NotificationChannel( + TEST_CHANNEL_ID, TEST_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT); + c.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT); + Message message1 = new Message("", 0, ""); + message1.setData("", Uri.fromParts("old", "", "old stuff")); + Message message2 = new Message("", 1, ""); + message2.setData("", Uri.fromParts("new", "", "new stuff")); + + Notification.Builder nb = new Notification.Builder(mContext, c.getId()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.MessagingStyle("") + .addMessage(message1) + .addMessage(message2)); + StatusBarNotification oldSbn = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, + nb.build(), new UserHandle(mUid), null, 0); + NotificationRecord oldRecord = + new NotificationRecord(mContext, oldSbn, c); + + Notification.Builder nb1 = new Notification.Builder(mContext, c.getId()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.MessagingStyle("").addMessage(message2)); + StatusBarNotification newSbn = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, + nb1.build(), new UserHandle(mUid), null, 0); + NotificationRecord newRecord = + new NotificationRecord(mContext, newSbn, c); + + mService.revokeUriPermissions(newRecord, oldRecord); + + verify(mAm, times(1)).revokeUriPermissionFromOwner(any(), eq(message1.getDataUri()), + anyInt(), anyInt()); + } + + @Test + public void revokeUriPermissions_cancel() throws Exception { + NotificationChannel c = new NotificationChannel( + TEST_CHANNEL_ID, TEST_CHANNEL_ID, NotificationManager.IMPORTANCE_DEFAULT); + c.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT); + Message message1 = new Message("", 0, ""); + message1.setData("", Uri.fromParts("old", "", "old stuff")); + + Notification.Builder nb = new Notification.Builder(mContext, c.getId()) + .setContentTitle("foo") + .setSmallIcon(android.R.drawable.sym_def_app_icon) + .setStyle(new Notification.MessagingStyle("") + .addMessage(message1)); + StatusBarNotification oldSbn = new StatusBarNotification(PKG, PKG, 0, "tag", mUid, 0, + nb.build(), new UserHandle(mUid), null, 0); + NotificationRecord oldRecord = + new NotificationRecord(mContext, oldSbn, c); + + mService.revokeUriPermissions(null, oldRecord); + + verify(mAm, times(1)).revokeUriPermissionFromOwner(any(), eq(message1.getDataUri()), + anyInt(), anyInt()); + } }