diff options
| author | 2019-01-29 13:08:30 -0500 | |
|---|---|---|
| committer | 2019-01-29 16:31:12 -0500 | |
| commit | d942566395d21ada4220347bc6c923f5bdc3428e (patch) | |
| tree | 6af3f8fe25608d183eccf6c28574a2dc52d9c26b | |
| parent | 2444f5a5104e8bad7712db0a1087ee0aab93b957 (diff) | |
API for querying/revoking current NotificationAssistant
Additionally, NotificationAssistants guarantees that only one
NotificationAssistantService can be allowed per user at a time.
Test: added atest
Bug: 120852765
Change-Id: I19d2940e6e198a166963a2dbc05dbe8d9b8d084e
6 files changed, 233 insertions, 6 deletions
diff --git a/api/system-current.txt b/api/system-current.txt index f203dbb8c3bc..b97abd5884da 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -499,6 +499,13 @@ package android.app { method public org.json.JSONObject toJson() throws org.json.JSONException; } + public class NotificationManager { + method @Nullable public android.content.ComponentName getAllowedNotificationAssistant(); + method @Nullable public android.content.ComponentName getAllowedNotificationAssistantForUser(android.os.UserHandle); + method public void setNotificationAssistantAccessGranted(android.content.ComponentName, boolean); + method public void setNotificationAssistantAccessGrantedForUser(android.content.ComponentName, android.os.UserHandle, boolean); + } + public final class StatsManager { method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void addConfig(long, byte[]) throws android.app.StatsManager.StatsUnavailableException; method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean addConfiguration(long, byte[]); diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 199c1338c50c..250e44621c49 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -149,6 +149,8 @@ interface INotificationManager void setNotificationAssistantAccessGrantedForUser(in ComponentName assistant, int userId, boolean enabled); List<String> getEnabledNotificationListenerPackages(); List<ComponentName> getEnabledNotificationListeners(int userId); + ComponentName getAllowedNotificationAssistantForUser(int userId); + ComponentName getAllowedNotificationAssistant(); int getZenMode(); ZenModeConfig getZenModeConfig(); diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index c4b4b4070ce7..1334906c44bb 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -20,6 +20,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; +import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.annotation.UnsupportedAppUsage; @@ -1137,6 +1138,19 @@ public class NotificationManager { } } + /** + * Checks whether the user has approved a given + * {@link android.service.notification.NotificationAssistantService}. + * + * <p> + * The assistant service must belong to the calling app. + * + * <p> + * Apps can request notification assistant access by sending the user to the activity that + * matches the system intent action + * TODO: STOPSHIP: Add correct intent + * {@link android.provider.Settings#ACTION_MANAGE_DEFAULT_APPS_SETTINGS}. + */ public boolean isNotificationAssistantAccessGranted(ComponentName assistant) { INotificationManager service = getService(); try { @@ -1232,6 +1246,45 @@ public class NotificationManager { } } + /** + * Grants/revokes Notification Assistant access to {@code assistant} for current user. + * + * @param assistant Name of component to grant/revoke access or {@code null} to revoke access to + * current assistant + * @param granted Grant/revoke access + * @hide + */ + @SystemApi + public void setNotificationAssistantAccessGranted(ComponentName assistant, boolean granted) { + INotificationManager service = getService(); + try { + service.setNotificationAssistantAccessGranted(assistant, granted); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Grants/revokes Notification Assistant access to {@code assistant} for given user. + * + * @param assistant Name of component to grant/revoke access or {@code null} to revoke access to + * current assistant + * @param user handle to associate assistant with + * @param granted Grant/revoke access + * @hide + */ + @SystemApi + public void setNotificationAssistantAccessGrantedForUser(ComponentName assistant, + UserHandle user, boolean granted) { + INotificationManager service = getService(); + try { + service.setNotificationAssistantAccessGrantedForUser(assistant, user.getIdentifier(), + granted); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** @hide */ public List<ComponentName> getEnabledNotificationListeners(int userId) { INotificationManager service = getService(); @@ -1242,6 +1295,29 @@ public class NotificationManager { } } + /** @hide */ + @SystemApi + public @Nullable ComponentName getAllowedNotificationAssistantForUser(UserHandle user) { + INotificationManager service = getService(); + try { + return service.getAllowedNotificationAssistantForUser(user.getIdentifier()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** @hide */ + @SystemApi + public @Nullable ComponentName getAllowedNotificationAssistant() { + INotificationManager service = getService(); + try { + return service.getAllowedNotificationAssistant(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private Context mContext; private static void checkRequired(String name, Object value) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index de3f50ad8c2c..dee66e02430f 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -197,6 +197,7 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.os.SomeArgs; import com.android.internal.statusbar.NotificationVisibility; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.CollectionUtils; import com.android.internal.util.DumpUtils; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.Preconditions; @@ -3618,6 +3619,22 @@ public class NotificationManagerService extends SystemService { } @Override + public ComponentName getAllowedNotificationAssistantForUser(int userId) { + checkCallerIsSystem(); + List<ComponentName> allowedComponents = mAssistants.getAllowedComponents(userId); + if (allowedComponents.size() > 1) { + throw new IllegalStateException( + "At most one NotificationAssistant: " + allowedComponents.size()); + } + return CollectionUtils.firstOrNull(allowedComponents); + } + + @Override + public ComponentName getAllowedNotificationAssistant() { + return getAllowedNotificationAssistantForUser(getCallingUserHandle().getIdentifier()); + } + + @Override public boolean isNotificationListenerAccessGranted(ComponentName listener) { Preconditions.checkNotNull(listener); checkCallerIsSystemOrSameApp(listener.getPackageName()); @@ -3685,8 +3702,15 @@ public class NotificationManagerService extends SystemService { @Override public void setNotificationAssistantAccessGrantedForUser(ComponentName assistant, int userId, boolean granted) throws RemoteException { - Preconditions.checkNotNull(assistant); checkCallerIsSystemOrShell(); + if (assistant == null) { + ComponentName allowedAssistant = CollectionUtils.firstOrNull( + mAssistants.getAllowedComponents(userId)); + if (allowedAssistant != null) { + setNotificationAssistantAccessGrantedForUser(allowedAssistant, userId, false); + } + return; + } final long identity = Binder.clearCallingIdentity(); try { if (mAllowedManagedServicePackages.test(assistant.getPackageName())) { @@ -7209,6 +7233,26 @@ public class NotificationManagerService extends SystemService { } } } + + @Override + protected void setPackageOrComponentEnabled(String pkgOrComponent, int userId, + boolean isPrimary, boolean enabled) { + // Ensures that only one component is enabled at a time + if (enabled) { + List<ComponentName> allowedComponents = getAllowedComponents(userId); + if (!allowedComponents.isEmpty()) { + ComponentName currentComponent = CollectionUtils.firstOrNull(allowedComponents); + if (currentComponent.flattenToString().equals(pkgOrComponent)) return; + try { + getBinderService().setNotificationAssistantAccessGrantedForUser( + currentComponent, userId, false); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + } + super.setPackageOrComponentEnabled(pkgOrComponent, userId, isPrimary, enabled); + } } public class NotificationListeners extends ManagedServices { diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java index 1de1e4ef8b9f..0b488c09b45a 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java @@ -25,6 +25,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.INotificationManager; import android.content.ComponentName; import android.content.Context; import android.content.pm.IPackageManager; @@ -36,22 +37,17 @@ import android.os.UserManager; import android.util.IntArray; import android.util.Xml; -import com.android.internal.util.FastXmlSerializer; import com.android.server.UiServiceTestCase; import com.android.server.notification.NotificationManagerService.NotificationAssistants; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlSerializer; import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.List; @@ -65,6 +61,8 @@ public class NotificationAssistantsTest extends UiServiceTestCase { private UserManager mUm; @Mock NotificationManagerService mNm; + @Mock + private INotificationManager mINm; NotificationAssistants mAssistants; @@ -83,6 +81,7 @@ public class NotificationAssistantsTest extends UiServiceTestCase { getContext().setMockPackageManager(mPm); getContext().addMockSystemService(Context.USER_SERVICE, mUm); mAssistants = spy(mNm.new NotificationAssistants(getContext(), mLock, mUserProfiles, miPm)); + when(mNm.getBinderService()).thenReturn(mINm); List<ResolveInfo> approved = new ArrayList<>(); ResolveInfo resolve = new ResolveInfo(); @@ -136,4 +135,30 @@ public class NotificationAssistantsTest extends UiServiceTestCase { verify(mAssistants, times(1)).addApprovedList( new ComponentName("b", "b").flattenToString(),10, true); } + + @Test + public void testSetPackageOrComponentEnabled_onlyOnePackage() throws Exception { + ComponentName component1 = ComponentName.unflattenFromString("package/Component1"); + ComponentName component2 = ComponentName.unflattenFromString("package/Component2"); + mAssistants.setPackageOrComponentEnabled(component1.flattenToString(), mZero.id, true, + true); + verify(mINm, never()).setNotificationAssistantAccessGrantedForUser(any(ComponentName.class), + eq(mZero.id), anyBoolean()); + + mAssistants.setPackageOrComponentEnabled(component2.flattenToString(), mZero.id, true, + true); + verify(mINm, times(1)).setNotificationAssistantAccessGrantedForUser(component1, mZero.id, + false); + } + + @Test + public void testSetPackageOrComponentEnabled_samePackage() throws Exception { + ComponentName component1 = ComponentName.unflattenFromString("package/Component1"); + mAssistants.setPackageOrComponentEnabled(component1.flattenToString(), mZero.id, true, + true); + mAssistants.setPackageOrComponentEnabled(component1.flattenToString(), mZero.id, true, + true); + verify(mINm, never()).setNotificationAssistantAccessGrantedForUser(any(ComponentName.class), + eq(mZero.id), anyBoolean()); + } } 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 9c6ab0ab9aa9..e5f119fa6b8a 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -2029,6 +2029,31 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testGetAssistantAllowedForUser() throws Exception { + UserHandle user = UserHandle.of(10); + try { + mBinderService.getAllowedNotificationAssistantForUser(user.getIdentifier()); + } catch (IllegalStateException e) { + if (!e.getMessage().contains("At most one NotificationAssistant")) { + throw e; + } + } + verify(mAssistants, times(1)).getAllowedComponents(user.getIdentifier()); + } + + @Test + public void testGetAssistantAllowed() throws Exception { + try { + mBinderService.getAllowedNotificationAssistant(); + } catch (IllegalStateException e) { + if (!e.getMessage().contains("At most one NotificationAssistant")) { + throw e; + } + } + verify(mAssistants, times(1)).getAllowedComponents(0); + } + + @Test public void testSetDndAccessForUser() throws Exception { UserHandle user = UserHandle.of(10); ComponentName c = ComponentName.unflattenFromString("package/Component"); @@ -2089,6 +2114,54 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testSetAssistantAccess_nullWithAllowedAssistant() throws Exception { + ArrayList<ComponentName> componentList = new ArrayList<>(); + ComponentName c = ComponentName.unflattenFromString("package/Component"); + componentList.add(c); + when(mAssistants.getAllowedComponents(anyInt())).thenReturn(componentList); + + try { + mBinderService.setNotificationAssistantAccessGranted(null, true); + } catch (SecurityException e) { + if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { + throw e; + } + } + + verify(mAssistants, times(1)).setPackageOrComponentEnabled( + c.flattenToString(), 0, true, false); + verify(mConditionProviders, times(1)).setPackageOrComponentEnabled( + c.flattenToString(), 0, false, false); + verify(mListeners, never()).setPackageOrComponentEnabled( + any(), anyInt(), anyBoolean(), anyBoolean()); + } + + @Test + public void testSetAssistantAccessForUser_nullWithAllowedAssistant() throws Exception { + UserHandle user = UserHandle.of(10); + ArrayList<ComponentName> componentList = new ArrayList<>(); + ComponentName c = ComponentName.unflattenFromString("package/Component"); + componentList.add(c); + when(mAssistants.getAllowedComponents(anyInt())).thenReturn(componentList); + + try { + mBinderService.setNotificationAssistantAccessGrantedForUser( + null, user.getIdentifier(), true); + } catch (SecurityException e) { + if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) { + throw e; + } + } + + verify(mAssistants, times(1)).setPackageOrComponentEnabled( + c.flattenToString(), user.getIdentifier(), true, false); + verify(mConditionProviders, times(1)).setPackageOrComponentEnabled( + c.flattenToString(), user.getIdentifier(), false, false); + verify(mListeners, never()).setPackageOrComponentEnabled( + any(), anyInt(), anyBoolean(), anyBoolean()); + } + + @Test public void testSetDndAccess() throws Exception { ComponentName c = ComponentName.unflattenFromString("package/Component"); try { |