Merge "Track and destroy inline URI grants separately from Notification URIs."
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index 499a4d2..d703b86 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -21,6 +21,7 @@
import android.content.ComponentName;
import android.graphics.Rect;
import android.os.Bundle;
+import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.hardware.biometrics.IBiometricServiceReceiverInternal;
@@ -78,7 +79,8 @@
void onNotificationSettingsViewed(String key);
void setSystemUiVisibility(int displayId, int vis, int mask, String cause);
void onNotificationBubbleChanged(String key, boolean isBubble);
- void grantInlineReplyUriPermission(String key, in Uri uri);
+ void grantInlineReplyUriPermission(String key, in Uri uri, in UserHandle user, String packageName);
+ void clearInlineReplyUriPermissions(String key);
void onGlobalActionsShown();
void onGlobalActionsHidden();
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
index 3acbc3a..706727b 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
@@ -89,6 +89,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
+import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.tv.TvStatusBar;
@@ -290,7 +291,8 @@
DozeScrimController dozeScrimController,
CommandQueue commandQueue,
PluginManager pluginManager,
- CarNavigationBarController carNavigationBarController) {
+ CarNavigationBarController carNavigationBarController,
+ RemoteInputUriController remoteInputUriController) {
return new CarStatusBar(
context,
featureFlags,
@@ -357,6 +359,7 @@
dozeScrimController,
commandQueue,
pluginManager,
+ remoteInputUriController,
carNavigationBarController);
}
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 7ab2036..110b32b 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -128,6 +128,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
+import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.util.InjectionInflationController;
@@ -301,7 +302,7 @@
DozeScrimController dozeScrimController,
CommandQueue commandQueue,
PluginManager pluginManager,
-
+ RemoteInputUriController remoteInputUriController,
/* Car Settings injected components. */
CarNavigationBarController carNavigationBarController) {
super(
@@ -370,7 +371,8 @@
powerManager,
dozeScrimController,
commandQueue,
- pluginManager);
+ pluginManager,
+ remoteInputUriController);
mScrimController = scrimController;
mCarNavigationBarController = carNavigationBarController;
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
index 4f429d3..3b0c9ae 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java
@@ -96,6 +96,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
+import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.statusbar.tv.TvStatusBar;
@@ -287,7 +288,8 @@
PowerManager powerManager,
DozeScrimController dozeScrimController,
CommandQueue commandQueue,
- PluginManager pluginManager) {
+ PluginManager pluginManager,
+ RemoteInputUriController remoteInputUriController) {
return new StatusBar(
context,
featureFlags,
@@ -354,7 +356,8 @@
powerManager,
dozeScrimController,
commandQueue,
- pluginManager);
+ pluginManager,
+ remoteInputUriController);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index c838ac5..35f06f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -59,6 +59,7 @@
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.statusbar.policy.RemoteInputView;
import java.io.FileDescriptor;
@@ -121,6 +122,7 @@
private final UserManager mUserManager;
private final KeyguardManager mKeyguardManager;
private final StatusBarStateController mStatusBarStateController;
+ private final RemoteInputUriController mRemoteInputUriController;
protected RemoteInputController mRemoteInputController;
protected NotificationLifetimeExtender.NotificationSafeToRemoveCallback
@@ -260,7 +262,8 @@
NotificationEntryManager notificationEntryManager,
Lazy<ShadeController> shadeController,
StatusBarStateController statusBarStateController,
- @MainHandler Handler mainHandler) {
+ @MainHandler Handler mainHandler,
+ RemoteInputUriController remoteInputUriController) {
mContext = context;
mLockscreenUserManager = lockscreenUserManager;
mSmartReplyController = smartReplyController;
@@ -273,6 +276,7 @@
addLifetimeExtenders();
mKeyguardManager = context.getSystemService(KeyguardManager.class);
mStatusBarStateController = statusBarStateController;
+ mRemoteInputUriController = remoteInputUriController;
notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
@Override
@@ -300,7 +304,7 @@
/** Initializes this component with the provided dependencies. */
public void setUpWithCallback(Callback callback, RemoteInputController.Delegate delegate) {
mCallback = callback;
- mRemoteInputController = new RemoteInputController(delegate);
+ mRemoteInputController = new RemoteInputController(delegate, mRemoteInputUriController);
mRemoteInputController.addCallback(new RemoteInputController.Callback() {
@Override
public void onRemoteInputSent(NotificationEntry entry) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index 998cf52..778443c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -19,12 +19,15 @@
import android.app.Notification;
import android.app.RemoteInput;
import android.content.Context;
+import android.net.Uri;
import android.os.SystemProperties;
+import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.util.Pair;
import com.android.internal.util.Preconditions;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.statusbar.policy.RemoteInputView;
import java.lang.ref.WeakReference;
@@ -43,9 +46,12 @@
private final ArrayMap<String, Object> mSpinning = new ArrayMap<>();
private final ArrayList<Callback> mCallbacks = new ArrayList<>(3);
private final Delegate mDelegate;
+ private final RemoteInputUriController mRemoteInputUriController;
- public RemoteInputController(Delegate delegate) {
+ public RemoteInputController(Delegate delegate,
+ RemoteInputUriController remoteInputUriController) {
mDelegate = delegate;
+ mRemoteInputUriController = remoteInputUriController;
}
/**
@@ -272,6 +278,14 @@
mDelegate.lockScrollTo(entry);
}
+ /**
+ * Create a temporary grant which allows the app that submitted the notification access to the
+ * specified URI.
+ */
+ public void grantInlineReplyUriPermission(StatusBarNotification sbn, Uri data) {
+ mRemoteInputUriController.grantInlineReplyUriPermission(sbn, data);
+ }
+
public interface Callback {
default void onRemoteInputActive(boolean active) {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 8f13741..4148a73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -238,6 +238,7 @@
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
+import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -401,6 +402,7 @@
private final DozeParameters mDozeParameters;
private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
private final PluginManager mPluginManager;
+ private final RemoteInputUriController mRemoteInputUriController;
// expanded notifications
protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
@@ -703,7 +705,8 @@
PowerManager powerManager,
DozeScrimController dozeScrimController,
CommandQueue commandQueue,
- PluginManager pluginManager) {
+ PluginManager pluginManager,
+ RemoteInputUriController remoteInputUriController) {
super(context);
mFeatureFlags = featureFlags;
mLightBarController = lightBarController;
@@ -770,7 +773,7 @@
mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
mCommandQueue = commandQueue;
mPluginManager = pluginManager;
-
+ mRemoteInputUriController = remoteInputUriController;
mBubbleExpandListener =
(isExpanding, key) -> {
mEntryManager.updateNotifications("onBubbleExpandChanged");
@@ -1296,6 +1299,8 @@
mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
mEntryManager.setRowBinder(rowBinder);
+ mRemoteInputUriController.attach(mEntryManager);
+
rowBinder.setNotificationClicker(new NotificationClicker(
this, mBubbleController, mNotificationActivityStarter));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputUriController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputUriController.java
new file mode 100644
index 0000000..4d912de
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputUriController.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import android.net.Uri;
+import android.os.RemoteException;
+import android.service.notification.StatusBarNotification;
+import android.util.Log;
+
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Handles granting and revoking inline URI grants associated with RemoteInputs.
+ */
+@Singleton
+public class RemoteInputUriController {
+
+ private final IStatusBarService mStatusBarManagerService;
+ private static final String TAG = "RemoteInputUriController";
+
+ @Inject
+ public RemoteInputUriController(IStatusBarService statusBarService) {
+ mStatusBarManagerService = statusBarService;
+ }
+
+ /**
+ * Attach this controller as a listener to the provided NotificationEntryManager to ensure
+ * that RemoteInput URI grants are cleaned up when the notification entry is removed from
+ * the shade.
+ */
+ public void attach(NotificationEntryManager manager) {
+ manager.addNotificationEntryListener(mInlineUriListener);
+ }
+
+ /**
+ * Create a temporary grant which allows the app that submitted the notification access to the
+ * specified URI.
+ */
+ public void grantInlineReplyUriPermission(StatusBarNotification sbn, Uri data) {
+ try {
+ mStatusBarManagerService.grantInlineReplyUriPermission(
+ sbn.getKey(), data, sbn.getUser(), sbn.getPackageName());
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to grant URI permissions:" + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Ensures that inline URI permissions are cleared when notification entries are removed from
+ * the shade.
+ */
+ private final NotificationEntryListener mInlineUriListener = new NotificationEntryListener() {
+ @Override
+ public void onEntryRemoved(NotificationEntry entry, NotificationVisibility visibility,
+ boolean removedByUser) {
+ try {
+ mStatusBarManagerService.clearInlineReplyUriPermissions(entry.getKey());
+ } catch (RemoteException ex) {
+ ex.rethrowFromSystemServer();
+ }
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 502a9bd..307e3bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -173,12 +173,7 @@
protected Intent prepareRemoteInputFromData(String contentType, Uri data) {
HashMap<String, Uri> results = new HashMap<>();
results.put(contentType, data);
- try {
- mStatusBarManagerService.grantInlineReplyUriPermission(
- mEntry.getSbn().getKey(), data);
- } catch (Exception e) {
- Log.e(TAG, "Failed to grant URI permissions:" + e.getMessage(), e);
- }
+ mController.grantInlineReplyUriPermission(mEntry.getSbn(), data);
Intent fillInIntent = new Intent().addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
RemoteInput.addDataResultToIntent(mRemoteInput, fillInIntent, results);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index 2514c93..a754a00d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -31,6 +31,7 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.google.android.collect.Sets;
@@ -57,6 +58,7 @@
@Mock private NotificationListenerService.RankingMap mRanking;
@Mock private ExpandableNotificationRow mRow;
@Mock private StatusBarStateController mStateController;
+ @Mock private RemoteInputUriController mRemoteInputUriController;
// Dependency mocks:
@Mock private NotificationEntryManager mEntryManager;
@@ -76,7 +78,8 @@
mLockscreenUserManager, mSmartReplyController, mEntryManager,
() -> mock(ShadeController.class),
mStateController,
- Handler.createAsync(Looper.myLooper()));
+ Handler.createAsync(Looper.myLooper()),
+ mRemoteInputUriController);
mEntry = new NotificationEntryBuilder()
.setPkg(TEST_PACKAGE_NAME)
.setOpPkg(TEST_PACKAGE_NAME)
@@ -211,9 +214,11 @@
NotificationEntryManager notificationEntryManager,
Lazy<ShadeController> shadeController,
StatusBarStateController statusBarStateController,
- Handler mainHandler) {
+ Handler mainHandler,
+ RemoteInputUriController remoteInputUriController) {
super(context, lockscreenUserManager, smartReplyController, notificationEntryManager,
- shadeController, statusBarStateController, mainHandler);
+ shadeController, statusBarStateController, mainHandler,
+ remoteInputUriController);
}
public void setUpWithPresenterForTest(Callback callback,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
index 88fb3e1..95ce53c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
@@ -42,6 +42,7 @@
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.policy.RemoteInputUriController;
import org.junit.Before;
import org.junit.Test;
@@ -72,6 +73,7 @@
@Mock private NotificationEntryManager mNotificationEntryManager;
@Mock private IStatusBarService mIStatusBarService;
@Mock private StatusBarStateController mStatusBarStateController;
+ @Mock private RemoteInputUriController mRemoteInputUriController;
@Before
public void setUp() {
@@ -88,7 +90,8 @@
mock(NotificationLockscreenUserManager.class), mSmartReplyController,
mNotificationEntryManager, () -> mock(ShadeController.class),
mStatusBarStateController,
- Handler.createAsync(Looper.myLooper()));
+ Handler.createAsync(Looper.myLooper()),
+ mRemoteInputUriController);
mRemoteInputManager.setUpWithCallback(mCallback, mDelegate);
mNotification = new Notification.Builder(mContext, "")
.setSmallIcon(R.drawable.ic_person)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 5d15205..c21e3ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -131,6 +131,7 @@
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.NetworkController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
+import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.util.InjectionInflationController;
@@ -181,6 +182,7 @@
@Mock private NotificationLockscreenUserManager mLockscreenUserManager;
@Mock private NotificationRemoteInputManager mRemoteInputManager;
@Mock private RemoteInputController mRemoteInputController;
+ @Mock private RemoteInputUriController mRemoteInputUriController;
@Mock private StatusBarStateControllerImpl mStatusBarStateController;
@Mock private BatteryController mBatteryController;
@Mock private DeviceProvisionedController mDeviceProvisionedController;
@@ -369,7 +371,8 @@
mPowerManager,
mDozeScrimController,
mCommandQueue,
- mPluginManager);
+ mPluginManager,
+ mRemoteInputUriController);
when(mStatusBarWindowView.findViewById(R.id.lock_icon_container)).thenReturn(
mLockIconContainer);
diff --git a/services/core/java/com/android/server/notification/InlineReplyUriRecord.java b/services/core/java/com/android/server/notification/InlineReplyUriRecord.java
new file mode 100644
index 0000000..76cfb03
--- /dev/null
+++ b/services/core/java/com/android/server/notification/InlineReplyUriRecord.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import android.net.Uri;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.util.ArraySet;
+
+/**
+ * A record of inline reply (ex. RemoteInput) URI grants associated with a Notification.
+ */
+public final class InlineReplyUriRecord {
+ private final IBinder mPermissionOwner;
+ private final ArraySet<Uri> mUris;
+ private final UserHandle mUser;
+ private final String mPackageName;
+ private final String mKey;
+
+ /**
+ * Construct a new InlineReplyUriRecord.
+ * @param owner The PermissionOwner associated with this record.
+ * @param user The user associated with this record.
+ * @param packageName The name of the package which posted the notification.
+ * @param key The key of the original NotificationRecord this notification as created with.
+ */
+ public InlineReplyUriRecord(IBinder owner, UserHandle user, String packageName, String key) {
+ mPermissionOwner = owner;
+ mUris = new ArraySet<>();
+ mUser = user;
+ mPackageName = packageName;
+ mKey = key;
+ }
+
+ /**
+ * Get the permission owner associated with this record.
+ */
+ public IBinder getPermissionOwner() {
+ return mPermissionOwner;
+ }
+
+ /**
+ * Get the content URIs associated with this record.
+ */
+ public ArraySet<Uri> getUris() {
+ return mUris;
+ }
+
+ /**
+ * Associate a new content URI with this record.
+ */
+ public void addUri(Uri uri) {
+ mUris.add(uri);
+ }
+
+ /**
+ * Get the user id associated with this record.
+ * If the UserHandle associated with this record belongs to USER_ALL, return the ID for
+ * USER_SYSTEM instead, to avoid errors around modifying URI permissions for an invalid user ID.
+ */
+ public int getUserId() {
+ int userId = mUser.getIdentifier();
+ if (userId == UserHandle.USER_ALL) {
+ return UserHandle.USER_SYSTEM;
+ } else {
+ return userId;
+ }
+ }
+
+ /**
+ * Get the name of the package associated with this record.
+ */
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Get the key associated with this record.
+ */
+ public String getKey() {
+ return mKey;
+ }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java
index 6f0ad33..88fc072 100644
--- a/services/core/java/com/android/server/notification/NotificationDelegate.java
+++ b/services/core/java/com/android/server/notification/NotificationDelegate.java
@@ -18,6 +18,7 @@
import android.app.Notification;
import android.net.Uri;
+import android.os.UserHandle;
import android.service.notification.NotificationStats;
import com.android.internal.statusbar.NotificationVisibility;
@@ -53,7 +54,13 @@
* Grant permission to read the specified URI to the package associated with the
* NotificationRecord associated with the given key.
*/
- void grantInlineReplyUriPermission(String key, Uri uri, int callingUid);
+ void grantInlineReplyUriPermission(String key, Uri uri, UserHandle user, String packageName,
+ int callingUid);
+
+ /**
+ * Clear inline URI grants associated with the given notification.
+ */
+ void clearInlineReplyUriPermissions(String key, int callingUid);
/**
* Notifies that smart replies and actions have been added to the UI.
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 0fc1718..5e76401 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -415,6 +415,8 @@
@GuardedBy("mNotificationLock")
final ArrayMap<String, NotificationRecord> mNotificationsByKey = new ArrayMap<>();
@GuardedBy("mNotificationLock")
+ final ArrayMap<String, InlineReplyUriRecord> mInlineReplyRecordsByKey = new ArrayMap<>();
+ @GuardedBy("mNotificationLock")
final ArrayList<NotificationRecord> mEnqueuedNotifications = new ArrayList<>();
@GuardedBy("mNotificationLock")
final ArrayMap<Integer, ArrayMap<String, String>> mAutobundledSummaries = new ArrayMap<>();
@@ -1167,42 +1169,53 @@
* user associated with the NotificationRecord, and this grant will fail when trying
* to grant URI permissions across users.
*/
- public void grantInlineReplyUriPermission(String key, Uri uri, int callingUid) {
+ public void grantInlineReplyUriPermission(String key, Uri uri, UserHandle user,
+ String packageName, int callingUid) {
synchronized (mNotificationLock) {
- NotificationRecord r = mNotificationsByKey.get(key);
- if (r != null) {
- IBinder owner = r.permissionOwner;
- if (owner == null) {
- r.permissionOwner = mUgmInternal.newUriPermissionOwner("NOTIF:" + key);
- owner = r.permissionOwner;
- }
- int uid = callingUid;
- int userId = r.sbn.getUserId();
- if (userId == UserHandle.USER_ALL) {
- userId = USER_SYSTEM;
- }
- if (UserHandle.getUserId(uid) != userId) {
- try {
- final String[] pkgs = mPackageManager.getPackagesForUid(callingUid);
- if (pkgs == null) {
- Log.e(TAG, "Cannot grant uri permission to unknown UID: "
- + callingUid);
- }
- final String pkg = pkgs[0]; // Get the SystemUI package
- // Find the UID for SystemUI for the correct user
- uid = mPackageManager.getPackageUid(pkg, 0, userId);
- } catch (RemoteException re) {
- Log.e(TAG, "Cannot talk to package manager", re);
+ InlineReplyUriRecord r = mInlineReplyRecordsByKey.get(key);
+ if (r == null) {
+ InlineReplyUriRecord newRecord = new InlineReplyUriRecord(
+ mUgmInternal.newUriPermissionOwner("INLINE_REPLY:" + key),
+ user,
+ packageName,
+ key);
+ r = newRecord;
+ mInlineReplyRecordsByKey.put(key, r);
+ }
+ IBinder owner = r.getPermissionOwner();
+ int uid = callingUid;
+ int userId = r.getUserId();
+ if (UserHandle.getUserId(uid) != userId) {
+ try {
+ final String[] pkgs = mPackageManager.getPackagesForUid(callingUid);
+ if (pkgs == null) {
+ Log.e(TAG, "Cannot grant uri permission to unknown UID: "
+ + callingUid);
}
+ final String pkg = pkgs[0]; // Get the SystemUI package
+ // Find the UID for SystemUI for the correct user
+ uid = mPackageManager.getPackageUid(pkg, 0, userId);
+ } catch (RemoteException re) {
+ Log.e(TAG, "Cannot talk to package manager", re);
}
- grantUriPermission(owner, uri, uid, r.sbn.getPackageName(), userId);
- } else {
- Log.w(TAG, "No record found for notification key:" + key);
+ }
+ r.addUri(uri);
+ grantUriPermission(owner, uri, uid, r.getPackageName(), userId);
+ }
+ }
- // TODO: figure out cancel story. I think it's: sysui needs to tell us
- // whenever noitifications held by a lifetimextender go away
- // IBinder owner = mUgmInternal.newUriPermissionOwner("InlineReply:" + key);
- // pass in userId and package as well as key (key for logging purposes)
+ @Override
+ /**
+ * Clears inline URI permission grants by destroying the permission owner for the specified
+ * notification.
+ */
+ public void clearInlineReplyUriPermissions(String key, int callingUid) {
+ synchronized (mNotificationLock) {
+ InlineReplyUriRecord uriRecord = mInlineReplyRecordsByKey.get(key);
+ if (uriRecord != null) {
+ destroyPermissionOwner(uriRecord.getPermissionOwner(), uriRecord.getUserId(),
+ "INLINE_REPLY: " + uriRecord.getKey());
+ mInlineReplyRecordsByKey.remove(key);
}
}
}
@@ -7036,15 +7049,8 @@
// If we have no Uris to grant, but an existing owner, go destroy it
if (newUris == null && permissionOwner != null) {
- final long ident = Binder.clearCallingIdentity();
- try {
- if (DBG) Slog.d(TAG, key + ": destroying owner");
- mUgmInternal.revokeUriPermissionFromOwner(permissionOwner, null, ~0,
- UserHandle.getUserId(oldRecord.getUid()));
- permissionOwner = null;
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
+ destroyPermissionOwner(permissionOwner, UserHandle.getUserId(oldRecord.getUid()), key);
+ permissionOwner = null;
}
// Grant access to new Uris
@@ -7065,7 +7071,9 @@
final Uri uri = oldUris.valueAt(i);
if (newUris == null || !newUris.contains(uri)) {
if (DBG) Slog.d(TAG, key + ": revoking " + uri);
- revokeUriPermission(permissionOwner, uri, oldRecord.getUid());
+ int userId = ContentProvider.getUserIdFromUri(
+ uri, UserHandle.getUserId(oldRecord.getUid()));
+ revokeUriPermission(permissionOwner, uri, userId);
}
}
}
@@ -7092,7 +7100,7 @@
}
}
- private void revokeUriPermission(IBinder owner, Uri uri, int sourceUid) {
+ private void revokeUriPermission(IBinder owner, Uri uri, int userId) {
if (uri == null || !ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) return;
final long ident = Binder.clearCallingIdentity();
@@ -7101,7 +7109,17 @@
owner,
ContentProvider.getUriWithoutUserId(uri),
Intent.FLAG_GRANT_READ_URI_PERMISSION,
- ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(sourceUid)));
+ userId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private void destroyPermissionOwner(IBinder owner, int userId, String logKey) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (DBG) Slog.d(TAG, logKey + ": destroying owner");
+ mUgmInternal.revokeUriPermissionFromOwner(owner, null, ~0, userId);
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 489c343..effeb80 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -1364,12 +1364,26 @@
}
@Override
- public void grantInlineReplyUriPermission(String key, Uri uri) {
+ public void grantInlineReplyUriPermission(String key, Uri uri, UserHandle user,
+ String packageName) {
enforceStatusBarService();
int callingUid = Binder.getCallingUid();
long identity = Binder.clearCallingIdentity();
try {
- mNotificationDelegate.grantInlineReplyUriPermission(key, uri, callingUid);
+ mNotificationDelegate.grantInlineReplyUriPermission(key, uri, user, packageName,
+ callingUid);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void clearInlineReplyUriPermissions(String key) {
+ enforceStatusBarService();
+ int callingUid = Binder.getCallingUid();
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mNotificationDelegate.clearInlineReplyUriPermissions(key, callingUid);
} finally {
Binder.restoreCallingIdentity(identity);
}
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 cd0f4f1..1ee71fb 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -5478,7 +5478,7 @@
Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1);
mService.mNotificationDelegate.grantInlineReplyUriPermission(
- nr.getKey(), uri, nr.sbn.getUid());
+ nr.getKey(), uri, nr.sbn.getUser(), nr.sbn.getPackageName(), nr.sbn.getUid());
// Grant permission called for the UID of SystemUI under the target user ID
verify(mUgm, times(1)).grantUriPermissionFromOwner(any(),
@@ -5487,6 +5487,27 @@
}
@Test
+ public void testGrantInlineReplyUriPermission_noRecordExists() throws Exception {
+ NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, 0);
+ waitForIdle();
+
+ // No notifications exist for the given record
+ StatusBarNotification[] notifsBefore = mBinderService.getActiveNotifications(PKG);
+ assertEquals(0, notifsBefore.length);
+
+ Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1);
+ int uid = 0; // sysui on primary user
+
+ mService.mNotificationDelegate.grantInlineReplyUriPermission(
+ nr.getKey(), uri, nr.sbn.getUser(), nr.sbn.getPackageName(), nr.sbn.getUid());
+
+ // Grant permission still called if no NotificationRecord exists for the given key
+ verify(mUgm, times(1)).grantUriPermissionFromOwner(any(),
+ eq(nr.sbn.getUid()), eq(nr.sbn.getPackageName()), eq(uri), anyInt(), anyInt(),
+ eq(nr.sbn.getUserId()));
+ }
+
+ @Test
public void testGrantInlineReplyUriPermission_userAll() throws Exception {
// generate a NotificationRecord for USER_ALL to make sure it's converted into USER_SYSTEM
NotificationRecord nr =
@@ -5504,7 +5525,7 @@
Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1);
mService.mNotificationDelegate.grantInlineReplyUriPermission(
- nr.getKey(), uri, nr.sbn.getUid());
+ nr.getKey(), uri, nr.sbn.getUser(), nr.sbn.getPackageName(), nr.sbn.getUid());
// Target user for the grant is USER_ALL instead of USER_SYSTEM
verify(mUgm, times(1)).grantUriPermissionFromOwner(any(),
@@ -5531,7 +5552,7 @@
Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1);
int uid = 0; // sysui on primary user
- int otherUserUid = (otherUserId * 100000) + 1; // SystemUI as a different user
+ int otherUserUid = (otherUserId * 100000) + 1; // sysui as a different user
String sysuiPackage = "sysui";
final String[] sysuiPackages = new String[] { sysuiPackage };
when(mPackageManager.getPackagesForUid(uid)).thenReturn(sysuiPackages);
@@ -5541,7 +5562,8 @@
when(mPackageManager.getPackageUid(sysuiPackage, 0, otherUserId))
.thenReturn(otherUserUid);
- mService.mNotificationDelegate.grantInlineReplyUriPermission(nr.getKey(), uri, uid);
+ mService.mNotificationDelegate.grantInlineReplyUriPermission(
+ nr.getKey(), uri, nr.sbn.getUser(), nr.sbn.getPackageName(), uid);
// Target user for the grant is USER_ALL instead of USER_SYSTEM
verify(mUgm, times(1)).grantUriPermissionFromOwner(any(),
@@ -5550,22 +5572,64 @@
}
@Test
- public void testGrantInlineReplyUriPermission_noRecordExists() throws Exception {
- NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
- waitForIdle();
+ public void testClearInlineReplyUriPermission_uriRecordExists() throws Exception {
+ NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, 0);
+ reset(mPackageManager);
- // No notifications exist for the given record
- StatusBarNotification[] notifsBefore = mBinderService.getActiveNotifications(PKG);
- assertEquals(0, notifsBefore.length);
+ Uri uri1 = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1);
+ Uri uri2 = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 2);
- Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1);
- int uid = 0; // sysui on primary user
+ // create an inline record with two uris in it
+ mService.mNotificationDelegate.grantInlineReplyUriPermission(
+ nr.getKey(), uri1, nr.sbn.getUser(), nr.sbn.getPackageName(), nr.sbn.getUid());
+ mService.mNotificationDelegate.grantInlineReplyUriPermission(
+ nr.getKey(), uri2, nr.sbn.getUser(), nr.sbn.getPackageName(), nr.sbn.getUid());
- mService.mNotificationDelegate.grantInlineReplyUriPermission(nr.getKey(), uri, uid);
+ InlineReplyUriRecord record = mService.mInlineReplyRecordsByKey.get(nr.getKey());
+ assertNotNull(record); // record exists
+ assertEquals(record.getUris().size(), 2); // record has two uris in it
- // Grant permission not called if no record exists for the given key
- verify(mUgm, times(0)).grantUriPermissionFromOwner(any(), anyInt(), any(),
- eq(uri), anyInt(), anyInt(), anyInt());
+ mService.mNotificationDelegate.clearInlineReplyUriPermissions(nr.getKey(), nr.sbn.getUid());
+
+ // permissionOwner destroyed
+ verify(mUgmInternal, times(1)).revokeUriPermissionFromOwner(
+ eq(record.getPermissionOwner()), eq(null), eq(~0), eq(nr.getUserId()));
+ }
+
+
+ @Test
+ public void testClearInlineReplyUriPermission_noUriRecordExists() throws Exception {
+ NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel, 0);
+ reset(mPackageManager);
+
+ mService.mNotificationDelegate.clearInlineReplyUriPermissions(nr.getKey(), nr.sbn.getUid());
+
+ // no permissionOwner destroyed
+ verify(mUgmInternal, times(0)).revokeUriPermissionFromOwner(
+ any(), eq(null), eq(~0), eq(nr.getUserId()));
+ }
+
+ @Test
+ public void testClearInlineReplyUriPermission_userAll() throws Exception {
+ NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
+ UserHandle.USER_ALL);
+ reset(mPackageManager);
+
+ Uri uri1 = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1);
+ Uri uri2 = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 2);
+
+ // create an inline record a uri in it
+ mService.mNotificationDelegate.grantInlineReplyUriPermission(
+ nr.getKey(), uri1, nr.sbn.getUser(), nr.sbn.getPackageName(), nr.sbn.getUid());
+
+ InlineReplyUriRecord record = mService.mInlineReplyRecordsByKey.get(nr.getKey());
+ assertNotNull(record); // record exists
+
+ mService.mNotificationDelegate.clearInlineReplyUriPermissions(nr.getKey(), nr.sbn.getUid());
+
+ // permissionOwner destroyed for USER_SYSTEM, not USER_ALL
+ verify(mUgmInternal, times(1)).revokeUriPermissionFromOwner(
+ eq(record.getPermissionOwner()), eq(null), eq(~0), eq(USER_SYSTEM));
}
@Test