Add a feature flag for secure lock screen.

Disable certain APIs which require secure lock screen if the device
doesn't have the feature.
Make sure one cannot set the password/PIN if there is no secure lock
screen, because the password/PIN wouldn't be really used afterwards
while the password strength checks would succeed, creating a false
sense of security.
Allow setting password strength requirements in DPM - test if the
current password is sufficient will fail automatically if there is
no secure lock screen.

Bug: 111072170
Bug: 111071972
Test: cts-tradefed run cts -m CtsDevicePolicyManagerTestCases
Test: cts-tradefed run cts -m CtsAdminTestCases
Test: frameworks/base/core/tests/utiltests/runtests.sh
Test: adb shell am instrument -w -e class com.android.internal.widget.LockPatternUtilsTest com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner
Test: atest SyntheticPasswordTests
Test: atest LockSettingsServiceTests
Test: atest LockSettingsShellCommandTest
Test: atest DevicePolicyManagerTest (for servicestests)

Change-Id: Ie46b0de6cb03c26dd05c05711c5c3b5e36a872df
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 38e8ac2..0813e6fa 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -206,6 +206,8 @@
         mAdmin1Context.binder.callingUid = DpmMockContext.CALLER_UID;
 
         setUpUserManager();
+
+        when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true);
     }
 
     private TransferOwnershipMetadataManager getMockTransferMetadataManager() {
@@ -836,6 +838,7 @@
                 MockUtils.checkIntent(intent),
                 MockUtils.checkUserHandle(MANAGED_PROFILE_USER_ID));
     }
+
     /**
      * Test for: {@link DevicePolicyManager#setDeviceOwner} DO on system user installs successfully.
      */
@@ -2618,6 +2621,7 @@
         when(getServices().lockPatternUtils
                 .isSeparateProfileChallengeEnabled(eq(PROFILE_USER))).thenReturn(false);
         dpmi.reportSeparateProfileChallengeChanged(PROFILE_USER);
+        when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true);
 
         verifyScreenTimeoutCall(Long.MAX_VALUE, PROFILE_USER);
         verifyScreenTimeoutCall(10L , UserHandle.USER_SYSTEM);
@@ -4233,6 +4237,41 @@
         assertTrue(dpm.isActivePasswordSufficient());
     }
 
