Merge "Add managed profile whitelist to control NotificationListenerServices" into oc-dev
diff --git a/api/current.txt b/api/current.txt
index 206eeff..bb083f1 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6328,6 +6328,7 @@
method public int getPermissionGrantState(android.content.ComponentName, java.lang.String, java.lang.String);
method public int getPermissionPolicy(android.content.ComponentName);
method public java.util.List<java.lang.String> getPermittedAccessibilityServices(android.content.ComponentName);
+ method public java.util.List<java.lang.String> getPermittedCrossProfileNotificationListeners(android.content.ComponentName);
method public java.util.List<java.lang.String> getPermittedInputMethods(android.content.ComponentName);
method public long getRequiredStrongAuthTimeout(android.content.ComponentName);
method public boolean getScreenCaptureDisabled(android.content.ComponentName);
@@ -6412,6 +6413,7 @@
method public boolean setPermissionGrantState(android.content.ComponentName, java.lang.String, java.lang.String, int);
method public void setPermissionPolicy(android.content.ComponentName, int);
method public boolean setPermittedAccessibilityServices(android.content.ComponentName, java.util.List<java.lang.String>);
+ method public boolean setPermittedCrossProfileNotificationListeners(android.content.ComponentName, java.util.List<java.lang.String>);
method public boolean setPermittedInputMethods(android.content.ComponentName, java.util.List<java.lang.String>);
method public void setProfileEnabled(android.content.ComponentName);
method public void setProfileName(android.content.ComponentName, java.lang.String);
diff --git a/api/system-current.txt b/api/system-current.txt
index 22c8cd3..5bb85be 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6550,6 +6550,7 @@
method public int getPermissionPolicy(android.content.ComponentName);
method public java.util.List<java.lang.String> getPermittedAccessibilityServices(android.content.ComponentName);
method public java.util.List<java.lang.String> getPermittedAccessibilityServices(int);
+ method public java.util.List<java.lang.String> getPermittedCrossProfileNotificationListeners(android.content.ComponentName);
method public java.util.List<java.lang.String> getPermittedInputMethods(android.content.ComponentName);
method public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser();
method public android.content.ComponentName getProfileOwner() throws java.lang.IllegalArgumentException;
@@ -6646,6 +6647,7 @@
method public boolean setPermissionGrantState(android.content.ComponentName, java.lang.String, java.lang.String, int);
method public void setPermissionPolicy(android.content.ComponentName, int);
method public boolean setPermittedAccessibilityServices(android.content.ComponentName, java.util.List<java.lang.String>);
+ method public boolean setPermittedCrossProfileNotificationListeners(android.content.ComponentName, java.util.List<java.lang.String>);
method public boolean setPermittedInputMethods(android.content.ComponentName, java.util.List<java.lang.String>);
method public void setProfileEnabled(android.content.ComponentName);
method public void setProfileName(android.content.ComponentName, java.lang.String);
diff --git a/api/test-current.txt b/api/test-current.txt
index 98ee73f..c39e490 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6353,6 +6353,7 @@
method public int getPermissionGrantState(android.content.ComponentName, java.lang.String, java.lang.String);
method public int getPermissionPolicy(android.content.ComponentName);
method public java.util.List<java.lang.String> getPermittedAccessibilityServices(android.content.ComponentName);
+ method public java.util.List<java.lang.String> getPermittedCrossProfileNotificationListeners(android.content.ComponentName);
method public java.util.List<java.lang.String> getPermittedInputMethods(android.content.ComponentName);
method public long getRequiredStrongAuthTimeout(android.content.ComponentName);
method public boolean getScreenCaptureDisabled(android.content.ComponentName);
@@ -6439,6 +6440,7 @@
method public boolean setPermissionGrantState(android.content.ComponentName, java.lang.String, java.lang.String, int);
method public void setPermissionPolicy(android.content.ComponentName, int);
method public boolean setPermittedAccessibilityServices(android.content.ComponentName, java.util.List<java.lang.String>);
+ method public boolean setPermittedCrossProfileNotificationListeners(android.content.ComponentName, java.util.List<java.lang.String>);
method public boolean setPermittedInputMethods(android.content.ComponentName, java.util.List<java.lang.String>);
method public void setProfileEnabled(android.content.ComponentName);
method public void setProfileName(android.content.ComponentName, java.lang.String);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 8fcabce..9b1b6cd 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2756,8 +2756,8 @@
/**
* Called by a profile or device owner to provision a token which can later be used to reset the
- * device lockscreen password (if called by device owner), or work challenge (if called by
- * profile owner), via {@link #resetPasswordWithToken}.
+ * device lockscreen password (if called by device owner), or managed profile challenge (if
+ * called by profile owner), via {@link #resetPasswordWithToken}.
* <p>
* If the user currently has a lockscreen password, the provisioned token will not be
* immediately usable; it only becomes active after the user performs a confirm credential
@@ -2835,8 +2835,8 @@
}
/**
- * Called by device or profile owner to force set a new device unlock password or a work profile
- * challenge on current user. This takes effect immediately.
+ * Called by device or profile owner to force set a new device unlock password or a managed
+ * profile challenge on current user. This takes effect immediately.
* <p>
* Unlike {@link #resetPassword}, this API can change the password even before the user or
* device is unlocked or decrypted. The supplied token must have been previously provisioned via
@@ -5562,7 +5562,7 @@
* Calling with a null value for the list disables the restriction so that all services can be
* used, calling with an empty list only allows the builtin system's services.
* <p>
- * System accesibility services are always available to the user the list can't modify this.
+ * System accessibility services are always available to the user the list can't modify this.
*
* @param admin Which {@link DeviceAdminReceiver} this request is associated with.
* @param packageNames List of accessibility service package names.
@@ -5672,7 +5672,8 @@
* non-system input methods currently enabled that are not in the packageNames list.
* @throws SecurityException if {@code admin} is not a device or profile owner.
*/
- public boolean setPermittedInputMethods(@NonNull ComponentName admin, List<String> packageNames) {
+ public boolean setPermittedInputMethods(
+ @NonNull ComponentName admin, List<String> packageNames) {
throwIfParentInstance("setPermittedInputMethods");
if (mService != null) {
try {
@@ -5754,6 +5755,85 @@
}
/**
+ * Called by a profile owner of a managed profile to set the packages that are allowed to use
+ * a {@link android.service.notification.NotificationListenerService} in the primary user to
+ * see notifications from the managed profile. By default all packages are permitted by this
+ * policy. When zero or more packages have been added, notification listeners installed on the
+ * primary user that are not in the list and are not part of the system won't receive events
+ * for managed profile notifications.
+ * <p>
+ * Calling with a {@code null} value for the list disables the restriction so that all
+ * notification listener services be used. Calling with an empty list disables all but the
+ * system's own notification listeners. System notification listener services are always
+ * available to the user.
+ * <p>
+ * If a device or profile owner want to stop notification listeners in their user from seeing
+ * that user's notifications they should prevent that service from running instead (e.g. via
+ * {@link #setApplicationHidden(ComponentName, String, boolean)})
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param packageList List of package names to whitelist
+ * @return true if setting the restriction succeeded. It will fail if called outside a managed
+ * profile
+ * @throws SecurityException if {@code admin} is not a profile owner.
+ *
+ * @see android.service.notification.NotificationListenerService
+ */
+ public boolean setPermittedCrossProfileNotificationListeners(
+ @NonNull ComponentName admin, @Nullable List<String> packageList) {
+ throwIfParentInstance("setPermittedCrossProfileNotificationListeners");
+ if (mService != null) {
+ try {
+ return mService.setPermittedCrossProfileNotificationListeners(admin, packageList);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the list of packages installed on the primary user that allowed to use a
+ * {@link android.service.notification.NotificationListenerService} to receive
+ * notifications from this managed profile, as set by the profile owner.
+ * <p>
+ * An empty list means no notification listener services except system ones are allowed.
+ * A {@code null} return value indicates that all notification listeners are allowed.
+ */
+ public @Nullable List<String> getPermittedCrossProfileNotificationListeners(
+ @NonNull ComponentName admin) {
+ throwIfParentInstance("getPermittedCrossProfileNotificationListeners");
+ if (mService != null) {
+ try {
+ return mService.getPermittedCrossProfileNotificationListeners(admin);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if {@code NotificationListenerServices} from the given package are allowed to
+ * receive events for notifications from the given user id. Can only be called by the system uid
+ *
+ * @see #setPermittedCrossProfileNotificationListeners(ComponentName, List)
+ *
+ * @hide
+ */
+ public boolean isNotificationListenerServicePermitted(
+ @NonNull String packageName, @UserIdInt int userId) {
+ if (mService != null) {
+ try {
+ return mService.isNotificationListenerServicePermitted(packageName, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return true;
+ }
+
+ /**
* Get the list of apps to keep around as APKs even if no user has currently installed it. This
* function can be called by a device owner or by a delegate given the
* {@link #DELEGATION_KEEP_UNINSTALLED_PACKAGES} scope via {@link #setDelegatedScopes}.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 8ea911f..e361d81 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -202,6 +202,10 @@
List getPermittedInputMethodsForCurrentUser();
boolean isInputMethodPermittedByAdmin(in ComponentName admin, String packageName, int userId);
+ boolean setPermittedCrossProfileNotificationListeners(in ComponentName admin, in List<String> packageList);
+ List<String> getPermittedCrossProfileNotificationListeners(in ComponentName admin);
+ boolean isNotificationListenerServicePermitted(in String packageName, int userId);
+
Intent createAdminSupportIntent(in String restriction);
boolean setApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName, boolean hidden);
boolean isApplicationHidden(in ComponentName admin, in String callerPackage, in String packageName);
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 3cb2f35..b8d633f 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -19,10 +19,12 @@
import static android.content.Context.BIND_ALLOW_WHITELIST_MANAGEMENT;
import static android.content.Context.BIND_AUTO_CREATE;
import static android.content.Context.BIND_FOREGROUND_SERVICE;
+import static android.content.Context.DEVICE_POLICY_SERVICE;
import android.annotation.NonNull;
import android.app.ActivityManager;
import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -39,6 +41,7 @@
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.net.Uri;
+import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
@@ -895,7 +898,9 @@
if (this.userid == UserHandle.USER_ALL) return true;
if (this.isSystem) return true;
if (nid == UserHandle.USER_ALL || nid == this.userid) return true;
- return supportsProfiles() && mUserProfiles.isCurrentProfile(nid);
+ return supportsProfiles()
+ && mUserProfiles.isCurrentProfile(nid)
+ && isPermittedForProfile(nid);
}
public boolean supportsProfiles() {
@@ -906,7 +911,7 @@
public void binderDied() {
if (DEBUG) Slog.d(TAG, "binderDied");
// Remove the service, but don't unbind from the service. The system will bring the
- // service back up, and the onServiceConnected handler will readd the service with the
+ // service back up, and the onServiceConnected handler will read the service with the
// new binding. If this isn't a bound service, and is just a registered
// service, just removing it from the list is all we need to do anyway.
removeServiceImpl(this.service, this.userid);
@@ -918,6 +923,26 @@
if (this.connection == null) return false;
return mEnabledServicesForCurrentProfiles.contains(this.component);
}
+
+ /**
+ * Returns true if this service is allowed to receive events for the given userId. A
+ * managed profile owner can disallow non-system services running outside of the profile
+ * from receiving events from the profile.
+ */
+ public boolean isPermittedForProfile(int userId) {
+ if (!mUserProfiles.isManagedProfile(userId)) {
+ return true;
+ }
+ DevicePolicyManager dpm =
+ (DevicePolicyManager) mContext.getSystemService(DEVICE_POLICY_SERVICE);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return dpm.isNotificationListenerServicePermitted(
+ component.getPackageName(), userId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
}
/** convenience method for looking in mEnabledServicesForCurrentProfiles */
@@ -959,6 +984,13 @@
return mCurrentProfiles.get(userId) != null;
}
}
+
+ public boolean isManagedProfile(int userId) {
+ synchronized (mCurrentProfiles) {
+ UserInfo user = mCurrentProfiles.get(userId);
+ return user != null && user.isManagedProfile();
+ }
+ }
}
public static class Config {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 6584672..87fb8c8 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -663,6 +663,8 @@
private static final String TAG_GLOBAL_PROXY_SPEC = "global-proxy-spec";
private static final String TAG_SPECIFIES_GLOBAL_PROXY = "specifies-global-proxy";
private static final String TAG_PERMITTED_IMES = "permitted-imes";
+ private static final String TAG_PERMITTED_NOTIFICATION_LISTENERS =
+ "permitted-notification-listeners";
private static final String TAG_MAX_FAILED_PASSWORD_WIPE = "max-failed-password-wipe";
private static final String TAG_MAX_TIME_TO_UNLOCK = "max-time-to-unlock";
private static final String TAG_STRONG_AUTH_UNLOCK_TIMEOUT = "strong-auth-unlock-timeout";
@@ -770,6 +772,11 @@
// allowed.
List<String> permittedInputMethods;
+ // The list of packages allowed to use a NotificationListenerService to receive events for
+ // notifications from this user. Null means that all packages are allowed. Empty list means
+ // that only packages from the system are allowed.
+ List<String> permittedNotificationListeners;
+
// List of package names to keep cached.
List<String> keepUninstalledPackages;
@@ -1016,6 +1023,8 @@
writePackageListToXml(out, TAG_PERMITTED_ACCESSIBILITY_SERVICES,
permittedAccessiblityServices);
writePackageListToXml(out, TAG_PERMITTED_IMES, permittedInputMethods);
+ writePackageListToXml(out, TAG_PERMITTED_NOTIFICATION_LISTENERS,
+ permittedNotificationListeners);
writePackageListToXml(out, TAG_KEEP_UNINSTALLED_PACKAGES, keepUninstalledPackages);
if (hasUserRestrictions()) {
UserRestrictionsUtils.writeRestrictions(
@@ -1187,6 +1196,8 @@
permittedAccessiblityServices = readPackageList(parser, tag);
} else if (TAG_PERMITTED_IMES.equals(tag)) {
permittedInputMethods = readPackageList(parser, tag);
+ } else if (TAG_PERMITTED_NOTIFICATION_LISTENERS.equals(tag)) {
+ permittedNotificationListeners = readPackageList(parser, tag);
} else if (TAG_KEEP_UNINSTALLED_PACKAGES.equals(tag)) {
keepUninstalledPackages = readPackageList(parser, tag);
} else if (TAG_USER_RESTRICTIONS.equals(tag)) {
@@ -1407,6 +1418,10 @@
pw.print(prefix); pw.print("permittedInputMethods=");
pw.println(permittedInputMethods);
}
+ if (permittedNotificationListeners != null) {
+ pw.print(prefix); pw.print("permittedNotificationListeners=");
+ pw.println(permittedNotificationListeners);
+ }
if (keepUninstalledPackages != null) {
pw.print(prefix); pw.print("keepUninstalledPackages=");
pw.println(keepUninstalledPackages);
@@ -7807,14 +7822,14 @@
if (admin.permittedAccessiblityServices == null) {
return true;
}
- return checkPackagesInPermittedListOrSystem(Arrays.asList(packageName),
+ return checkPackagesInPermittedListOrSystem(Collections.singletonList(packageName),
admin.permittedAccessiblityServices, userHandle);
}
}
private boolean checkCallerIsCurrentUserOrProfile() {
- int callingUserId = UserHandle.getCallingUserId();
- long token = mInjector.binderClearCallingIdentity();
+ final int callingUserId = UserHandle.getCallingUserId();
+ final long token = mInjector.binderClearCallingIdentity();
try {
UserInfo currentUser;
UserInfo callingUser = getUserInfo(callingUserId);
@@ -7854,6 +7869,7 @@
return false;
}
+ final int callingUserId = mInjector.userHandleGetCallingUserId();
if (packageList != null) {
// InputMethodManager fetches input methods for current user.
// So this can only be set when calling user is the current user
@@ -7868,7 +7884,7 @@
enabledPackages.add(ime.getPackageName());
}
if (!checkPackagesInPermittedListOrSystem(enabledPackages, packageList,
- mInjector.binderGetCallingUserHandle().getIdentifier())) {
+ callingUserId)) {
Slog.e(LOG_TAG, "Cannot set permitted input methods, "
+ "because it contains already enabled input method.");
return false;
@@ -7880,7 +7896,7 @@
ActiveAdmin admin = getActiveAdminForCallerLocked(who,
DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
admin.permittedInputMethods = packageList;
- saveSettingsLocked(UserHandle.getCallingUserId());
+ saveSettingsLocked(callingUserId);
}
return true;
}
@@ -7979,11 +7995,70 @@
if (admin.permittedInputMethods == null) {
return true;
}
- return checkPackagesInPermittedListOrSystem(Arrays.asList(packageName),
+ return checkPackagesInPermittedListOrSystem(Collections.singletonList(packageName),
admin.permittedInputMethods, userHandle);
}
}
+ @Override
+ public boolean setPermittedCrossProfileNotificationListeners(
+ ComponentName who, List<String> packageList) {
+ if (!mHasFeature) {
+ return false;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null");
+
+ final int callingUserId = mInjector.userHandleGetCallingUserId();
+ if (!isManagedProfile(callingUserId)) {
+ return false;
+ }
+
+ synchronized (this) {
+ ActiveAdmin admin = getActiveAdminForCallerLocked(
+ who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ admin.permittedNotificationListeners = packageList;
+ saveSettingsLocked(callingUserId);
+ }
+ return true;
+ }
+
+ @Override
+ public List<String> getPermittedCrossProfileNotificationListeners(ComponentName who) {
+ if (!mHasFeature) {
+ return null;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null");
+
+ synchronized (this) {
+ ActiveAdmin admin = getActiveAdminForCallerLocked(
+ who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ return admin.permittedNotificationListeners;
+ }
+ }
+
+ @Override
+ public boolean isNotificationListenerServicePermitted(String packageName, int userId) {
+ if (!mHasFeature) {
+ return true;
+ }
+
+ Preconditions.checkStringNotEmpty(packageName, "packageName is null or empty");
+ if (!isCallerWithSystemUid()) {
+ throw new SecurityException(
+ "Only the system can query if a notification listener service is permitted");
+ }
+ synchronized (this) {
+ ActiveAdmin profileOwner = getProfileOwnerAdminLocked(userId);
+ if (profileOwner == null || profileOwner.permittedNotificationListeners == null) {
+ return true;
+ }
+ return checkPackagesInPermittedListOrSystem(Collections.singletonList(packageName),
+ profileOwner.permittedNotificationListeners, userId);
+
+ }
+ }
+
+
private void sendAdminEnabledBroadcastLocked(int userHandle) {
DevicePolicyData policyData = getUserData(userHandle);
if (policyData.mAdminBroadcastPending) {
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 971cdf8..c2b0ea5 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -1217,7 +1217,8 @@
}
/**
- * Setup a package in the package manager mock. Useful for faking installed applications.
+ * Setup a package in the package manager mock for {@link DpmMockContext#CALLER_USER_HANDLE}.
+ * Useful for faking installed applications.
*
* @param packageName the name of the package to be setup
* @param appId the application ID to be given to the package
@@ -1225,19 +1226,41 @@
*/
private int setupPackageInPackageManager(final String packageName, final int appId)
throws Exception {
+ return setupPackageInPackageManager(
+ packageName, DpmMockContext.CALLER_USER_HANDLE, appId,
+ ApplicationInfo.FLAG_HAS_CODE);
+ }
+
+ /**
+ * Setup a package in the package manager mock. Useful for faking installed applications.
+ *
+ * @param packageName the name of the package to be setup
+ * @param userId the user id where the package will be "installed"
+ * @param appId the application ID to be given to the package
+ * @param flags flags to set in the ApplicationInfo for this package
+ * @return the UID of the package as known by the mock package manager
+ */
+ private int setupPackageInPackageManager(
+ final String packageName, int userId, final int appId, int flags)
+ throws Exception {
// Make the PackageManager return the package instead of throwing a NameNotFoundException
final PackageInfo pi = new PackageInfo();
pi.applicationInfo = new ApplicationInfo();
- pi.applicationInfo.flags = ApplicationInfo.FLAG_HAS_CODE;
+ pi.applicationInfo.flags = flags;
doReturn(pi).when(mContext.ipackageManager).getPackageInfo(
eq(packageName),
anyInt(),
- eq(DpmMockContext.CALLER_USER_HANDLE));
+ eq(userId));
+ doReturn(pi.applicationInfo).when(mContext.ipackageManager).getApplicationInfo(
+ eq(packageName),
+ anyInt(),
+ eq(userId));
+
// Setup application UID with the PackageManager
- final int uid = UserHandle.getUid(DpmMockContext.CALLER_USER_HANDLE, appId);
+ final int uid = UserHandle.getUid(userId, appId);
doReturn(uid).when(mContext.packageManager).getPackageUidAsUser(
eq(packageName),
- eq(DpmMockContext.CALLER_USER_HANDLE));
+ eq(userId));
// Associate packageName to uid
doReturn(packageName).when(mContext.ipackageManager).getNameForUid(eq(uid));
doReturn(new String[]{packageName})
@@ -3971,6 +3994,210 @@
assertFalse(dpm.isCurrentInputMethodSetByOwner());
}
+ public void testSetPermittedCrossProfileNotificationListeners_unavailableForDo()
+ throws Exception {
+ // Set up a device owner.
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+ assertSetPermittedCrossProfileNotificationListenersUnavailable(mContext.binder.callingUid);
+ }
+
+ public void testSetPermittedCrossProfileNotificationListeners_unavailableForPoOnUser()
+ throws Exception {
+ // Set up a profile owner.
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ setupProfileOwner();
+ assertSetPermittedCrossProfileNotificationListenersUnavailable(mContext.binder.callingUid);
+ }
+
+ private void assertSetPermittedCrossProfileNotificationListenersUnavailable(
+ int adminUid) throws Exception {
+ mContext.binder.callingUid = adminUid;
+ final int userId = UserHandle.getUserId(adminUid);
+
+ final String packageName = "some.package";
+ assertFalse(dpms.setPermittedCrossProfileNotificationListeners(
+ admin1, Collections.singletonList(packageName)));
+ assertNull(dpms.getPermittedCrossProfileNotificationListeners(admin1));
+
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ assertTrue(dpms.isNotificationListenerServicePermitted(packageName, userId));
+
+ // Attempt to set to empty list (which means no listener is whitelisted)
+ mContext.binder.callingUid = adminUid;
+ assertFalse(dpms.setPermittedCrossProfileNotificationListeners(
+ admin1, Collections.<String>emptyList()));
+ assertNull(dpms.getPermittedCrossProfileNotificationListeners(admin1));
+
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ assertTrue(dpms.isNotificationListenerServicePermitted(packageName, userId));
+ }
+
+ public void testIsNotificationListenerServicePermitted_onlySystemCanCall() throws Exception {
+ // Set up a managed profile
+ final int MANAGED_PROFILE_USER_ID = 15;
+ final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436);
+ addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+ mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+
+ final String permittedListener = "some.package";
+ setupPackageInPackageManager(
+ permittedListener,
+ UserHandle.USER_SYSTEM, // We check the packageInfo from the primary user.
+ /*appId=*/ 12345, /*flags=*/ 0);
+
+ assertTrue(dpms.setPermittedCrossProfileNotificationListeners(
+ admin1, Collections.singletonList(permittedListener)));
+
+ try {
+ dpms.isNotificationListenerServicePermitted(
+ permittedListener, MANAGED_PROFILE_USER_ID);
+ fail("isNotificationListenerServicePermitted should throw if not called from System");
+ } catch (SecurityException expected) {
+ }
+
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ assertTrue(dpms.isNotificationListenerServicePermitted(
+ permittedListener, MANAGED_PROFILE_USER_ID));
+ }
+
+ public void testSetPermittedCrossProfileNotificationListeners_managedProfile()
+ throws Exception {
+ // Set up a managed profile
+ final int MANAGED_PROFILE_USER_ID = 15;
+ final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436);
+ addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+ mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+
+ final String permittedListener = "permitted.package";
+ int appId = 12345;
+ setupPackageInPackageManager(
+ permittedListener,
+ UserHandle.USER_SYSTEM, // We check the packageInfo from the primary user.
+ appId, /*flags=*/ 0);
+
+ final String notPermittedListener = "not.permitted.package";
+ setupPackageInPackageManager(
+ notPermittedListener,
+ UserHandle.USER_SYSTEM, // We check the packageInfo from the primary user.
+ ++appId, /*flags=*/ 0);
+
+ final String systemListener = "system.package";
+ setupPackageInPackageManager(
+ systemListener,
+ UserHandle.USER_SYSTEM, // We check the packageInfo from the primary user.
+ ++appId, ApplicationInfo.FLAG_SYSTEM);
+
+ // By default all packages are allowed
+ assertNull(dpms.getPermittedCrossProfileNotificationListeners(admin1));
+
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ assertTrue(dpms.isNotificationListenerServicePermitted(
+ permittedListener, MANAGED_PROFILE_USER_ID));
+ assertTrue(dpms.isNotificationListenerServicePermitted(
+ notPermittedListener, MANAGED_PROFILE_USER_ID));
+ assertTrue(dpms.isNotificationListenerServicePermitted(
+ systemListener, MANAGED_PROFILE_USER_ID));
+
+ // Setting only one package in the whitelist
+ mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+ assertTrue(dpms.setPermittedCrossProfileNotificationListeners(
+ admin1, Collections.singletonList(permittedListener)));
+ List<String> permittedListeners =
+ dpms.getPermittedCrossProfileNotificationListeners(admin1);
+ assertEquals(1, permittedListeners.size());
+ assertEquals(permittedListener, permittedListeners.get(0));
+
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ assertTrue(dpms.isNotificationListenerServicePermitted(
+ permittedListener, MANAGED_PROFILE_USER_ID));
+ assertFalse(dpms.isNotificationListenerServicePermitted(
+ notPermittedListener, MANAGED_PROFILE_USER_ID));
+ // System packages are always allowed (even if not in the whitelist)
+ assertTrue(dpms.isNotificationListenerServicePermitted(
+ systemListener, MANAGED_PROFILE_USER_ID));
+
+ // Setting an empty whitelist - only system listeners allowed
+ mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+ assertTrue(dpms.setPermittedCrossProfileNotificationListeners(
+ admin1, Collections.<String>emptyList()));
+ assertEquals(0, dpms.getPermittedCrossProfileNotificationListeners(admin1).size());
+
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ assertFalse(dpms.isNotificationListenerServicePermitted(
+ permittedListener, MANAGED_PROFILE_USER_ID));
+ assertFalse(dpms.isNotificationListenerServicePermitted(
+ notPermittedListener, MANAGED_PROFILE_USER_ID));
+ // System packages are always allowed (even if not in the whitelist)
+ assertTrue(dpms.isNotificationListenerServicePermitted(
+ systemListener, MANAGED_PROFILE_USER_ID));
+
+ // Setting a null whitelist - all listeners allowed
+ mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+ assertTrue(dpms.setPermittedCrossProfileNotificationListeners(admin1, null));
+ assertNull(dpms.getPermittedCrossProfileNotificationListeners(admin1));
+
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ assertTrue(dpms.isNotificationListenerServicePermitted(
+ permittedListener, MANAGED_PROFILE_USER_ID));
+ assertTrue(dpms.isNotificationListenerServicePermitted(
+ notPermittedListener, MANAGED_PROFILE_USER_ID));
+ assertTrue(dpms.isNotificationListenerServicePermitted(
+ systemListener, MANAGED_PROFILE_USER_ID));
+ }
+
+ public void testSetPermittedCrossProfileNotificationListeners_doesNotAffectPrimaryProfile()
+ throws Exception {
+ // Set up a managed profile
+ final int MANAGED_PROFILE_USER_ID = 15;
+ final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 19436);
+ addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
+ mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+
+ final String nonSystemPackage = "non.system.package";
+ int appId = 12345;
+ setupPackageInPackageManager(
+ nonSystemPackage,
+ UserHandle.USER_SYSTEM, // We check the packageInfo from the primary user.
+ appId, /*flags=*/ 0);
+
+ final String systemListener = "system.package";
+ setupPackageInPackageManager(
+ systemListener,
+ UserHandle.USER_SYSTEM, // We check the packageInfo from the primary user.
+ ++appId, ApplicationInfo.FLAG_SYSTEM);
+
+ // By default all packages are allowed (for all profiles)
+ assertNull(dpms.getPermittedCrossProfileNotificationListeners(admin1));
+
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ assertTrue(dpms.isNotificationListenerServicePermitted(
+ nonSystemPackage, MANAGED_PROFILE_USER_ID));
+ assertTrue(dpms.isNotificationListenerServicePermitted(
+ systemListener, MANAGED_PROFILE_USER_ID));
+ assertTrue(dpms.isNotificationListenerServicePermitted(
+ nonSystemPackage, UserHandle.USER_SYSTEM));
+ assertTrue(dpms.isNotificationListenerServicePermitted(
+ systemListener, UserHandle.USER_SYSTEM));
+
+ // Setting an empty whitelist - only system listeners allowed in managed profile, but
+ // all allowed in primary profile
+ mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
+ assertTrue(dpms.setPermittedCrossProfileNotificationListeners(
+ admin1, Collections.<String>emptyList()));
+ assertEquals(0, dpms.getPermittedCrossProfileNotificationListeners(admin1).size());
+
+ mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+ assertFalse(dpms.isNotificationListenerServicePermitted(
+ nonSystemPackage, MANAGED_PROFILE_USER_ID));
+ assertTrue(dpms.isNotificationListenerServicePermitted(
+ systemListener, MANAGED_PROFILE_USER_ID));
+ assertTrue(dpms.isNotificationListenerServicePermitted(
+ nonSystemPackage, UserHandle.USER_SYSTEM));
+ assertTrue(dpms.isNotificationListenerServicePermitted(
+ systemListener, UserHandle.USER_SYSTEM));
+ }
+
public void testGetOwnerInstalledCaCertsForDeviceOwner() throws Exception {
mContext.packageName = mRealTestContext.getPackageName();
setDeviceOwner();