Add user affiliation APIs

Make setAffiliationIds public so that it can be used for COMP.
That way we can allow network logging and other features to
work on devices that have a DO and a managed profile.
Those features are currently restricted to single user devices but we'll
open them up to devices where all users are affiliated.

Also create a getter for that API.

Bug: 32326223
Test: m FrameworksServicesTests &&
 adb install \
   -r ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
 adb shell am instrument -e class com.android.server.devicepolicy.DevicePolicyManagerTest \
   -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner

Change-Id: Ie443be887a6ca61a7f7a07e137757dceab7eb3d3
diff --git a/api/current.txt b/api/current.txt
index 8a00273..c0be4d0 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5996,6 +5996,7 @@
     method public int enableSystemApp(android.content.ComponentName, android.content.Intent);
     method public java.lang.String[] getAccountTypesWithManagementDisabled();
     method public java.util.List<android.content.ComponentName> getActiveAdmins();
+    method public java.util.List<java.lang.String> getAffiliationIds(android.content.ComponentName);
     method public java.lang.String getAlwaysOnVpnPackage(android.content.ComponentName);
     method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
     method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
@@ -6069,6 +6070,7 @@
     method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrievePreRebootSecurityLogs(android.content.ComponentName);
     method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrieveSecurityLogs(android.content.ComponentName);
     method public void setAccountManagementDisabled(android.content.ComponentName, java.lang.String, boolean);
+    method public void setAffiliationIds(android.content.ComponentName, java.util.List<java.lang.String>);
     method public void setAlwaysOnVpnPackage(android.content.ComponentName, java.lang.String, boolean) throws android.content.pm.PackageManager.NameNotFoundException, java.lang.UnsupportedOperationException;
     method public boolean setApplicationHidden(android.content.ComponentName, java.lang.String, boolean);
     method public void setApplicationRestrictions(android.content.ComponentName, java.lang.String, android.os.Bundle);
diff --git a/api/system-current.txt b/api/system-current.txt
index 3ecc8aa..e88b948 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6168,6 +6168,7 @@
     method public int enableSystemApp(android.content.ComponentName, android.content.Intent);
     method public java.lang.String[] getAccountTypesWithManagementDisabled();
     method public java.util.List<android.content.ComponentName> getActiveAdmins();
+    method public java.util.List<java.lang.String> getAffiliationIds(android.content.ComponentName);
     method public java.lang.String getAlwaysOnVpnPackage(android.content.ComponentName);
     method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
     method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
@@ -6252,6 +6253,7 @@
     method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrieveSecurityLogs(android.content.ComponentName);
     method public void setAccountManagementDisabled(android.content.ComponentName, java.lang.String, boolean);
     method public deprecated boolean setActiveProfileOwner(android.content.ComponentName, java.lang.String) throws java.lang.IllegalArgumentException;
+    method public void setAffiliationIds(android.content.ComponentName, java.util.List<java.lang.String>);
     method public void setAlwaysOnVpnPackage(android.content.ComponentName, java.lang.String, boolean) throws android.content.pm.PackageManager.NameNotFoundException, java.lang.UnsupportedOperationException;
     method public boolean setApplicationHidden(android.content.ComponentName, java.lang.String, boolean);
     method public void setApplicationRestrictions(android.content.ComponentName, java.lang.String, android.os.Bundle);
diff --git a/api/test-current.txt b/api/test-current.txt
index 13b0c94..b6f5530 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6012,6 +6012,7 @@
     method public int enableSystemApp(android.content.ComponentName, android.content.Intent);
     method public java.lang.String[] getAccountTypesWithManagementDisabled();
     method public java.util.List<android.content.ComponentName> getActiveAdmins();
+    method public java.util.List<java.lang.String> getAffiliationIds(android.content.ComponentName);
     method public java.lang.String getAlwaysOnVpnPackage(android.content.ComponentName);
     method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
     method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
@@ -6085,6 +6086,7 @@
     method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrievePreRebootSecurityLogs(android.content.ComponentName);
     method public java.util.List<android.app.admin.SecurityLog.SecurityEvent> retrieveSecurityLogs(android.content.ComponentName);
     method public void setAccountManagementDisabled(android.content.ComponentName, java.lang.String, boolean);