+    public void testIsActivePasswordSufficient_noLockScreen() throws Exception {
+        // If there is no lock screen, the password is considered empty no matter what, because
+        // it provides no security.
+        when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(false);
+
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        mContext.packageName = admin1.getPackageName();
+        setupDeviceOwner();
+
+        // If no password requirements are set, isActivePasswordSufficient should succeed.
+        assertTrue(dpm.isActivePasswordSufficient());
+
+        // Now set some password quality requirements.
+        dpm.setPasswordQuality(admin1, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
+
+        reset(mContext.spiedContext);
+        final int userHandle = UserHandle.getUserId(mContext.binder.callingUid);
+        PasswordMetrics passwordMetricsNoSymbols = new PasswordMetrics(
+                DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, 9,
+                8, 2,
+                6, 1,
+                0, 1);
+        // This should be ignored, as there is no lock screen.
+        dpm.setActivePasswordState(passwordMetricsNoSymbols, userHandle);
+        dpm.reportPasswordChanged(userHandle);
+
+        // No broadcast should be sent.
+        verify(mContext.spiedContext, times(0)).sendBroadcastAsUser(
+                MockUtils.checkIntentAction(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED),
+                MockUtils.checkUserHandle(userHandle));
+
+        // The active (nonexistent) password doesn't comply with the requirements.
+        assertFalse(dpm.isActivePasswordSufficient());
+    }
+
     private void setActivePasswordState(PasswordMetrics passwordMetrics)
             throws Exception {
         final int userHandle = UserHandle.getUserId(mContext.binder.callingUid);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index 2dc3510..cf89cb8 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -37,8 +37,8 @@
 import android.os.IProgressListener;
 import android.os.RemoteException;
 import android.os.UserManager;
-import android.os.storage.StorageManager;
 import android.os.storage.IStorageManager;
+import android.os.storage.StorageManager;
 import android.security.KeyStore;
 import android.test.AndroidTestCase;
 
@@ -46,6 +46,7 @@
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.LockSettingsInternal;
 import com.android.server.LocalServices;
+import com.android.server.wm.WindowManagerInternal;
 
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
@@ -85,6 +86,8 @@
     KeyStore mKeyStore;
     MockSyntheticPasswordManager mSpManager;
     IAuthSecret mAuthSecretService;
+    WindowManagerInternal mMockWindowManager;
+    protected boolean mHasSecureLockScreen;
 
     @Override
     protected void setUp() throws Exception {
@@ -97,10 +100,13 @@
         mActivityManager = mock(IActivityManager.class);
         mDevicePolicyManager = mock(DevicePolicyManager.class);
         mDevicePolicyManagerInternal = mock(DevicePolicyManagerInternal.class);
+        mMockWindowManager = mock(WindowManagerInternal.class);
 
         LocalServices.removeServiceForTest(LockSettingsInternal.class);
         LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+        LocalServices.removeServiceForTest(WindowManagerInternal.class);
         LocalServices.addService(DevicePolicyManagerInternal.class, mDevicePolicyManagerInternal);
+        LocalServices.addService(WindowManagerInternal.class, mMockWindowManager);
 
         mContext = new MockLockSettingsContext(getContext(), mUserManager, mNotificationManager,
                 mDevicePolicyManager, mock(StorageManager.class), mock(TrustManager.class),
@@ -114,11 +120,17 @@
             storageDir.mkdirs();
         }
 
+        mHasSecureLockScreen = true;
         mLockPatternUtils = new LockPatternUtils(mContext) {
             @Override
             public ILockSettings getLockSettings() {
                 return mService;
             }
+
+            @Override
+            public boolean hasSecureLockScreen() {
+                return mHasSecureLockScreen;
+            }
         };
         mSpManager = new MockSyntheticPasswordManager(mContext, mStorage, mGateKeeperService,
                 mUserManager);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
index e12f6d3..5124803 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTests.java
@@ -26,13 +26,12 @@
 import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
 
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.service.gatekeeper.GateKeeperResponse;
 
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.VerifyCredentialResponse;
-import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
 import com.android.server.locksettings.FakeGateKeeperService.VerifyHandle;
+import com.android.server.locksettings.LockSettingsStorage.CredentialHash;
 
 /**
  * runtest frameworks-services -c com.android.server.locksettings.LockSettingsServiceTests
@@ -54,11 +53,21 @@
                 PASSWORD_QUALITY_ALPHABETIC);
     }
 
+    public void testCreatePasswordFailsWithoutLockScreen() throws RemoteException {
+        testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, "password",
+                CREDENTIAL_TYPE_PASSWORD, PASSWORD_QUALITY_ALPHABETIC);
+    }
+
     public void testCreatePatternPrimaryUser() throws RemoteException {
         testCreateCredential(PRIMARY_USER_ID, "123456789", CREDENTIAL_TYPE_PATTERN,
                 PASSWORD_QUALITY_SOMETHING);
     }
 
+    public void testCreatePatternFailsWithoutLockScreen() throws RemoteException {
+        testCreateCredentialFailsWithoutLockScreen(PRIMARY_USER_ID, "123456789",
+                CREDENTIAL_TYPE_PATTERN, PASSWORD_QUALITY_SOMETHING);
+    }
+
     public void testChangePasswordPrimaryUser() throws RemoteException {
         testChangeCredentials(PRIMARY_USER_ID, "78963214", CREDENTIAL_TYPE_PATTERN,
                 "asdfghjk", CREDENTIAL_TYPE_PASSWORD, PASSWORD_QUALITY_ALPHABETIC);
@@ -198,6 +207,21 @@
         assertVerifyCredentials(userId, credential, type, -1);
     }
 
+    private void testCreateCredentialFailsWithoutLockScreen(
+            int userId, String credential, int type, int quality) throws RemoteException {
+        mHasSecureLockScreen = false;
+
+        try {
+            mService.setLockCredential(credential, type, null, quality, userId);
+            fail("An exception should have been thrown.");
+        } catch (UnsupportedOperationException e) {
+            // Success - the exception was expected.
+        }
+
+        assertFalse(mService.havePassword(userId));
+        assertFalse(mService.havePattern(userId));
+    }
+
     private void testChangeCredentials(int userId, String newCredential, int newType,
             String oldCredential, int oldType, int quality) throws RemoteException {
         final long sid = 1234;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
index a28a5a1..929c3b5 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsShellCommandTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
@@ -77,6 +78,7 @@
         final Context context = InstrumentationRegistry.getTargetContext();
         mUserId = ActivityManager.getCurrentUser();
         mCommand = new LockSettingsShellCommand(mLockPatternUtils);
+        when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(true);
     }
 
     @Test
@@ -103,6 +105,16 @@
     }
 
     @Test
+    public void testChangePin_noLockScreen() throws Exception {
+        when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false);
+        assertEquals(-1, mCommand.exec(new Binder(), in, out, err,
+                new String[] { "set-pin", "--old", "1234", "4321" },
+                mShellCallback, mResultReceiver));
+        verify(mLockPatternUtils).hasSecureLockScreen();
+        verifyNoMoreInteractions(mLockPatternUtils);
+    }
+
+    @Test
     public void testChangePassword() throws Exception {
         when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(false);
         when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(true);
@@ -115,6 +127,16 @@
     }
 
     @Test
+    public void testChangePassword_noLockScreen() throws Exception {
+        when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false);
+        assertEquals(-1,  mCommand.exec(new Binder(), in, out, err,
+                new String[] { "set-password", "--old", "1234", "4321" },
+                mShellCallback, mResultReceiver));
+        verify(mLockPatternUtils).hasSecureLockScreen();
+        verifyNoMoreInteractions(mLockPatternUtils);
+    }
+
+    @Test
     public void testChangePattern() throws Exception {
         when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
         when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
@@ -126,6 +148,16 @@
     }
 
     @Test
+    public void testChangePattern_noLockScreen() throws Exception {
+        when(mLockPatternUtils.hasSecureLockScreen()).thenReturn(false);
+        assertEquals(-1, mCommand.exec(new Binder(), in, out, err,
+                new String[] { "set-pattern", "--old", "1234", "4321" },
+                mShellCallback, mResultReceiver));
+        verify(mLockPatternUtils).hasSecureLockScreen();
+        verifyNoMoreInteractions(mLockPatternUtils);
+    }
+
+    @Test
     public void testClear() throws Exception {
         when(mLockPatternUtils.isLockPatternEnabled(mUserId)).thenReturn(true);
         when(mLockPatternUtils.isLockPasswordEnabled(mUserId)).thenReturn(false);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index 94e02bc..0595a5b 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -40,10 +40,10 @@
 import com.android.server.locksettings.SyntheticPasswordManager.AuthenticationToken;
 import com.android.server.locksettings.SyntheticPasswordManager.PasswordData;
 
-import java.util.ArrayList;
-
 import org.mockito.ArgumentCaptor;
 
+import java.util.ArrayList;
+
 
 /**
  * runtest frameworks-services -c com.android.server.locksettings.SyntheticPasswordTests
@@ -448,6 +448,37 @@
         assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
     }
 
+    public void testSetLockCredentialWithTokenFailsWithoutLockScreen() throws Exception {
+        final String password = "password";
+        final String pattern = "123654";
+        final String token = "some-high-entropy-secure-token";
+
+        mHasSecureLockScreen = false;
+        enableSyntheticPassword();
+        long handle = mLocalService.addEscrowToken(token.getBytes(), PRIMARY_USER_ID);
+        assertTrue(mLocalService.isEscrowTokenActive(handle, PRIMARY_USER_ID));
+
+        try {
+            mLocalService.setLockCredentialWithToken(password,
+                    LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, handle, token.getBytes(),
+                    PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+            fail("An exception should have been thrown.");
+        } catch (UnsupportedOperationException e) {
+            // Success - the exception was expected.
+        }
+        assertFalse(mService.havePassword(PRIMARY_USER_ID));
+
+        try {
+            mLocalService.setLockCredentialWithToken(pattern,
+                    LockPatternUtils.CREDENTIAL_TYPE_PATTERN, handle, token.getBytes(),
+                    PASSWORD_QUALITY_ALPHABETIC, PRIMARY_USER_ID);
+            fail("An exception should have been thrown.");
+        } catch (UnsupportedOperationException e) {
+            // Success - the exception was expected.
+        }
+        assertFalse(mService.havePattern(PRIMARY_USER_ID));
+    }
+
     public void testgetHashFactorPrimaryUser() throws RemoteException {
         final String password = "password";
         mService.setLockCredential(password, LockPatternUtils.CREDENTIAL_TYPE_PASSWORD, null,