Revoke Uri access after a NotificationListener is removed.
Fixes: 162233630
Test: android.app.cts.NotificationManagerTest
Change-Id: Ib08bfa96b1ff9497251659ff5cc8465fb3da63c0
(cherry picked from commit 55e611dafb)
This commit is contained in:
@@ -7733,6 +7733,13 @@ public class NotificationManagerService extends SystemService {
|
|||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void updateUriPermissions(@Nullable NotificationRecord newRecord,
|
void updateUriPermissions(@Nullable NotificationRecord newRecord,
|
||||||
@Nullable NotificationRecord oldRecord, String targetPkg, int targetUserId) {
|
@Nullable NotificationRecord oldRecord, String targetPkg, int targetUserId) {
|
||||||
|
updateUriPermissions(newRecord, oldRecord, targetPkg, targetUserId, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
void updateUriPermissions(@Nullable NotificationRecord newRecord,
|
||||||
|
@Nullable NotificationRecord oldRecord, String targetPkg, int targetUserId,
|
||||||
|
boolean onlyRevokeCurrentTarget) {
|
||||||
final String key = (newRecord != null) ? newRecord.getKey() : oldRecord.getKey();
|
final String key = (newRecord != null) ? newRecord.getKey() : oldRecord.getKey();
|
||||||
if (DBG) Slog.d(TAG, key + ": updating permissions");
|
if (DBG) Slog.d(TAG, key + ": updating permissions");
|
||||||
|
|
||||||
@@ -7760,7 +7767,9 @@ public class NotificationManagerService extends SystemService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we have no Uris to grant, but an existing owner, go destroy it
|
// If we have no Uris to grant, but an existing owner, go destroy it
|
||||||
if (newUris == null && permissionOwner != null) {
|
// When revoking permissions of a single listener, destroying the owner will revoke
|
||||||
|
// permissions of other listeners who need to keep access.
|
||||||
|
if (newUris == null && permissionOwner != null && !onlyRevokeCurrentTarget) {
|
||||||
destroyPermissionOwner(permissionOwner, UserHandle.getUserId(oldRecord.getUid()), key);
|
destroyPermissionOwner(permissionOwner, UserHandle.getUserId(oldRecord.getUid()), key);
|
||||||
permissionOwner = null;
|
permissionOwner = null;
|
||||||
}
|
}
|
||||||
@@ -7783,9 +7792,20 @@ public class NotificationManagerService extends SystemService {
|
|||||||
final Uri uri = oldUris.valueAt(i);
|
final Uri uri = oldUris.valueAt(i);
|
||||||
if (newUris == null || !newUris.contains(uri)) {
|
if (newUris == null || !newUris.contains(uri)) {
|
||||||
if (DBG) Slog.d(TAG, key + ": revoking " + uri);
|
if (DBG) Slog.d(TAG, key + ": revoking " + uri);
|
||||||
int userId = ContentProvider.getUserIdFromUri(
|
if (onlyRevokeCurrentTarget) {
|
||||||
uri, UserHandle.getUserId(oldRecord.getUid()));
|
// We're revoking permission from one listener only; other listeners may
|
||||||
revokeUriPermission(permissionOwner, uri, userId);
|
// still need access because the notification may still exist
|
||||||
|
revokeUriPermission(permissionOwner, uri,
|
||||||
|
UserHandle.getUserId(oldRecord.getUid()), targetPkg, targetUserId);
|
||||||
|
} else {
|
||||||
|
// This is broad to unilaterally revoke permissions to this Uri as granted
|
||||||
|
// by this notification. But this code-path can only be used when the
|
||||||
|
// reason for revoking is that the notification posted again without this
|
||||||
|
// Uri, not when removing an individual listener.
|
||||||
|
revokeUriPermission(permissionOwner, uri,
|
||||||
|
UserHandle.getUserId(oldRecord.getUid()),
|
||||||
|
null, UserHandle.USER_ALL);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7814,8 +7834,10 @@ public class NotificationManagerService extends SystemService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void revokeUriPermission(IBinder owner, Uri uri, int userId) {
|
private void revokeUriPermission(IBinder owner, Uri uri, int sourceUserId, String targetPkg,
|
||||||
|
int targetUserId) {
|
||||||
if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
|
if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
|
||||||
|
int userId = ContentProvider.getUserIdFromUri(uri, sourceUserId);
|
||||||
|
|
||||||
final long ident = Binder.clearCallingIdentity();
|
final long ident = Binder.clearCallingIdentity();
|
||||||
try {
|
try {
|
||||||
@@ -7823,7 +7845,7 @@ public class NotificationManagerService extends SystemService {
|
|||||||
owner,
|
owner,
|
||||||
ContentProvider.getUriWithoutUserId(uri),
|
ContentProvider.getUriWithoutUserId(uri),
|
||||||
Intent.FLAG_GRANT_READ_URI_PERMISSION,
|
Intent.FLAG_GRANT_READ_URI_PERMISSION,
|
||||||
userId);
|
userId, targetPkg, targetUserId);
|
||||||
} finally {
|
} finally {
|
||||||
Binder.restoreCallingIdentity(ident);
|
Binder.restoreCallingIdentity(ident);
|
||||||
}
|
}
|
||||||
@@ -9191,7 +9213,7 @@ public class NotificationManagerService extends SystemService {
|
|||||||
final NotificationRankingUpdate update;
|
final NotificationRankingUpdate update;
|
||||||
synchronized (mNotificationLock) {
|
synchronized (mNotificationLock) {
|
||||||
update = makeRankingUpdateLocked(info);
|
update = makeRankingUpdateLocked(info);
|
||||||
grantUriPermissionsForActiveNotificationsLocked(info);
|
updateUriPermissionsForActiveNotificationsLocked(info, true);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
listener.onListenerConnected(update);
|
listener.onListenerConnected(update);
|
||||||
@@ -9203,6 +9225,7 @@ public class NotificationManagerService extends SystemService {
|
|||||||
@Override
|
@Override
|
||||||
@GuardedBy("mNotificationLock")
|
@GuardedBy("mNotificationLock")
|
||||||
protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
|
protected void onServiceRemovedLocked(ManagedServiceInfo removed) {
|
||||||
|
updateUriPermissionsForActiveNotificationsLocked(removed, false);
|
||||||
if (removeDisabledHints(removed)) {
|
if (removeDisabledHints(removed)) {
|
||||||
updateListenerHintsLocked();
|
updateListenerHintsLocked();
|
||||||
updateEffectsSuppressorLocked();
|
updateEffectsSuppressorLocked();
|
||||||
@@ -9269,8 +9292,7 @@ public class NotificationManagerService extends SystemService {
|
|||||||
|
|
||||||
for (final ManagedServiceInfo info : getServices()) {
|
for (final ManagedServiceInfo info : getServices()) {
|
||||||
boolean sbnVisible = isVisibleToListener(sbn, info);
|
boolean sbnVisible = isVisibleToListener(sbn, info);
|
||||||
boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info)
|
boolean oldSbnVisible = (oldSbn != null) && isVisibleToListener(oldSbn, info);
|
||||||
: false;
|
|
||||||
// This notification hasn't been and still isn't visible -> ignore.
|
// This notification hasn't been and still isn't visible -> ignore.
|
||||||
if (!oldSbnVisible && !sbnVisible) {
|
if (!oldSbnVisible && !sbnVisible) {
|
||||||
continue;
|
continue;
|
||||||
@@ -9313,28 +9335,42 @@ public class NotificationManagerService extends SystemService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronously grant permissions to Uris for all active and visible notifications to the
|
* Synchronously grant or revoke permissions to Uris for all active and visible
|
||||||
* NotificationListenerService provided.
|
* notifications to just the NotificationListenerService provided.
|
||||||
*/
|
*/
|
||||||
@GuardedBy("mNotificationLock")
|
@GuardedBy("mNotificationLock")
|
||||||
private void grantUriPermissionsForActiveNotificationsLocked(ManagedServiceInfo info) {
|
private void updateUriPermissionsForActiveNotificationsLocked(
|
||||||
|
ManagedServiceInfo info, boolean grant) {
|
||||||
try {
|
try {
|
||||||
for (final NotificationRecord r : mNotificationList) {
|
for (final NotificationRecord r : mNotificationList) {
|
||||||
// This notification isn't visible -> ignore.
|
// When granting permissions, ignore notifications which are invisible.
|
||||||
if (!isVisibleToListener(r.getSbn(), info)) {
|
// When revoking permissions, all notifications are invisible, so process all.
|
||||||
|
if (grant && !isVisibleToListener(r.getSbn(), info)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// If the notification is hidden, permissions are not required by the listener.
|
// If the notification is hidden, permissions are not required by the listener.
|
||||||
if (r.isHidden() && info.targetSdkVersion < Build.VERSION_CODES.P) {
|
if (r.isHidden() && info.targetSdkVersion < Build.VERSION_CODES.P) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Grant access before listener is initialized
|
// Grant or revoke access synchronously
|
||||||
final int targetUserId = (info.userid == UserHandle.USER_ALL)
|
final int targetUserId = (info.userid == UserHandle.USER_ALL)
|
||||||
? UserHandle.USER_SYSTEM : info.userid;
|
? UserHandle.USER_SYSTEM : info.userid;
|
||||||
updateUriPermissions(r, null, info.component.getPackageName(), targetUserId);
|
if (grant) {
|
||||||
|
// Grant permissions by passing arguments as if the notification is new.
|
||||||
|
updateUriPermissions(/* newRecord */ r, /* oldRecord */ null,
|
||||||
|
info.component.getPackageName(), targetUserId);
|
||||||
|
} else {
|
||||||
|
// Revoke permissions by passing arguments as if the notification was
|
||||||
|
// removed, but set `onlyRevokeCurrentTarget` to avoid revoking permissions
|
||||||
|
// granted to *other* targets by this notification's URIs.
|
||||||
|
updateUriPermissions(/* newRecord */ null, /* oldRecord */ r,
|
||||||
|
info.component.getPackageName(), targetUserId,
|
||||||
|
/* onlyRevokeCurrentTarget */ true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Slog.e(TAG, "Could not grant Uri permissions to " + info.component, e);
|
Slog.e(TAG, "Could not " + (grant ? "grant" : "revoke") + " Uri permissions to "
|
||||||
|
+ info.component, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9373,18 +9409,11 @@ public class NotificationManagerService extends SystemService {
|
|||||||
final NotificationStats stats = mAssistants.isServiceTokenValidLocked(info.service)
|
final NotificationStats stats = mAssistants.isServiceTokenValidLocked(info.service)
|
||||||
? notificationStats : null;
|
? notificationStats : null;
|
||||||
final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
|
final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
|
||||||
mHandler.post(new Runnable() {
|
mHandler.post(() -> notifyRemoved(info, sbnLight, update, stats, reason));
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
notifyRemoved(info, sbnLight, update, stats, reason);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Revoke access after all listeners have been updated
|
// Revoke access after all listeners have been updated
|
||||||
mHandler.post(() -> {
|
mHandler.post(() -> updateUriPermissions(null, r, null, UserHandle.USER_SYSTEM));
|
||||||
updateUriPermissions(null, r, null, UserHandle.USER_SYSTEM);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -75,10 +75,31 @@ public interface UriGrantsManagerInternal {
|
|||||||
void removeUriPermissionsForPackage(
|
void removeUriPermissionsForPackage(
|
||||||
String packageName, int userHandle, boolean persistable, boolean targetOnly);
|
String packageName, int userHandle, boolean persistable, boolean targetOnly);
|
||||||
/**
|
/**
|
||||||
* @param uri This uri must NOT contain an embedded userId.
|
* Remove any {@link UriPermission} associated with the owner whose values match the given
|
||||||
|
* filtering parameters.
|
||||||
|
*
|
||||||
|
* @param token An opaque owner token as returned by {@link #newUriPermissionOwner(String)}.
|
||||||
|
* @param uri This uri must NOT contain an embedded userId. {@code null} to apply to all Uris.
|
||||||
|
* @param mode The modes (as a bitmask) to revoke.
|
||||||
* @param userId The userId in which the uri is to be resolved.
|
* @param userId The userId in which the uri is to be resolved.
|
||||||
*/
|
*/
|
||||||
void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode, int userId);
|
void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode, int userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove any {@link UriPermission} associated with the owner whose values match the given
|
||||||
|
* filtering parameters.
|
||||||
|
*
|
||||||
|
* @param token An opaque owner token as returned by {@link #newUriPermissionOwner(String)}.
|
||||||
|
* @param uri This uri must NOT contain an embedded userId. {@code null} to apply to all Uris.
|
||||||
|
* @param mode The modes (as a bitmask) to revoke.
|
||||||
|
* @param userId The userId in which the uri is to be resolved.
|
||||||
|
* @param targetPkg Calling package name to match, or {@code null} to apply to all packages.
|
||||||
|
* @param targetUserId Calling user to match, or {@link UserHandle#USER_ALL} to apply to all
|
||||||
|
* users.
|
||||||
|
*/
|
||||||
|
void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode, int userId,
|
||||||
|
String targetPkg, int targetUserId);
|
||||||
|
|
||||||
boolean checkAuthorityGrants(
|
boolean checkAuthorityGrants(
|
||||||
int callingUid, ProviderInfo cpi, int userId, boolean checkUser);
|
int callingUid, ProviderInfo cpi, int userId, boolean checkUser);
|
||||||
void dump(PrintWriter pw, boolean dumpAll, String dumpPackage);
|
void dump(PrintWriter pw, boolean dumpAll, String dumpPackage);
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ import android.app.AppGlobals;
|
|||||||
import android.app.GrantedUriPermission;
|
import android.app.GrantedUriPermission;
|
||||||
import android.app.IUriGrantsManager;
|
import android.app.IUriGrantsManager;
|
||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.ContentProvider;
|
import android.content.ContentProvider;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@@ -88,11 +87,11 @@ import com.android.server.LocalServices;
|
|||||||
import com.android.server.SystemService;
|
import com.android.server.SystemService;
|
||||||
import com.android.server.SystemServiceManager;
|
import com.android.server.SystemServiceManager;
|
||||||
|
|
||||||
import libcore.io.IoUtils;
|
|
||||||
|
|
||||||
import com.google.android.collect.Lists;
|
import com.google.android.collect.Lists;
|
||||||
import com.google.android.collect.Maps;
|
import com.google.android.collect.Maps;
|
||||||
|
|
||||||
|
import libcore.io.IoUtils;
|
||||||
|
|
||||||
import org.xmlpull.v1.XmlPullParser;
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
import org.xmlpull.v1.XmlSerializer;
|
import org.xmlpull.v1.XmlSerializer;
|
||||||
@@ -1431,16 +1430,18 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode, int userId) {
|
public void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode, int userId) {
|
||||||
|
revokeUriPermissionFromOwner(token, uri, mode, userId, null, UserHandle.USER_ALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode, int userId,
|
||||||
|
String targetPkg, int targetUserId) {
|
||||||
final UriPermissionOwner owner = UriPermissionOwner.fromExternalToken(token);
|
final UriPermissionOwner owner = UriPermissionOwner.fromExternalToken(token);
|
||||||
if (owner == null) {
|
if (owner == null) {
|
||||||
throw new IllegalArgumentException("Unknown owner: " + token);
|
throw new IllegalArgumentException("Unknown owner: " + token);
|
||||||
}
|
}
|
||||||
|
GrantUri grantUri = uri == null ? null : new GrantUri(userId, uri, mode);
|
||||||
if (uri == null) {
|
owner.removeUriPermission(grantUri, mode, targetPkg, targetUserId);
|
||||||
owner.removeUriPermissions(mode);
|
|
||||||
} else {
|
|
||||||
owner.removeUriPermission(new GrantUri(userId, uri, mode), mode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import static android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
|
|||||||
|
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import android.os.UserHandle;
|
||||||
import android.util.ArraySet;
|
import android.util.ArraySet;
|
||||||
import android.util.proto.ProtoOutputStream;
|
import android.util.proto.ProtoOutputStream;
|
||||||
|
|
||||||
@@ -74,31 +75,48 @@ public class UriPermissionOwner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void removeUriPermission(GrantUri grantUri, int mode) {
|
void removeUriPermission(GrantUri grantUri, int mode) {
|
||||||
|
removeUriPermission(grantUri, mode, null, UserHandle.USER_ALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeUriPermission(GrantUri grantUri, int mode, String targetPgk, int targetUserId) {
|
||||||
if ((mode & FLAG_GRANT_READ_URI_PERMISSION) != 0 && mReadPerms != null) {
|
if ((mode & FLAG_GRANT_READ_URI_PERMISSION) != 0 && mReadPerms != null) {
|
||||||
Iterator<UriPermission> it = mReadPerms.iterator();
|
Iterator<UriPermission> it = mReadPerms.iterator();
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
UriPermission perm = it.next();
|
UriPermission perm = it.next();
|
||||||
if (grantUri == null || grantUri.equals(perm.uri)) {
|
if (grantUri != null && !grantUri.equals(perm.uri)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (targetPgk != null && !targetPgk.equals(perm.targetPkg)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (targetUserId != UserHandle.USER_ALL && targetUserId != perm.targetUserId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
perm.removeReadOwner(this);
|
perm.removeReadOwner(this);
|
||||||
mService.removeUriPermissionIfNeeded(perm);
|
mService.removeUriPermissionIfNeeded(perm);
|
||||||
it.remove();
|
it.remove();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (mReadPerms.isEmpty()) {
|
if (mReadPerms.isEmpty()) {
|
||||||
mReadPerms = null;
|
mReadPerms = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ((mode & FLAG_GRANT_WRITE_URI_PERMISSION) != 0
|
if ((mode & FLAG_GRANT_WRITE_URI_PERMISSION) != 0 && mWritePerms != null) {
|
||||||
&& mWritePerms != null) {
|
|
||||||
Iterator<UriPermission> it = mWritePerms.iterator();
|
Iterator<UriPermission> it = mWritePerms.iterator();
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
UriPermission perm = it.next();
|
UriPermission perm = it.next();
|
||||||
if (grantUri == null || grantUri.equals(perm.uri)) {
|
if (grantUri != null && !grantUri.equals(perm.uri)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (targetPgk != null && !targetPgk.equals(perm.targetPkg)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (targetUserId != UserHandle.USER_ALL && targetUserId != perm.targetUserId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
perm.removeWriteOwner(this);
|
perm.removeWriteOwner(this);
|
||||||
mService.removeUriPermissionIfNeeded(perm);
|
mService.removeUriPermissionIfNeeded(perm);
|
||||||
it.remove();
|
it.remove();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (mWritePerms.isEmpty()) {
|
if (mWritePerms.isEmpty()) {
|
||||||
mWritePerms = null;
|
mWritePerms = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3865,7 +3865,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
|
|||||||
mService.updateUriPermissions(recordB, recordA, mContext.getPackageName(),
|
mService.updateUriPermissions(recordB, recordA, mContext.getPackageName(),
|
||||||
USER_SYSTEM);
|
USER_SYSTEM);
|
||||||
verify(mUgmInternal, times(1)).revokeUriPermissionFromOwner(any(),
|
verify(mUgmInternal, times(1)).revokeUriPermissionFromOwner(any(),
|
||||||
eq(message1.getDataUri()), anyInt(), anyInt());
|
eq(message1.getDataUri()), anyInt(), anyInt(), eq(null), eq(-1));
|
||||||
|
|
||||||
// Update back means we grant access to first again
|
// Update back means we grant access to first again
|
||||||
reset(mUgm);
|
reset(mUgm);
|
||||||
|
|||||||
Reference in New Issue
Block a user