+    method public void setAffiliationIds(android.content.ComponentName, java.util.List<java.lang.String>);
     method public void setAlwaysOnVpnPackage(android.content.ComponentName, java.lang.String, boolean) throws android.content.pm.PackageManager.NameNotFoundException, java.lang.UnsupportedOperationException;
     method public boolean setApplicationHidden(android.content.ComponentName, java.lang.String, boolean);
     method public void setApplicationRestrictions(android.content.ComponentName, java.lang.String, android.os.Bundle);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 0196312..6adca01 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -78,7 +78,6 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import java.util.Set;
 
 /**
  * Public interface for managing policies enforced on a device. Most clients of this class must be
@@ -6463,27 +6462,32 @@
     }
 
     /**
-     * @hide
-     * Indicates the entity that controls the device or profile owner. A user/profile is considered
-     * affiliated if it is managed by the same entity as the device.
-     *
-     * <p> By definition, the user that the device owner runs on is always affiliated. Any other
-     * user/profile is considered affiliated if the following conditions are both met:
-     * <ul>
-     * <li>The device owner and the user's/profile's profile owner have called this method,
-     *   specifying a set of opaque affiliation ids each. If the sets specified by the device owner
-     *   and a profile owner intersect, they must have come from the same source, which means that
-     *   the device owner and profile owner are controlled by the same entity.</li>
-     * <li>The device owner's and profile owner's package names are the same.</li>
-     * </ul>
+     * Indicates the entity that controls the device or profile owner. Two users/profiles are
+     * affiliated if the set of ids set by their device or profile owners intersect.
      *
      * @param admin Which profile or device owner this request is associated with.
-     * @param ids A set of opaque affiliation ids.
+     * @param ids A list of opaque non-empty affiliation ids. Duplicate elements will be ignored.
+     *
+     * @throws NullPointerException if {@code ids} is null or contains null elements.
+     * @throws IllegalArgumentException if {@code ids} contains an empty string.
      */
