New API for getting the screen lock complexity

Return parent profile screen lock complexity if unified challenge is used

Bug: 111173457
Test: atest FrameworksCoreTests:PasswordMetricsTest
      atest FrameworksServicesTests:DevicePolicyManagerTest
      atest CtsDevicePolicyManagerTestCases:com.android.cts.devicepolicy.PasswordComplexityTest
      manual test with sample app

Change-Id: I99f8bd644c5119590f49add98f216c4a527d6f2d
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 6462d16..4a9a923 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -95,6 +95,11 @@
     public void grantDeviceIdsAccessToProfileOwner(ComponentName who, int userId) { }
 
     @Override
+    public int getPasswordComplexity() {
+        return DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
+    }
+
+    @Override
     public void installUpdateFromFile(ComponentName admin,
             ParcelFileDescriptor updateFileDescriptor, StartInstallingUpdateCallback listener) {}
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 7751b4a..d3f7a6f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.devicepolicy;
 
 import static android.Manifest.permission.BIND_DEVICE_ADMIN;
+import static android.Manifest.permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY;
 import static android.Manifest.permission.MANAGE_CA_CERTIFICATES;
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
 import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE;
@@ -53,6 +54,7 @@
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_HOME;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
 import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
 import static android.app.admin.DevicePolicyManager.PRIVATE_DNS_MODE_OFF;
@@ -108,6 +110,7 @@
 import android.app.admin.DeviceAdminReceiver;
 import android.app.admin.DevicePolicyCache;
 import android.app.admin.DevicePolicyManager;
+import android.app.admin.DevicePolicyManager.PasswordComplexity;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.app.admin.NetworkEvent;
 import android.app.admin.PasswordMetrics;
@@ -4644,6 +4647,22 @@
     }
 
     @Override
+    @PasswordComplexity
+    public int getPasswordComplexity() {
+        final int callingUserId = mInjector.userHandleGetCallingUserId();
+        enforceUserUnlocked(callingUserId);
+        mContext.enforceCallingOrSelfPermission(
+                GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY,
+                "Must have " + GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY + " permission.");
+
+        synchronized (getLockObject()) {
+            int targetUserId = getCredentialOwner(callingUserId, /* parent= */ false);
+            PasswordMetrics metrics = getUserPasswordMetricsLocked(targetUserId);
+            return metrics == null ? PASSWORD_COMPLEXITY_NONE : metrics.determineComplexity();
+        }
+    }
+
+    @Override
     public int getCurrentFailedPasswordAttempts(int userHandle, boolean parent) {
         enforceFullCrossUsersPermission(userHandle);
         synchronized (getLockObject()) {
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 5dc6d83..2a1c3db 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -21,6 +21,9 @@
 import static android.app.admin.DevicePolicyManager.ID_TYPE_IMEI;
 import static android.app.admin.DevicePolicyManager.ID_TYPE_MEID;
 import static android.app.admin.DevicePolicyManager.ID_TYPE_SERIAL;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_HIGH;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_MEDIUM;
+import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
 import static android.app.admin.DevicePolicyManager.WIPE_EUICC;
 import static android.os.UserManagerInternal.CAMERA_DISABLED_GLOBALLY;
 import static android.os.UserManagerInternal.CAMERA_DISABLED_LOCALLY;
@@ -48,6 +51,7 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 import static org.mockito.hamcrest.MockitoHamcrest.argThat;
+import static org.testng.Assert.assertThrows;
 
 import android.Manifest.permission;
 import android.annotation.RawRes;
@@ -5133,6 +5137,71 @@
         });
     }
 
+    public void testGetPasswordComplexity_securityExceptionIfParentInstance() {
+        assertThrows(SecurityException.class,
+                () -> new DevicePolicyManagerTestable(
+                        mServiceContext,
+                        dpms,
+                        /* parentInstance= */ true)
+                        .getPasswordComplexity());
+    }
+
+    public void testGetPasswordComplexity_illegalStateExceptionIfLocked() {
+        when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE))
+                .thenReturn(false);
+        assertThrows(IllegalStateException.class, () -> dpm.getPasswordComplexity());
+    }
+
+    public void testGetPasswordComplexity_securityExceptionWithoutPermissions() {
+        when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE))
+                .thenReturn(true);
+        assertThrows(SecurityException.class, () -> dpm.getPasswordComplexity());
+    }
+
+
+    public void testGetPasswordComplexity_currentUserNoPassword() {
+        when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE))
+                .thenReturn(true);
+        mServiceContext.permissions.add(permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY);
+        when(getServices().userManager.getCredentialOwnerProfile(DpmMockContext.CALLER_USER_HANDLE))
+                .thenReturn(DpmMockContext.CALLER_USER_HANDLE);
+
+        assertEquals(PASSWORD_COMPLEXITY_NONE, dpm.getPasswordComplexity());
+    }
+
+    public void testGetPasswordComplexity_currentUserHasPassword() {
+        when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE))
+                .thenReturn(true);
+        mServiceContext.permissions.add(permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY);
+        when(getServices().userManager.getCredentialOwnerProfile(DpmMockContext.CALLER_USER_HANDLE))
+                .thenReturn(DpmMockContext.CALLER_USER_HANDLE);
+        dpms.mUserPasswordMetrics.put(
+                DpmMockContext.CALLER_USER_HANDLE,
+                PasswordMetrics.computeForPassword("asdf"));
+
+        assertEquals(PASSWORD_COMPLEXITY_MEDIUM, dpm.getPasswordComplexity());
+    }
+
+    public void testGetPasswordComplexity_unifiedChallengeReturnsParentUserPassword() {
+        when(getServices().userManager.isUserUnlocked(DpmMockContext.CALLER_USER_HANDLE))
+                .thenReturn(true);
+        mServiceContext.permissions.add(permission.GET_AND_REQUEST_SCREEN_LOCK_COMPLEXITY);
+
+        UserInfo parentUser = new UserInfo();
+        parentUser.id = DpmMockContext.CALLER_USER_HANDLE + 10;
+        when(getServices().userManager.getCredentialOwnerProfile(DpmMockContext.CALLER_USER_HANDLE))
+                .thenReturn(parentUser.id);
+
+        dpms.mUserPasswordMetrics.put(
+                DpmMockContext.CALLER_USER_HANDLE,
+                PasswordMetrics.computeForPassword("asdf"));
+        dpms.mUserPasswordMetrics.put(
+                parentUser.id,
+                PasswordMetrics.computeForPassword("parentUser"));
+
+        assertEquals(PASSWORD_COMPLEXITY_HIGH, dpm.getPasswordComplexity());
+    }
+
     private void configureProfileOwnerForDeviceIdAccess(ComponentName who, int userId) {
         final long ident = mServiceContext.binder.clearCallingIdentity();
         mServiceContext.binder.callingUid =
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java
index 3da61d6..4982d6e 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTestable.java
@@ -26,7 +26,12 @@
 
     public DevicePolicyManagerTestable(DpmMockContext context,
             DevicePolicyManagerServiceTestable dpms) {
-        super(context, dpms, /* parentInstance = */ false);
+        this(context, dpms, /* parentInstance= */ false);
+    }
+
+    public DevicePolicyManagerTestable(DpmMockContext context,
+            DevicePolicyManagerServiceTestable dpms, boolean parentInstance) {
+        super(context, dpms, parentInstance);
         this.dpms = dpms;
     }