DO NOT MERGE Adding setup lock setting service
Adds support for setting the lock through setup wizard if there is not
one already set. This includes making it so that this service is
provided ONLY to setup wizard and will give it all the functionality to
match its existing implementation in settings. The removal of the old
settings implementation and dependency on the primary Car support
library will be next.
Fixes: 129551356
Fixes: 131248006
Test: Robolectric tests and manual testing, all passing.
Change-Id: Ic2421e47f99e1dce50c32939dcf72025a6d16dba
diff --git a/Android.mk b/Android.mk
index b27b4d8..1fd2fc6 100644
--- a/Android.mk
+++ b/Android.mk
@@ -40,6 +40,7 @@
car-apps-common \
car-settings-lib \
car-setup-wizard-lib \
+ car-setup-wizard-lib-utils \
setup-wizard-lib-gingerbread-compat \
SettingsLib
@@ -99,6 +100,7 @@
car-apps-common \
car-settings-lib \
car-setup-wizard-lib \
+ car-setup-wizard-lib-utils \
setup-wizard-lib-gingerbread-compat \
SettingsLib
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index f4d2a8e..ee56b77 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -61,6 +61,9 @@
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
+ <permission android:name="com.android.car.settings.SET_INITIAL_LOCK"
+ android:protectionLevel="signature|setup"/>
+
<application
android:icon="@drawable/ic_launcher_settings"
android:theme="@style/CarSettingTheme"
@@ -307,6 +310,10 @@
<service android:name=".bluetooth.BluetoothPairingService" />
+ <service android:name=".setupservice.InitialLockSetupService"
+ android:exported="true"
+ android:permission="com.android.car.settings.SET_INITIAL_LOCK"/>
+
<receiver android:name=".bluetooth.BluetoothPairingRequest">
<intent-filter>
<action android:name="android.bluetooth.device.action.PAIRING_REQUEST" />
diff --git a/src/com/android/car/settings/security/PasswordHelper.java b/src/com/android/car/settings/security/PasswordHelper.java
index 856d42f..daefc84 100644
--- a/src/com/android/car/settings/security/PasswordHelper.java
+++ b/src/com/android/car/settings/security/PasswordHelper.java
@@ -21,6 +21,8 @@
import android.content.Context;
import com.android.car.settings.R;
+import com.android.car.settings.common.Logger;
+import com.android.car.setupwizardlib.InitialLockSetupConstants.ValidateLockFlags;
import java.util.LinkedList;
import java.util.List;
@@ -30,22 +32,20 @@
*/
public class PasswordHelper {
public static final String EXTRA_CURRENT_SCREEN_LOCK = "extra_current_screen_lock";
-
/**
* Required minimum length of PIN or password.
*/
- static final int MIN_LENGTH = 4;
-
+ public static final int MIN_LENGTH = 4;
// Error code returned from validate(String).
static final int NO_ERROR = 0;
static final int CONTAINS_INVALID_CHARACTERS = 1;
static final int TOO_SHORT = 1 << 1;
static final int CONTAINS_NON_DIGITS = 1 << 2;
static final int CONTAINS_SEQUENTIAL_DIGITS = 1 << 3;
-
+ private static final Logger LOG = new Logger(PasswordHelper.class);
private final boolean mIsPin;
- PasswordHelper(boolean isPin) {
+ public PasswordHelper(boolean isPin) {
mIsPin = isPin;
}
@@ -70,9 +70,21 @@
}
/**
+ * Validates PIN/Password using the setup wizard {@link ValidateLockFlags}.
+ *
+ * @param password The password to validate.
+ * @return The error code where 0 is no error.
+ */
+ public int validateSetupWizard(byte[] password) {
+ return mIsPin ? translateSettingsToSuwError(validatePin(password))
+ : translateSettingsToSuwError(validatePassword(password));
+ }
+
+ /**
* Converts error code from validatePassword to an array of messages describing the errors with
* important message comes first. The messages are concatenated with a space in between.
* Please make sure each message ends with a period.
+ *
* @param errorCode the code returned by {@link #validatePassword(byte[]) validatePassword}
*/
public List<String> convertErrorCodeToMessages(Context context, int errorCode) {
@@ -99,6 +111,27 @@
return errorCode;
}
+ private int translateSettingsToSuwError(int error) {
+ int output = 0;
+ if ((error & CONTAINS_NON_DIGITS) > 0) {
+ LOG.v("CONTAINS_NON_DIGITS");
+ output |= ValidateLockFlags.INVALID_BAD_SYMBOLS;
+ }
+ if ((error & CONTAINS_INVALID_CHARACTERS) > 0) {
+ LOG.v("INVALID_CHAR");
+ output |= ValidateLockFlags.INVALID_BAD_SYMBOLS;
+ }
+ if ((error & TOO_SHORT) > 0) {
+ LOG.v("TOO_SHORT");
+ output |= ValidateLockFlags.INVALID_LENGTH;
+ }
+ if ((error & CONTAINS_SEQUENTIAL_DIGITS) > 0) {
+ LOG.v("SEQUENTIAL_DIGITS");
+ output |= ValidateLockFlags.INVALID_LACKS_COMPLEXITY;
+ }
+ return output;
+ }
+
private int validatePin(byte[] pin) {
int errorCode = NO_ERROR;
PasswordMetrics metrics = PasswordMetrics.computeForPassword(pin);
diff --git a/src/com/android/car/settings/setupservice/InitialLockSetupService.java b/src/com/android/car/settings/setupservice/InitialLockSetupService.java
new file mode 100644
index 0000000..7ea8bc8
--- /dev/null
+++ b/src/com/android/car/settings/setupservice/InitialLockSetupService.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.setupservice;
+
+
+import android.app.Service;
+import android.app.admin.DevicePolicyManager;
+import android.car.userlib.CarUserManagerHelper;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.security.PasswordHelper;
+import com.android.car.setupwizardlib.IInitialLockSetupService;
+import com.android.car.setupwizardlib.InitialLockSetupConstants;
+import com.android.car.setupwizardlib.InitialLockSetupConstants.LockTypes;
+import com.android.car.setupwizardlib.InitialLockSetupConstants.SetLockCodes;
+import com.android.car.setupwizardlib.InitialLockSetupConstants.ValidateLockFlags;
+import com.android.car.setupwizardlib.InitialLockSetupHelper;
+import com.android.car.setupwizardlib.LockConfig;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Service that is used by Setup Wizard (exclusively) to set the initial lock screen.
+ *
+ * <p>This service provides functionality to get the lock config state, check if a password is
+ * valid based on the Settings defined password criteria, and save a lock if there is not one
+ * already saved. The interface for these operations is found in the {@link
+ * IInitialLockSetupService}.
+ */
+public class InitialLockSetupService extends Service {
+
+ private static final Logger LOG = new Logger(InitialLockSetupService.class);
+ private static final String SET_LOCK_PERMISSION = "com.android.car.settings.SET_INITIAL_LOCK";
+
+ private final InitialLockSetupServiceImpl mIInitialLockSetupService =
+ new InitialLockSetupServiceImpl();
+
+ /**
+ * Will return an {@link IBinder} for the service unless either the caller does not have the
+ * appropriate permissions or a lock has already been set on the device. In this case, the
+ * service will return {@code null}.
+ */
+ @Override
+ public IBinder onBind(Intent intent) {
+ LOG.v("onBind");
+ if (checkCallingOrSelfPermission(SET_LOCK_PERMISSION)
+ != PackageManager.PERMISSION_GRANTED) {
+ // Check permission as a failsafe.
+ return null;
+ }
+ int userId = new CarUserManagerHelper(getApplicationContext()).getCurrentProcessUserId();
+ LockPatternUtils lockPatternUtils = new LockPatternUtils(getApplicationContext());
+ // Deny binding if there is an existing lock.
+ if (lockPatternUtils.getKeyguardStoredPasswordQuality(userId)
+ != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
+ LOG.v("Rejecting binding, lock exists");
+ return null;
+ }
+ return mIInitialLockSetupService;
+ }
+
+ // Translates the byte[] pattern received into the List<LockPatternView.Cell> that is
+ // recognized by LockPatternUtils.
+ private List<LockPatternView.Cell> toSettingsPattern(byte[] pattern) {
+ List<LockPatternView.Cell> outputList = new ArrayList<>();
+ for (int i = 0; i < pattern.length; i++) {
+ outputList.add(LockPatternView.Cell.of(
+ InitialLockSetupHelper.getPatternCellRowFromByte(pattern[i]),
+ InitialLockSetupHelper.getPatternCellColumnFromByte(pattern[i])));
+ }
+ return outputList;
+ }
+
+ // Implementation of the service binder interface.
+ private class InitialLockSetupServiceImpl extends IInitialLockSetupService.Stub {
+
+ @Override
+ public int getServiceVersion() {
+ return InitialLockSetupConstants.LIBRARY_VERSION;
+ }
+
+ @Override
+ public LockConfig getLockConfig(@LockTypes int lockType) {
+ // All lock types currently are configured the same.
+ switch (lockType) {
+ case LockTypes.PASSWORD:
+ // fall through
+ case LockTypes.PIN:
+ // fall through
+ case LockTypes.PATTERN:
+ return new LockConfig(/* enabled= */true, PasswordHelper.MIN_LENGTH);
+ }
+ return null;
+ }
+
+ @Override
+ @ValidateLockFlags
+ public int checkValidLock(@LockTypes int lockType, byte[] password) {
+ PasswordHelper passwordHelper;
+ switch (lockType) {
+ case LockTypes.PASSWORD:
+ passwordHelper = new PasswordHelper(/* isPin= */ false);
+ return passwordHelper.validateSetupWizard(password);
+ case LockTypes.PIN:
+ passwordHelper = new PasswordHelper(/* isPin= */ true);
+ return passwordHelper.validateSetupWizard(password);
+ case LockTypes.PATTERN:
+ return password.length >= LockPatternUtils.MIN_LOCK_PATTERN_SIZE
+ ? 0 : ValidateLockFlags.INVALID_LENGTH;
+ default:
+ LOG.e("other lock type, returning generic error");
+ return ValidateLockFlags.INVALID_GENERIC;
+ }
+ }
+
+ @Override
+ @SetLockCodes
+ public int setLock(@LockTypes int lockType, byte[] password) {
+ int userId = new CarUserManagerHelper(
+ InitialLockSetupService.this.getApplicationContext())
+ .getCurrentProcessUserId();
+ LockPatternUtils lockPatternUtils = new LockPatternUtils(
+ InitialLockSetupService.this.getApplicationContext());
+ int currentPassword = lockPatternUtils.getKeyguardStoredPasswordQuality(userId);
+ if (currentPassword != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
+ LOG.v("Password already set, rejecting call to setLock");
+ return SetLockCodes.FAIL_LOCK_EXISTS;
+ }
+ if (!InitialLockSetupHelper.isValidLockResultCode(checkValidLock(lockType, password))) {
+ LOG.v("Password is not valid, rejecting call to setLock");
+ return SetLockCodes.FAIL_LOCK_INVALID;
+ }
+
+ boolean success = false;
+ try {
+ switch (lockType) {
+ case LockTypes.PASSWORD:
+ // Need to remove setup wizard lib byte array encoding and use the
+ // LockPatternUtils encoding.
+ byte[] encodedPassword = LockPatternUtils.charSequenceToByteArray(
+ InitialLockSetupHelper.byteArrayToCharSequence(password));
+ lockPatternUtils.saveLockPassword(encodedPassword,
+ /* savedPassword= */ null,
+ DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, userId);
+ success = true;
+ break;
+ case LockTypes.PIN:
+ // Need to remove setup wizard lib byte array encoding and use the
+ // LockPatternUtils encoding.
+ byte[] encodedPin = LockPatternUtils.charSequenceToByteArray(
+ InitialLockSetupHelper.byteArrayToCharSequence(password));
+ lockPatternUtils.saveLockPassword(encodedPin, /* savedPassword= */ null,
+ DevicePolicyManager.PASSWORD_QUALITY_NUMERIC, userId);
+ success = true;
+ break;
+ case LockTypes.PATTERN:
+ // Need to remove the setup wizard lib pattern encoding and use the
+ // LockPatternUtils pattern format.
+ List<LockPatternView.Cell> pattern = toSettingsPattern(password);
+ lockPatternUtils.saveLockPattern(pattern, userId);
+ pattern.clear();
+ success = true;
+ break;
+ default:
+ LOG.e("Unknown lock type, returning a failure");
+ }
+ } catch (Exception e) {
+ LOG.e("Save lock exception", e);
+ success = false;
+ }
+ Arrays.fill(password, (byte) 0);
+ return success ? SetLockCodes.SUCCESS : SetLockCodes.FAIL_LOCK_GENERIC;
+ }
+ }
+}
diff --git a/tests/robotests/AndroidManifest.xml b/tests/robotests/AndroidManifest.xml
index 77fd39c..efe1f69 100644
--- a/tests/robotests/AndroidManifest.xml
+++ b/tests/robotests/AndroidManifest.xml
@@ -19,5 +19,4 @@
coreApp="true"
package="com.android.car.settings.robotests">
<application/>
-
</manifest>
diff --git a/tests/robotests/src/com/android/car/settings/CarSettingsRobolectricTestRunner.java b/tests/robotests/src/com/android/car/settings/CarSettingsRobolectricTestRunner.java
index a5ac933..ba9b84d 100644
--- a/tests/robotests/src/com/android/car/settings/CarSettingsRobolectricTestRunner.java
+++ b/tests/robotests/src/com/android/car/settings/CarSettingsRobolectricTestRunner.java
@@ -101,7 +101,8 @@
// By adding any resources from libraries we need to the AndroidManifest, we can access
// them from within the parallel universe's resource loader.
- return new AndroidManifest(Fs.fromURL(manifestPath), Fs.fromURL(resDir),
+ AndroidManifest androidManifest = new AndroidManifest(Fs.fromURL(manifestPath),
+ Fs.fromURL(resDir),
Fs.fromURL(assetsDir)) {
@Override
public List<ResourcePath> getIncludedResourcePaths() {
@@ -123,6 +124,10 @@
return paths;
}
};
+ // Need to remove this permission since this version of robolectric does not support
+ // multiple protection levels.
+ androidManifest.getPermissions().remove("com.android.car.settings.SET_INITIAL_LOCK");
+ return androidManifest;
} catch (MalformedURLException e) {
throw new RuntimeException("CarSettingsobolectricTestRunner failure", e);
}
diff --git a/tests/robotests/src/com/android/car/settings/security/InitialLockSetupServiceTest.java b/tests/robotests/src/com/android/car/settings/security/InitialLockSetupServiceTest.java
new file mode 100644
index 0000000..d05a33d
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/security/InitialLockSetupServiceTest.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.settings.security;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.os.RemoteException;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.setupservice.InitialLockSetupService;
+import com.android.car.settings.testutils.ShadowLockPatternUtils;
+import com.android.car.setupwizardlib.IInitialLockSetupService;
+import com.android.car.setupwizardlib.InitialLockSetupConstants.LockTypes;
+import com.android.car.setupwizardlib.InitialLockSetupConstants.SetLockCodes;
+import com.android.car.setupwizardlib.InitialLockSetupConstants.ValidateLockFlags;
+import com.android.car.setupwizardlib.InitialLockSetupHelper;
+import com.android.car.setupwizardlib.LockConfig;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.Robolectric;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowContextWrapper;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Tests that the {@link InitialLockSetupService} properly handles connections and lock requests.
+ */
+@Config(shadows = ShadowLockPatternUtils.class)
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class InitialLockSetupServiceTest {
+
+ private static final String LOCK_PERMISSION = "com.android.car.settings.SET_INITIAL_LOCK";
+
+ private InitialLockSetupService mInitialLockSetupService;
+ private Context mContext;
+
+ @Before
+ public void setupService() {
+ ShadowLockPatternUtils.reset();
+ mInitialLockSetupService = Robolectric.buildService(InitialLockSetupService.class)
+ .create()
+ .get();
+ mContext = RuntimeEnvironment.application;
+ ShadowContextWrapper shadowContextWrapper = Shadows.shadowOf((ContextWrapper) mContext);
+ shadowContextWrapper.grantPermissions(LOCK_PERMISSION);
+ }
+
+ @Test
+ public void testBindReturnsNull_ifLockSet() {
+ ShadowLockPatternUtils.setPasswordQuality(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC);
+ assertThat(mInitialLockSetupService.onBind(new Intent())).isNull();
+ }
+
+ @Test
+ public void testBindReturnsInstanceOfServiceInterface_ifLockNotSet() throws RemoteException {
+ assertThat(mInitialLockSetupService.onBind(
+ new Intent()) instanceof IInitialLockSetupService.Stub).isTrue();
+ }
+
+ @Test
+ public void testGetLockConfig_returnsCorrectConfig() throws RemoteException {
+ IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
+ mInitialLockSetupService.onBind(new Intent()));
+ LockConfig pinConfig = service.getLockConfig(LockTypes.PIN);
+ assertThat(pinConfig.enabled).isTrue();
+ assertThat(pinConfig.minLockLength).isEqualTo(LockPatternUtils.MIN_LOCK_PASSWORD_SIZE);
+ LockConfig patternConfig = service.getLockConfig(LockTypes.PATTERN);
+ assertThat(patternConfig.enabled).isTrue();
+ assertThat(patternConfig.minLockLength).isEqualTo(LockPatternUtils.MIN_LOCK_PATTERN_SIZE);
+ LockConfig passwordConfig = service.getLockConfig(LockTypes.PASSWORD);
+ assertThat(passwordConfig.enabled).isTrue();
+ assertThat(passwordConfig.minLockLength).isEqualTo(LockPatternUtils.MIN_LOCK_PASSWORD_SIZE);
+ }
+
+ @Test
+ public void testCheckValidLock_tooShort() throws RemoteException {
+ IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
+ mInitialLockSetupService.onBind(new Intent()));
+ int result = service.checkValidLock(LockTypes.PASSWORD, "hi".getBytes());
+ assertThat(result & ValidateLockFlags.INVALID_LENGTH)
+ .isEqualTo(ValidateLockFlags.INVALID_LENGTH);
+ }
+
+ @Test
+ public void testCheckValidLock_longEnough() throws RemoteException {
+ IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
+ mInitialLockSetupService.onBind(new Intent()));
+ int result = service.checkValidLock(LockTypes.PASSWORD, "password".getBytes());
+ assertThat(result & ValidateLockFlags.INVALID_LENGTH)
+ .isNotEqualTo(ValidateLockFlags.INVALID_LENGTH);
+ }
+
+ @Test
+ public void testCheckValidLockPin_withLetters() throws RemoteException {
+ IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
+ mInitialLockSetupService.onBind(new Intent()));
+ int result = service.checkValidLock(LockTypes.PIN, "12a3".getBytes());
+ assertThat(result & ValidateLockFlags.INVALID_BAD_SYMBOLS)
+ .isEqualTo(ValidateLockFlags.INVALID_BAD_SYMBOLS);
+ }
+
+ @Test
+ public void testCheckValidLockPattern_tooShort() throws RemoteException {
+ IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
+ mInitialLockSetupService.onBind(new Intent()));
+ byte[] pattern = new byte[LockPatternUtils.MIN_LOCK_PATTERN_SIZE - 1];
+ for (int i = 0; i < pattern.length; i++) {
+ pattern[i] = (byte) i;
+ }
+ int result = service.checkValidLock(LockTypes.PATTERN, pattern);
+ assertThat(result & ValidateLockFlags.INVALID_LENGTH)
+ .isEqualTo(ValidateLockFlags.INVALID_LENGTH);
+ }
+
+ @Test
+ public void testCheckValidLockPattern_longEnough() throws RemoteException {
+ IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
+ mInitialLockSetupService.onBind(new Intent()));
+ byte[] pattern = new byte[LockPatternUtils.MIN_LOCK_PATTERN_SIZE + 1];
+ for (int i = 0; i < pattern.length; i++) {
+ pattern[i] = (byte) i;
+ }
+ int result = service.checkValidLock(LockTypes.PATTERN, pattern);
+ assertThat(result & ValidateLockFlags.INVALID_LENGTH)
+ .isNotEqualTo(ValidateLockFlags.INVALID_LENGTH);
+ }
+
+ @Test
+ public void testSetLockPassword_doesNotWorkWithExistingPassword() throws RemoteException {
+ IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
+ mInitialLockSetupService.onBind(new Intent()));
+ ShadowLockPatternUtils.setPasswordQuality(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC);
+ int result = service.setLock(LockTypes.PASSWORD, "password".getBytes());
+ assertThat(result).isEqualTo(SetLockCodes.FAIL_LOCK_EXISTS);
+ }
+
+ @Test
+ public void testSetLockPassword_doesNotWorkWithInvalidPassword() throws RemoteException {
+ IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
+ mInitialLockSetupService.onBind(new Intent()));
+ int result = service.setLock(LockTypes.PASSWORD, "hi".getBytes());
+ assertThat(result).isEqualTo(SetLockCodes.FAIL_LOCK_INVALID);
+ }
+
+ @Test
+ public void testSetLockPassword_setsDevicePassword() throws RemoteException {
+ IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
+ mInitialLockSetupService.onBind(new Intent()));
+ byte[] password = "password".getBytes();
+ // Need copy since password is cleared.
+ byte[] expectedPassword = Arrays.copyOf(password, password.length);
+ int result = service.setLock(LockTypes.PASSWORD, password);
+ assertThat(result).isEqualTo(SetLockCodes.SUCCESS);
+ assertThat(Arrays.equals(ShadowLockPatternUtils.getSavedPassword(),
+ expectedPassword)).isTrue();
+ }
+
+ @Test
+ public void testSetLockPin_setsDevicePin() throws RemoteException {
+ IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
+ mInitialLockSetupService.onBind(new Intent()));
+ byte[] password = "1580".getBytes();
+ byte[] expectedPassword = Arrays.copyOf(password, password.length);
+ int result = service.setLock(LockTypes.PIN, password);
+ assertThat(result).isEqualTo(SetLockCodes.SUCCESS);
+ assertThat(Arrays.equals(ShadowLockPatternUtils.getSavedPassword(),
+ expectedPassword)).isTrue();
+ }
+
+ @Test
+ public void testSetLockPattern_setsDevicePattern() throws RemoteException {
+ IInitialLockSetupService service = IInitialLockSetupService.Stub.asInterface(
+ mInitialLockSetupService.onBind(new Intent()));
+ List<LockPatternView.Cell> pattern = new ArrayList<>();
+ pattern.add(LockPatternView.Cell.of(0, 0));
+ pattern.add(LockPatternView.Cell.of(1, 0));
+ pattern.add(LockPatternView.Cell.of(2, 0));
+ pattern.add(LockPatternView.Cell.of(0, 1));
+ byte[] patternBytes = new byte[pattern.size()];
+ for (int i = 0; i < patternBytes.length; i++) {
+ LockPatternView.Cell cell = pattern.get(i);
+ patternBytes[i] = InitialLockSetupHelper.getByteFromPatternCell(cell.getRow(),
+ cell.getColumn());
+ }
+ int result = service.setLock(LockTypes.PATTERN, patternBytes);
+ assertThat(result).isEqualTo(SetLockCodes.SUCCESS);
+ List<LockPatternView.Cell> savedPattern = ShadowLockPatternUtils.getSavedPattern();
+ assertThat(savedPattern).containsExactlyElementsIn(pattern);
+
+ }
+
+ @Test
+ public void testBindFails_ifNoPermissionGranted() {
+ ShadowContextWrapper shadowContextWrapper = Shadows.shadowOf((ContextWrapper) mContext);
+ shadowContextWrapper.denyPermissions(LOCK_PERMISSION);
+ assertThat(mInitialLockSetupService.onBind(new Intent())).isNull();
+ }
+
+}
diff --git a/tests/robotests/src/com/android/car/settings/security/PasswordHelperTest.java b/tests/robotests/src/com/android/car/settings/security/PasswordHelperTest.java
index bbc468a..567a009 100644
--- a/tests/robotests/src/com/android/car/settings/security/PasswordHelperTest.java
+++ b/tests/robotests/src/com/android/car/settings/security/PasswordHelperTest.java
@@ -19,6 +19,7 @@
import static com.google.common.truth.Truth.assertThat;
import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.setupwizardlib.InitialLockSetupConstants.ValidateLockFlags;
import org.junit.Before;
import org.junit.Test;
@@ -31,12 +32,12 @@
public class PasswordHelperTest {
private PasswordHelper mPasswordHelper;
- private PasswordHelper mPinPresenter;
+ private PasswordHelper mPinHelper;
@Before
public void initObjects() {
- mPasswordHelper = new PasswordHelper(false);
- mPinPresenter = new PasswordHelper(true);
+ mPasswordHelper = new PasswordHelper(/* isPin= */ false);
+ mPinHelper = new PasswordHelper(/* isPin= */ true);
}
/**
@@ -51,7 +52,7 @@
}
/**
- * A test to check validate works when alphanumeric passwor contains white space.
+ * A test to check validate works when alphanumeric password contains white space.
*/
@Test
public void testValidatePasswordWhiteSpace() {
@@ -77,7 +78,7 @@
@Test
public void testValidatePinContainingNonDigits() {
byte[] password = "1a34".getBytes();
- assertThat(mPinPresenter.validate(password))
+ assertThat(mPinHelper.validate(password))
.isEqualTo(PasswordHelper.CONTAINS_NON_DIGITS);
}
@@ -87,7 +88,71 @@
@Test
public void testValidatePinWithTooFewDigits() {
byte[] password = "12".getBytes();
- assertThat(mPinPresenter.validate(password))
+ assertThat(mPinHelper.validate(password))
.isEqualTo(PasswordHelper.TOO_SHORT);
}
+
+ /**
+ * A test to check that validate will work as expected for setup wizard passwords that are
+ * too short.
+ */
+ @Test
+ public void testValidatePasswordTooShort_setupWizard() {
+ byte[] password = "lov".getBytes();
+ assertThat(mPasswordHelper.validateSetupWizard(password) & ValidateLockFlags.INVALID_LENGTH)
+ .isEqualTo(ValidateLockFlags.INVALID_LENGTH);
+ }
+
+ /**
+ * A test to check that validate works when setup wizard alphanumeric passwords contain white
+ * space.
+ */
+ @Test
+ public void testValidatePasswordWhiteSpace_setupWizard() {
+ byte[] password = "pass wd".getBytes();
+ assertThat(mPasswordHelper.validateSetupWizard(password)).isEqualTo(0);
+ }
+
+ /**
+ * A test to check validate works as expected for setup wizard alphanumeric password
+ * that contains an invalid character.
+ */
+ @Test
+ public void testValidatePasswordNonAscii_setupWizard() {
+ byte[] password = "1passwýd".getBytes();
+ assertThat(mPasswordHelper.validateSetupWizard(password)
+ & ValidateLockFlags.INVALID_BAD_SYMBOLS)
+ .isEqualTo(ValidateLockFlags.INVALID_BAD_SYMBOLS);
+ }
+
+ /**
+ * A test to check validate works as expected for setup wizard pin that contains non digits.
+ */
+ @Test
+ public void testValidatePinContainingNonDigits_setupWizard() {
+ byte[] password = "1a34".getBytes();
+ assertThat(mPinHelper.validateSetupWizard(password)
+ & ValidateLockFlags.INVALID_BAD_SYMBOLS)
+ .isEqualTo(ValidateLockFlags.INVALID_BAD_SYMBOLS);
+ }
+
+ /**
+ * A test to check validate works as expected for setup wizard pin with too few digits.
+ */
+ @Test
+ public void testValidatePinWithTooFewDigits_setupWizard() {
+ byte[] password = "12".getBytes();
+ assertThat(mPinHelper.validateSetupWizard(password) & ValidateLockFlags.INVALID_LENGTH)
+ .isEqualTo(ValidateLockFlags.INVALID_LENGTH);
+ }
+
+ /**
+ * A test to check that validate works as expected for a valid setup wizard pin.
+ */
+ @Test
+ public void testValidatePinWithValidPin_setupWizard() {
+ byte[] password = "1234".getBytes();
+ assertThat(mPinHelper.validateSetupWizard(password)).isEqualTo(0);
+ }
+
}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowLockPatternUtils.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowLockPatternUtils.java
index 37fe981..8f819c3 100644
--- a/tests/robotests/src/com/android/car/settings/testutils/ShadowLockPatternUtils.java
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowLockPatternUtils.java
@@ -16,16 +16,28 @@
package com.android.car.settings.testutils;
+import android.app.admin.DevicePolicyManager;
+
import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternView;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.Resetter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Shadow for LockPatternUtils.
+ */
@Implements(LockPatternUtils.class)
public class ShadowLockPatternUtils {
private static LockPatternUtils sInstance;
+ private static int sPasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+ private static byte[] sSavedPassword;
+ private static List<LockPatternView.Cell> sSavedPattern;
public static void setInstance(LockPatternUtils lockPatternUtils) {
sInstance = lockPatternUtils;
@@ -34,10 +46,51 @@
@Resetter
public static void reset() {
sInstance = null;
+ sPasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+ sSavedPassword = null;
+ sSavedPattern = null;
+ }
+
+ /**
+ * Sets the current password quality that is returned by
+ * {@link LockPatternUtils#getKeyguardStoredPasswordQuality}.
+ */
+ public static void setPasswordQuality(int passwordQuality) {
+ sPasswordQuality = passwordQuality;
+ }
+
+ /**
+ * Returns the password saved by a call to {@link LockPatternUtils#saveLockPassword}.
+ */
+ public static byte[] getSavedPassword() {
+ return sSavedPassword;
+ }
+
+ /**
+ * Returns the pattern saved by a call to {@link LockPatternUtils#saveLockPattern}.
+ */
+ public static List<LockPatternView.Cell> getSavedPattern() {
+ return sSavedPattern;
}
@Implementation
protected void clearLock(byte[] savedCredential, int userHandle) {
sInstance.clearLock(savedCredential, userHandle);
}
+
+ @Implementation
+ public int getKeyguardStoredPasswordQuality(int userHandle) {
+ return sPasswordQuality;
+ }
+
+ @Implementation
+ public void saveLockPassword(byte[] password, byte[] savedPassword, int requestedQuality,
+ int userHandler) {
+ sSavedPassword = password;
+ }
+
+ @Implementation
+ public void saveLockPattern(List<LockPatternView.Cell> pattern, int userId) {
+ sSavedPattern = new ArrayList<>(pattern);
+ }
}