diff options
| author | 2019-11-04 20:31:06 +0000 | |
|---|---|---|
| committer | 2019-11-04 20:31:06 +0000 | |
| commit | 2b041061e38df1afedfd72d28eae896bd3c833b0 (patch) | |
| tree | 4c88d80cc42e89f9c24298448285ee815df632d4 | |
| parent | a339967462c0c77bd0a7e83280b11138f114b3ab (diff) | |
| parent | 979c9766dfc4f9eeb2cfd7bc445623e520087a07 (diff) | |
Merge "Track and destroy inline URI grants separately from Notification URIs."
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 3acbc3a00d57..706727bf050e 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; @@ -290,7 +291,8 @@ public abstract class CarSystemUIBinder { DozeScrimController dozeScrimController, CommandQueue commandQueue, PluginManager pluginManager, - CarNavigationBarController carNavigationBarController) { + CarNavigationBarController carNavigationBarController, + RemoteInputUriController remoteInputUriController) { return new CarStatusBar( context, featureFlags, @@ -357,6 +359,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 4f429d3fd9c2..3b0c9aebfe08 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.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; @@ -287,7 +288,8 @@ public abstract class SystemUIBinder { PowerManager powerManager, DozeScrimController dozeScrimController, CommandQueue commandQueue, - PluginManager pluginManager) { + PluginManager pluginManager, + RemoteInputUriController remoteInputUriController) { return new StatusBar( context, featureFlags, @@ -354,7 +356,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 8f13741f9fc8..4148a73e579c 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"); @@ -1296,6 +1299,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 |