-    public void setAffiliationIds(@NonNull ComponentName admin, Set<String> ids) {
+    public void setAffiliationIds(@NonNull ComponentName admin, @NonNull List<String> ids) {
         throwIfParentInstance("setAffiliationIds");
         try {
-            mService.setAffiliationIds(admin, new ArrayList<String>(ids));
+            mService.setAffiliationIds(admin, ids);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the list of affiliation ids previously set via {@link #setAffiliationIds}, or an
+     * empty list if none have been set.
+     */
+    public @NonNull List<String> getAffiliationIds(@NonNull ComponentName admin) {
+        throwIfParentInstance("getAffiliationIds");
+        try {
+            return mService.getAffiliationIds(admin);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -6491,15 +6495,17 @@
 
     /**
      * @hide
-     * Returns whether this user/profile is affiliated with the device. See
-     * {@link #setAffiliationIds} for the definition of affiliation.
+     * Returns whether this user/profile is affiliated with the device.
+     * <p>
+     * By definition, the user that the device owner runs on is always affiliated with the device.
+     * Any other user/profile is considered affiliated with the device if the set specified by its
+     * profile owner via {@link #setAffiliationIds} intersects with the device owner's.
      *
-     * @return whether this user/profile is affiliated with the device.
      */
     public boolean isAffiliatedUser() {
         throwIfParentInstance("isAffiliatedUser");
         try {
-            return mService != null && mService.isAffiliatedUser();
+            return mService.isAffiliatedUser();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index d14e0d0..be3902ca 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -299,6 +299,7 @@
     void setUserProvisioningState(int state, int userHandle);
 
     void setAffiliationIds(in ComponentName admin, in List<String> ids);
+    List<String> getAffiliationIds(in ComponentName admin);
     boolean isAffiliatedUser();
 
     void setSecurityLoggingEnabled(in ComponentName admin, boolean enabled);
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c497cb1..86f42bc 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -213,6 +213,8 @@
 
     private static final String TAG_ADMIN_BROADCAST_PENDING = "admin-broadcast-pending";
 
+    private static final String ATTR_ID = "id";
+
     private static final String ATTR_VALUE = "value";
 
     private static final String TAG_INITIALIZATION_BUNDLE = "initialization-bundle";
@@ -2367,7 +2369,7 @@
 
             for (String id : policy.mAffiliationIds) {
                 out.startTag(null, TAG_AFFILIATION_ID);
-                out.attribute(null, "id", id);
+                out.attribute(null, ATTR_ID, id);
                 out.endTag(null, TAG_AFFILIATION_ID);
             }
 
@@ -2547,7 +2549,7 @@
                 } else if (DO_NOT_ASK_CREDENTIALS_ON_BOOT_XML.equals(tag)) {
                     policy.doNotAskCredentialsOnBoot = true;
                 } else if (TAG_AFFILIATION_ID.equals(tag)) {
-                    policy.mAffiliationIds.add(parser.getAttributeValue(null, "id"));
+                    policy.mAffiliationIds.add(parser.getAttributeValue(null, ATTR_ID));
                 } else if (TAG_LAST_SECURITY_LOG_RETRIEVAL.equals(tag)) {
                     policy.mLastSecurityLogRetrievalTime = Long.parseLong(
                             parser.getAttributeValue(null, ATTR_VALUE));
@@ -8041,13 +8043,11 @@
     public void setLockTaskPackages(ComponentName who, String[] packages)
             throws SecurityException {
         Preconditions.checkNotNull(who, "ComponentName is null");
+
         synchronized (this) {
-            ActiveAdmin deviceOwner = getActiveAdminWithPolicyForUidLocked(
-                who, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER, mInjector.binderGetCallingUid());
-            ActiveAdmin profileOwner = getActiveAdminWithPolicyForUidLocked(
-                who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, mInjector.binderGetCallingUid());
-            if (deviceOwner != null || (profileOwner != null && isAffiliatedUser())) {
-                int userHandle = mInjector.userHandleGetCallingUserId();
+            getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            final int userHandle = mInjector.userHandleGetCallingUserId();
+            if (isUserAffiliatedWithDevice(userHandle)) {
                 setLockTaskPackagesLocked(userHandle, new ArrayList<>(Arrays.asList(packages)));
             } else {
                 throw new SecurityException("Admin " + who +
@@ -9142,9 +9142,18 @@
 
     @Override
     public void setAffiliationIds(ComponentName admin, List<String> ids) {
-        final Set<String> affiliationIds = new ArraySet<String>(ids);
-        final int callingUserId = mInjector.userHandleGetCallingUserId();
+        if (!mHasFeature) {
+            return;
+        }
 
+        Preconditions.checkNotNull(admin);
+        Preconditions.checkCollectionElementsNotNull(ids, "ids");
+
+        final Set<String> affiliationIds = new ArraySet<String>(ids);
+        Preconditions.checkArgument(
+                !affiliationIds.contains(""), "ids must not contain empty strings");
+
+        final int callingUserId = mInjector.userHandleGetCallingUserId();
         synchronized (this) {
             getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
             getUserData(callingUserId).mAffiliationIds = affiliationIds;
@@ -9159,20 +9168,44 @@
     }
 
     @Override
-    public boolean isAffiliatedUser() {
-        final int callingUserId = mInjector.userHandleGetCallingUserId();
+    public List<String> getAffiliationIds(ComponentName admin) {
+        if (!mHasFeature) {
+            return Collections.emptyList();
+        }
 
+        Preconditions.checkNotNull(admin);
         synchronized (this) {
-            if (mOwners.getDeviceOwnerUserId() == callingUserId) {
-                // The user that the DO is installed on is always affiliated.
-                return true;
-            }
-            final ComponentName profileOwner = getProfileOwner(callingUserId);
-            if (profileOwner == null
-                    || !profileOwner.getPackageName().equals(mOwners.getDeviceOwnerPackageName())) {
+            getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+            return new ArrayList<String>(
+                    getUserData(mInjector.userHandleGetCallingUserId()).mAffiliationIds);
+        }
+    }
+
+    @Override
+    public boolean isAffiliatedUser() {
+        return isUserAffiliatedWithDevice(mInjector.userHandleGetCallingUserId());
+    }
+
+    private boolean isUserAffiliatedWithDevice(int userId) {
+        synchronized (this) {
+            if (!mOwners.hasDeviceOwner()) {
                 return false;
             }
-            final Set<String> userAffiliationIds = getUserData(callingUserId).mAffiliationIds;
+            if (userId == mOwners.getDeviceOwnerUserId()) {
+                // The user that the DO is installed on is always affiliated with the device.
+                return true;
+            }
+            if (userId == UserHandle.USER_SYSTEM) {
+                // The system user is always affiliated in a DO device, even if the DO is set on a
+                // different user. This could be the case if the DO is set in the primary user
+                // of a split user device.
+                return true;
+            }
+            final ComponentName profileOwner = getProfileOwner(userId);
+            if (profileOwner == null) {
+                return false;
+            }
+            final Set<String> userAffiliationIds = getUserData(userId).mAffiliationIds;
             final Set<String> deviceAffiliationIds =
                     getUserData(UserHandle.USER_SYSTEM).mAffiliationIds;
             for (String id : userAffiliationIds) {
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 e55cafb..ed37b80 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -50,6 +50,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -1643,6 +1644,7 @@
     /**
      * Test for:
      * {@link DevicePolicyManager#setAffiliationIds}
+     * {@link DevicePolicyManager#getAffiliationIds}
      * {@link DevicePolicyManager#isAffiliatedUser}
      */
     public void testUserAffiliation() throws Exception {
@@ -1659,30 +1661,34 @@
         dpm.setActiveAdmin(admin1, /* replace =*/ false);
         assertTrue(dpm.setDeviceOwner(admin1, "owner-name"));
         assertTrue(dpm.isAffiliatedUser());
+        assertTrue(dpm.getAffiliationIds(admin1).isEmpty());
 
-        // Install a profile owner whose package name matches the device owner on a test user. Check
-        // that the test user is unaffiliated.
+        // Install a profile owner. Check that the test user is unaffiliated.
         mContext.binder.callingUid = DpmMockContext.CALLER_UID;
         setAsProfileOwner(admin2);
         assertFalse(dpm.isAffiliatedUser());
+        assertTrue(dpm.getAffiliationIds(admin2).isEmpty());
 
         // Have the profile owner specify a set of affiliation ids. Check that the test user remains
         // unaffiliated.
-        final Set<String> userAffiliationIds = new ArraySet<>();
+        final List<String> userAffiliationIds = new ArrayList<>();
         userAffiliationIds.add("red");
         userAffiliationIds.add("green");
         userAffiliationIds.add("blue");
         dpm.setAffiliationIds(admin2, userAffiliationIds);
+        MoreAsserts.assertContentsInAnyOrder(dpm.getAffiliationIds(admin2), "red", "green", "blue");
         assertFalse(dpm.isAffiliatedUser());
 
         // Have the device owner specify a set of affiliation ids that do not intersect with those
         // specified by the profile owner. Check that the test user remains unaffiliated.
-        final Set<String> deviceAffiliationIds = new ArraySet<>();
+        final List<String> deviceAffiliationIds = new ArrayList<>();
         deviceAffiliationIds.add("cyan");
         deviceAffiliationIds.add("yellow");
         deviceAffiliationIds.add("magenta");
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         dpm.setAffiliationIds(admin1, deviceAffiliationIds);
+        MoreAsserts.assertContentsInAnyOrder(
+            dpm.getAffiliationIds(admin1), "cyan", "yellow", "magenta");
         mContext.binder.callingUid = DpmMockContext.CALLER_UID;
         assertFalse(dpm.isAffiliatedUser());
 
@@ -1690,19 +1696,13 @@
         // specified by the device owner. Check that the test user becomes affiliated.
         userAffiliationIds.add("yellow");
         dpm.setAffiliationIds(admin2, userAffiliationIds);
+        MoreAsserts.assertContentsInAnyOrder(
+            dpm.getAffiliationIds(admin2), "red", "green", "blue", "yellow");
         assertTrue(dpm.isAffiliatedUser());
 
-        // Change the profile owner to one whose package name does not match the device owner. Check
-        // that the test user is not affiliated anymore.
-        dpm.clearProfileOwner(admin2);
-        final ComponentName admin = new ComponentName("test", "test");
-
-        setUpPackageManagerForFakeAdmin(admin, DpmMockContext.CALLER_UID,
-                /* enabledSetting =*/ PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
-                /* appTargetSdk = */ null, admin2);
-
-        dpm.setActiveAdmin(admin, /* refreshing =*/ true, DpmMockContext.CALLER_USER_HANDLE);
-        assertTrue(dpm.setProfileOwner(admin, "owner-name", DpmMockContext.CALLER_USER_HANDLE));
+        // Clear affiliation ids for the profile owner. The user becomes unaffiliated.
+        dpm.setAffiliationIds(admin2, Collections.emptyList());
+        assertTrue(dpm.getAffiliationIds(admin2).isEmpty());
         assertFalse(dpm.isAffiliatedUser());
 
         // Check that the system user remains affiliated.