diff options
| author | 2019-10-24 16:09:45 -0400 | |
|---|---|---|
| committer | 2019-11-04 12:56:41 -0500 | |
| commit | 979c9766dfc4f9eeb2cfd7bc445623e520087a07 (patch) | |
| tree | 944e5218d77c9aed2dd0a4aa6b3dc6df0c8303a8 | |
| parent | 3e900e92c5548044f81468f8d28af8c0f6f78741 (diff) | |
Track and destroy inline URI grants separately from Notification URIs.
Bug: 142492493
Bug: 137398133
Test: Unit tests pass. Manual testing with an app configured to receive images via RemoteInput can access and display image URIs after the notification has been canceled, but before the notification entry has been removed from the shade.
Change-Id: If64ae7eff7293df3317a1f629977aaaccd866b32
17 files changed, 408 insertions, 85 deletions
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 499a4d2fb949..d703b86d6c8d 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.net.Uri; 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 @@ interface IStatusBarService 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 b2859f6e719e..412312e5af25 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.DeviceProvisionedController; 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; @@ -277,7 +278,8 @@ public abstract class CarSystemUIBinder { DozeScrimController dozeScrimController, CommandQueue commandQueue, PluginManager pluginManager, - CarNavigationBarController carNavigationBarController) { + CarNavigationBarController carNavigationBarController, + RemoteInputUriController remoteInputUriController) { return new CarStatusBar( context, featureFlags, @@ -344,6 +346,7 @@ public abstract class CarSystemUIBinder { 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 7ab2036251db..110b32b57878 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.DeviceProvisionedController; 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 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt DozeScrimController dozeScrimController, CommandQueue commandQueue, PluginManager pluginManager, - + RemoteInputUriController remoteInputUriController, /* Car Settings injected components. */ CarNavigationBarController carNavigationBarController) { super( @@ -370,7 +371,8 @@ public class CarStatusBar extends StatusBar implements CarBatteryController.Batt 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 88ec76c7706b..4a9301cf5cfc 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIBinder.java @@ -95,6 +95,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController; 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; @@ -273,7 +274,8 @@ public abstract class SystemUIBinder { PowerManager powerManager, DozeScrimController dozeScrimController, CommandQueue commandQueue, - PluginManager pluginManager) { + PluginManager pluginManager, + RemoteInputUriController remoteInputUriController) { return new StatusBar( context, featureFlags, @@ -340,7 +342,8 @@ public abstract class SystemUIBinder { 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 c838ac5315a7..35f06f9d95c8 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.collection.NotificationEntry. 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 @@ public class NotificationRemoteInputManager implements Dumpable { 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 @@ public class NotificationRemoteInputManager implements Dumpable { 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 @@ public class NotificationRemoteInputManager implements Dumpable { addLifetimeExtenders(); mKeyguardManager = context.getSystemService(KeyguardManager.class); mStatusBarStateController = statusBarStateController; + mRemoteInputUriController = remoteInputUriController; notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() { @Override @@ -300,7 +304,7 @@ public class NotificationRemoteInputManager implements Dumpable { /** 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 998cf523d3df..778443c4bddf 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 @@ package com.android.systemui.statusbar; 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 @@ public class RemoteInputController { 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 @@ public class RemoteInputController { 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 2125d126752a..51a51a5d406f 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.KeyguardUserSwitcher; 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 @@ public class StatusBar extends SystemUI implements DemoMode, 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 @@ public class StatusBar extends SystemUI implements DemoMode, PowerManager powerManager, DozeScrimController dozeScrimController, CommandQueue commandQueue, - PluginManager pluginManager) { + PluginManager pluginManager, + RemoteInputUriController remoteInputUriController) { super(context); mFeatureFlags = featureFlags; mLightBarController = lightBarController; @@ -770,7 +773,7 @@ public class StatusBar extends SystemUI implements DemoMode, mBiometricUnlockControllerLazy = biometricUnlockControllerLazy; mCommandQueue = commandQueue; mPluginManager = pluginManager; - + mRemoteInputUriController = remoteInputUriController; mBubbleExpandListener = (isExpanding, key) -> { mEntryManager.updateNotifications("onBubbleExpandChanged"); @@ -1295,6 +1298,8 @@ public class StatusBar extends SystemUI implements DemoMode, 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 000000000000..4d912deb7d41 --- /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 502a9bd34b12..307e3bcbaba5 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 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene 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 2514c9382e44..a754a00df940 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.NotificationEntryManager; 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 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { @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 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { 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 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase { 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 88fb3e1d5c19..95ce53c58e95 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.plugins.statusbar.StatusBarStateController; 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 @@ public class SmartReplyControllerTest extends SysuiTestCase { @Mock private NotificationEntryManager mNotificationEntryManager; @Mock private IStatusBarService mIStatusBarService; @Mock private StatusBarStateController mStatusBarStateController; + @Mock private RemoteInputUriController mRemoteInputUriController; @Before public void setUp() { @@ -88,7 +90,8 @@ public class SmartReplyControllerTest extends SysuiTestCase { 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 5d152054fdd3..c21e3ab079ff 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.DeviceProvisionedController; 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 @@ public class StatusBarTest extends SysuiTestCase { @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 @@ public class StatusBarTest extends SysuiTestCase { 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 000000000000..76cfb032aaf4 --- /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 6f0ad33244e4..88fc072e2481 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 @@ package com.android.server.notification; 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 @@ public interface NotificationDelegate { * 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 0fc1718ad673..5e764012f46d 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 @@ public class NotificationManagerService extends SystemService { @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 @@ public class NotificationManagerService extends SystemService { * 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 @@ public class NotificationManagerService extends SystemService { // 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 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { } } - 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 @@ public class NotificationManagerService extends SystemService { 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 489c34359645..effeb80298b8 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 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @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 cd0f4f185927..1ee71fbd3831 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @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()); + + InlineReplyUriRecord record = mService.mInlineReplyRecordsByKey.get(nr.getKey()); + assertNotNull(record); // record exists + assertEquals(record.getUris().size(), 2); // record has two uris in it + + 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.grantInlineReplyUriPermission(nr.getKey(), uri, uid); + mService.mNotificationDelegate.clearInlineReplyUriPermissions(nr.getKey(), nr.sbn.getUid()); - // 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()); + // permissionOwner destroyed for USER_SYSTEM, not USER_ALL + verify(mUgmInternal, times(1)).revokeUriPermissionFromOwner( + eq(record.getPermissionOwner()), eq(null), eq(~0), eq(USER_SYSTEM)); } @Test |