getWifiMacAddress: Enable for profile owner on org-owned device

Allow the profile owner on an organization-owned device to call
getWifiMacAddress.

Part of this change is introducing a new DeviceAdminInfo policy,
USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER, which can be used
to find out if the admin is either device owner or profile owner
on organization-owned device.

Bug: 138709470
Bug: 145308393
Bug: 145336515
Test: atest CtsDevicePolicyManagerTestCases:com.android.cts.devicepolicy.MixedDeviceOwnerTest#testWifi
Test: atest CtsDevicePolicyManagerTestCases:com.android.cts.devicepolicy.MixedManagedProfileOwnerTest#testWifiMacAddress
Test: atest CtsDevicePolicyManagerTestCases:com.android.cts.devicepolicy.OrgOwnedProfileOwnerTest#testWifi
Change-Id: Id790408a07b9dd19530563bd2aa9216a7333e863
diff --git a/core/java/android/app/admin/DeviceAdminInfo.java b/core/java/android/app/admin/DeviceAdminInfo.java
index 00903c4..63bc40b 100644
--- a/core/java/android/app/admin/DeviceAdminInfo.java
+++ b/core/java/android/app/admin/DeviceAdminInfo.java
@@ -55,6 +55,14 @@
     static final String TAG = "DeviceAdminInfo";
 
     /**
+     * A type of policy that this device admin can use: profile owner on an organization-owned
+     * device.
+     *
+     * @hide
+     */
+    public static final int USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER = -3;
+
+    /**
      * A type of policy that this device admin can use: device owner meta-policy
      * for an admin that is designated as owner of the device.
      *
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 39dc51e..34ceb08 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9135,7 +9135,14 @@
     }
 
     /**
-     * Called by device owner to get the MAC address of the Wi-Fi device.
+     * Called by device owner, or profile owner on organization-owned device, to get the MAC
+     * address of the Wi-Fi device.
+     *
+     * NOTE: The MAC address returned here should only be used for inventory management and is
+     * not likely to be the MAC address used by the device to connect to Wi-Fi networks: MAC
+     * addresses used for scanning and connecting to Wi-Fi networks are randomized by default.
+     * To get the randomized MAC address used, call
+     * {@link android.net.wifi.WifiConfiguration#getRandomizedMacAddress}.
      *
      * @param admin Which device owner this request is associated with.
      * @return the MAC address of the Wi-Fi device, or null when the information is not available.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2499ad8..c2993ae 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -2748,6 +2748,9 @@
             return null;
         }
 
+        // Code for handling failure from getActiveAdminWithPolicyForUidLocked to find an admin
+        // that satisfies the required policy.
+        // Throws a security exception with the right error message.
         if (who != null) {
             final int userId = UserHandle.getUserId(callingUid);
             final DevicePolicyData policy = getUserData(userId);
@@ -2763,6 +2766,10 @@
                 throw new SecurityException("Admin " + admin.info.getComponent()
                         + " does not own the profile");
             }
+            if (reqPolicy == DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER) {
+                throw new SecurityException("Admin " + admin.info.getComponent()
+                        + " is not the profile owner on organization-owned device");
+            }
             if (DA_DISALLOWED_POLICIES.contains(reqPolicy) && !isDeviceOwner && !isProfileOwner) {
                 throw new SecurityException("Admin " + admin.info.getComponent()
                         + " is not a device owner or profile owner, so may not use policy: "
@@ -2869,12 +2876,16 @@
         ensureLocked();
         final boolean ownsDevice = isDeviceOwner(admin.info.getComponent(), userId);
         final boolean ownsProfile = isProfileOwner(admin.info.getComponent(), userId);
+        final boolean ownsProfileOnOrganizationOwnedDevice =
+                    isProfileOwnerOfOrganizationOwnedDevice(admin.info.getComponent(), userId);
 
         if (reqPolicy == DeviceAdminInfo.USES_POLICY_DEVICE_OWNER) {
             return ownsDevice;
+        } else if (reqPolicy == DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER) {
+            return ownsDevice || ownsProfileOnOrganizationOwnedDevice;
         } else if (reqPolicy == DeviceAdminInfo.USES_POLICY_PROFILE_OWNER) {
             // DO always has the PO power.
-            return ownsDevice || ownsProfile;
+            return ownsDevice || ownsProfileOnOrganizationOwnedDevice || ownsProfile;
         } else {
             boolean allowedToUsePolicy = ownsDevice || ownsProfile
                     || !DA_DISALLOWED_POLICIES.contains(reqPolicy)
@@ -5574,6 +5585,13 @@
         }
     }
 
+    private void enforceDeviceOwnerOrProfileOwnerOnOrganizationOwnedDevice(ComponentName who) {
+        synchronized (getLockObject()) {
+            getActiveAdminForCallerLocked(
+                    who, DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
+        }
+    }
+
     private void enforceProfileOwnerOfOrganizationOwnedDevice(ActiveAdmin admin) {
         if (!isProfileOwnerOfOrganizationOwnedDevice(admin)) {
             throw new SecurityException(String.format("Provided admin %s is either not a profile "
@@ -8071,21 +8089,12 @@
             return false;
         }
 
-        final int adminUserId = admin.getUserHandle().getIdentifier();
+        return isProfileOwnerOfOrganizationOwnedDevice(
+                admin.info.getComponent(), admin.getUserHandle().getIdentifier());
+    }
 
-        if (!isProfileOwner(admin.info.getComponent(), adminUserId)) {
-            Slog.w(LOG_TAG, String.format("%s is not profile owner of user %d",
-                    admin.info.getComponent(), adminUserId));
-            return false;
-        }
-
-        if (!canProfileOwnerAccessDeviceIds(adminUserId)) {
-            Slog.w(LOG_TAG, String.format("Profile owner of user %d does not own the device.",
-                    adminUserId));
-            return false;
-        }
-
-        return true;
+    private boolean isProfileOwnerOfOrganizationOwnedDevice(ComponentName who, int userId) {
+        return isProfileOwner(who, userId) && canProfileOwnerAccessDeviceIds(userId);
     }
 
     @Override
@@ -12377,7 +12386,7 @@
     @Override
     public String getWifiMacAddress(ComponentName admin) {
         // Make sure caller has DO.
-        enforceDeviceOwner(admin);
+        enforceDeviceOwnerOrProfileOwnerOnOrganizationOwnedDevice(admin);
 
         final long ident = mInjector.binderClearCallingIdentity();
         try {
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 0590020..ba7d24b2 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -138,6 +138,8 @@
             permission.MANAGE_USERS, permission.INTERACT_ACROSS_USERS_FULL);
     public static final String NOT_DEVICE_OWNER_MSG = "does not own the device";
     public static final String NOT_PROFILE_OWNER_MSG = "does not own the profile";
+    public static final String NOT_ORG_OWNED_PROFILE_OWNER_MSG =
+            "not the profile owner on organization-owned device";
     public static final String ONGOING_CALL_MSG = "ongoing call on the device";
 
     // TODO replace all instances of this with explicit {@link #mServiceContext}.
@@ -2114,12 +2116,14 @@
         assertTrue(dpm.isAdminActive(admin1));
 
         // Test 2. Caller has DA, but not DO.
-        assertExpectException(SecurityException.class, /* messageRegex= */ NOT_DEVICE_OWNER_MSG,
+        assertExpectException(SecurityException.class,
+                /* messageRegex= */ NOT_ORG_OWNED_PROFILE_OWNER_MSG,
                 () -> dpm.getWifiMacAddress(admin1));
 
         // Test 3. Caller has PO, but not DO.
         assertTrue(dpm.setProfileOwner(admin1, null, UserHandle.USER_SYSTEM));
-        assertExpectException(SecurityException.class, /* messageRegex= */ NOT_DEVICE_OWNER_MSG,
+        assertExpectException(SecurityException.class,
+                /* messageRegex= */ NOT_ORG_OWNED_PROFILE_OWNER_MSG,
                 () -> dpm.getWifiMacAddress(admin1));
 
         // Remove PO.
@@ -2141,6 +2145,15 @@
         assertEquals("11:22:33:44:55:66", dpm.getWifiMacAddress(admin1));
     }
 
+    public void testGetMacAddressByOrgOwnedPO() throws Exception {
+        setupProfileOwner();
+        configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
+
+        final String[] macAddresses = new String[]{"11:22:33:44:55:66"};
+        when(getServices().wifiManager.getFactoryMacAddresses()).thenReturn(macAddresses);
+        assertEquals("11:22:33:44:55:66", dpm.getWifiMacAddress(admin1));
+    }
+
     public void testReboot() throws Exception {
         mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
         mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);