[Auto Pin Confirm]: Changes to allow PIN auto confirm feature to be controllable by the user

- Add SwitchPreference to allow user to control the pin auto confirm feature
- Add Checkbox option during the PIN setup in Security app
- Disable the opt-in checkbox during SUW entry point for PIN setup
- Update SwitchPreference availability appropriately according to current PIN length
- Update the pin_auto_confirm setting appropriately according to state of switchPreference or checkbox state (in PIN setup)
- Update the error-message when PIN Too short to let user know six digit is recommended

Bug: 262926000
Bug: 262936383
Bug: 262934702
Bug: 262935305
Test: Manual Test
Test: atest SettingsRoboTests
Change-Id: Ib9e09bd5ce44652158e77f80e8be19c4dd50f3bf
This commit is contained in:
Avinash Vadlamudi
2023-01-20 18:42:46 +00:00
parent 51357872de
commit 4c8ad8f911
12 changed files with 551 additions and 4 deletions

View File

@@ -0,0 +1,24 @@
<!--
~ Copyright (C) 2023 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_checked="true"
android:drawable="@drawable/ic_check_circle_filled_24dp" />
<item
android:state_checked="false"
android:drawable="@drawable/ic_circle_outline_24dp" />
</selector>

View File

@@ -0,0 +1,27 @@
<!--
~ Copyright (C) 2023 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?androidprv:attr/colorAccentPrimaryVariant">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10c5.52,0 10,-4.48 10,-10S17.52,2 12,2zM10.59,16.6l-4.24,-4.24l1.41,-1.41l2.83,2.83l5.66,-5.66l1.41,1.41L10.59,16.6z"/>
</vector>

View File

@@ -0,0 +1,26 @@
<!--
~ Copyright (C) 2023 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
</vector>

View File

@@ -67,8 +67,31 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<CheckBox
android:id="@+id/auto_pin_confirm_enabler"
android:layout_marginTop="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center"
android:paddingLeft="14dp"
android:text="@string/auto_pin_confirm_user_message"
android:textSize="16sp"
android:button="@drawable/checkbox_circle_shape"
android:visibility="gone" />
</LinearLayout>
<TextView
android:id="@+id/auto_pin_confirm_security_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:gravity="center"
android:text="@string/auto_pin_confirm_opt_in_security_message"
android:textSize="16sp"
android:visibility="gone" />
<Button
android:id="@+id/screen_lock_options"
style="@style/SudGlifButton.Tertiary"

View File

@@ -1090,6 +1090,15 @@
<!-- Title for dialog in screen lock settings, allowing users to choose other types of screen locks. [CHAR LIMIT=40] -->
<string name="setup_lock_settings_options_dialog_title">Screen lock options</string>
<!-- Title of the lock screen auto pin confirm setting. [CHAR LIMIT=NONE] -->
<string name="lock_screen_auto_pin_confirm_title">Auto-confirm unlock</string>
<!-- Summary of the lock screen auto pin confirm setting. [CHAR LIMIT=NONE] -->
<string name="lock_screen_auto_pin_confirm_summary">Unlock automatically if you input a correct PIN of 6 digits or more. This is slightly less secure than tapping Enter to confirm.</string>
<!-- Message shown to check auto pin confirmation feature when the user is updating the PIN. [CHAR LIMIT=NONE] -->
<string name="auto_pin_confirm_user_message">Auto-confirm correct PIN</string>
<!-- Message shown to explain the security concern if a user opts-in to the auto-pin feature. [CHAR LIMIT=NONE] -->
<string name="auto_pin_confirm_opt_in_security_message">Confirming your PIN by tapping Enter is more secure than using auto-confirm</string>
<!-- Main Security lock settings --><skip />
<!-- Title for PreferenceScreen to launch picker for security method when there is none [CHAR LIMIT=22] -->
<string name="unlock_set_unlock_launch_picker_title">Screen lock</string>
@@ -1250,6 +1259,12 @@
other {PIN must be at least # digits}
}</string>
<!-- Hint shown in dialog screen when PIN is too short with Additional text indicating minAutoConfirmLen(eg: 6) digits PIN offer additional security -->
<string name="lockpassword_pin_too_short_autoConfirm_extra_message">{count, plural,
=1 {PIN must contain at least # digit, but a {minAutoConfirmLen}-digit PIN is recommended for added security}
other {PIN must be at least # digits, but a {minAutoConfirmLen}-digit PIN is recommended for added security}
}</string>
<!-- Error shown in popup when password is too long -->
<string name="lockpassword_password_too_long">{count, plural,
=1 {Must be fewer than # character}

View File

@@ -27,6 +27,12 @@
android:key="visiblepattern"
android:title="@string/lockpattern_settings_enable_visible_pattern_title" />
<!-- available in pin -->
<SwitchPreference
android:key="auto_pin_confirm"
android:title="@string/lock_screen_auto_pin_confirm_title"
android:summary="@string/lock_screen_auto_pin_confirm_summary" />
<!-- available in pin/pattern/password -->
<com.android.settings.display.TimeoutListPreference
android:key="lock_after_timeout"

View File

@@ -71,6 +71,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.CheckBox;
import android.widget.ImeAwareEditText;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
@@ -101,7 +102,9 @@ import com.google.android.setupdesign.util.ThemeHelper;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ChooseLockPassword extends SettingsActivity {
private static final String TAG = "ChooseLockPassword";
@@ -223,6 +226,8 @@ public class ChooseLockPassword extends SettingsActivity {
private static final String KEY_CURRENT_CREDENTIAL = "current_credential";
private static final String FRAGMENT_TAG_SAVE_AND_FINISH = "save_and_finish_worker";
private static final int MIN_AUTO_PIN_REQUIREMENT_LENGTH = 6;
private LockscreenCredential mCurrentCredential;
private LockscreenCredential mChosenPassword;
private boolean mRequestGatekeeperPassword;
@@ -255,6 +260,9 @@ public class ChooseLockPassword extends SettingsActivity {
protected FooterButton mSkipOrClearButton;
private FooterButton mNextButton;
private TextView mMessage;
protected CheckBox mAutoPinConfirmOption;
protected TextView mAutoConfirmSecurityMessage;
protected boolean mIsAutoPinConfirmOptionSetManually;
private TextChangedHandler mTextChangedHandler;
@@ -515,6 +523,16 @@ public class ChooseLockPassword extends SettingsActivity {
mPasswordEntry.requestFocus();
mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry);
// Fetch the AutoPinConfirmOption
mAutoPinConfirmOption = view.findViewById(R.id.auto_pin_confirm_enabler);
mAutoConfirmSecurityMessage = view.findViewById(R.id.auto_pin_confirm_security_message);
mIsAutoPinConfirmOptionSetManually = false;
setOnAutoConfirmOptionClickListener();
if (mAutoPinConfirmOption != null) {
mAutoPinConfirmOption.setVisibility(View.GONE);
mAutoPinConfirmOption.setChecked(false);
}
final Activity activity = getActivity();
int currentType = mPasswordEntry.getInputType();
@@ -808,10 +826,22 @@ public class ChooseLockPassword extends SettingsActivity {
R.string.lockpassword_password_requires_nonnumerical));
break;
case TOO_SHORT:
messages.add(StringUtil.getIcuPluralsString(getContext(), error.requirement,
String message = StringUtil.getIcuPluralsString(getContext(),
error.requirement,
mIsAlphaMode
? R.string.lockpassword_password_too_short
: R.string.lockpassword_pin_too_short));
: R.string.lockpassword_pin_too_short);
if (mLockPatternUtils.isAutoPinConfirmFeatureAvailable()
&& !mIsAlphaMode
&& error.requirement < MIN_AUTO_PIN_REQUIREMENT_LENGTH) {
Map<String, Object> arguments = new HashMap<>();
arguments.put("count", error.requirement);
arguments.put("minAutoConfirmLen", MIN_AUTO_PIN_REQUIREMENT_LENGTH);
message = StringUtil.getIcuPluralsString(getContext(),
arguments,
R.string.lockpassword_pin_too_short_autoConfirm_extra_message);
}
messages.add(message);
break;
case TOO_SHORT_WHEN_ALL_NUMERIC:
messages.add(
@@ -864,6 +894,8 @@ public class ChooseLockPassword extends SettingsActivity {
String[] messages = convertErrorCodeToMessages();
// Update the fulfillment of requirements.
mPasswordRequirementAdapter.setRequirements(messages);
// set the visibility of pin_auto_confirm option accordingly
setAutoPinConfirmOption(passwordCompliant, length);
// Enable/Disable the next button accordingly.
setNextEnabled(passwordCompliant);
} else {
@@ -896,6 +928,36 @@ public class ChooseLockPassword extends SettingsActivity {
return visibleOrGone ? View.VISIBLE : View.GONE;
}
private void setAutoPinConfirmOption(boolean enabled, int length) {
if (!mLockPatternUtils.isAutoPinConfirmFeatureAvailable()
|| mAutoPinConfirmOption == null) {
return;
}
if (enabled && !mIsAlphaMode && isAutoPinConfirmPossible(length)) {
mAutoPinConfirmOption.setVisibility(View.VISIBLE);
mAutoConfirmSecurityMessage.setVisibility(View.VISIBLE);
if (!mIsAutoPinConfirmOptionSetManually) {
mAutoPinConfirmOption.setChecked(length == MIN_AUTO_PIN_REQUIREMENT_LENGTH);
}
} else {
mAutoPinConfirmOption.setVisibility(View.GONE);
mAutoConfirmSecurityMessage.setVisibility(View.GONE);
mAutoPinConfirmOption.setChecked(false);
}
}
private boolean isAutoPinConfirmPossible(int currentPinLength) {
return currentPinLength >= MIN_AUTO_PIN_REQUIREMENT_LENGTH;
}
private void setOnAutoConfirmOptionClickListener() {
if (mAutoPinConfirmOption != null) {
mAutoPinConfirmOption.setOnClickListener((v) -> {
mIsAutoPinConfirmOptionSetManually = true;
});
}
}
private void setHeaderText(String text) {
// Only set the text if it is different than the existing one to avoid announcing again.
if (!TextUtils.isEmpty(mLayout.getHeaderText())
@@ -951,6 +1013,10 @@ public class ChooseLockPassword extends SettingsActivity {
}
mSaveAndFinishWorker.start(mLockPatternUtils, mRequestGatekeeperPassword,
mChosenPassword, mCurrentCredential, mUserId);
// update the pin_auto_confirm setting accordingly.
mLockPatternUtils.setAutoPinConfirm(
(mAutoPinConfirmOption != null && mAutoPinConfirmOption.isChecked()),
mUserId);
}
@Override

View File

@@ -170,6 +170,12 @@ public class SetupChooseLockPassword extends ChooseLockPassword {
mOptionsButton.setVisibility(
mUiStage == Stage.Introduction ? View.VISIBLE : View.GONE);
}
// Visibility of auto pin confirm opt-in/out option should always be invisible.
if (mAutoPinConfirmOption != null) {
mAutoPinConfirmOption.setVisibility(View.GONE);
mAutoConfirmSecurityMessage.setVisibility(View.GONE);
}
}
}
}

View File

@@ -0,0 +1,85 @@
/*
* Copyright (C) 2023 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.settings.security.screenlock;
import android.content.Context;
import androidx.preference.Preference;
import androidx.preference.TwoStatePreference;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.core.AbstractPreferenceController;
/**
* Preference controller for the pin_auto_confirm setting.
*/
public class AutoPinConfirmPreferenceController extends AbstractPreferenceController implements
PreferenceControllerMixin, Preference.OnPreferenceChangeListener {
private static final String PREF_KEY_PIN_AUTO_CONFIRM = "auto_pin_confirm";
private static final long MIN_AUTO_PIN_REQUIREMENT_LENGTH = 6L;
private final int mUserId;
private final LockPatternUtils mLockPatternUtils;
public AutoPinConfirmPreferenceController(Context context, int userId,
LockPatternUtils lockPatternUtils) {
super(context);
mUserId = userId;
mLockPatternUtils = lockPatternUtils;
}
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
setPinAutoConfirmSettingState((boolean) newValue);
return true;
}
@Override
public void updateState(Preference preference) {
((TwoStatePreference) preference).setChecked(getPinAutoConfirmSettingState());
}
@Override
public boolean isAvailable() {
return mLockPatternUtils.isAutoPinConfirmFeatureAvailable() && isPinLock()
&& isPinLengthEligibleForAutoConfirmation();
}
@Override
public String getPreferenceKey() {
return PREF_KEY_PIN_AUTO_CONFIRM;
}
private boolean isPinLock() {
return mLockPatternUtils.getCredentialTypeForUser(mUserId)
== LockPatternUtils.CREDENTIAL_TYPE_PIN;
}
private boolean isPinLengthEligibleForAutoConfirmation() {
return mLockPatternUtils.getPinLength(mUserId) >= MIN_AUTO_PIN_REQUIREMENT_LENGTH;
}
private boolean getPinAutoConfirmSettingState() {
return mLockPatternUtils.isAutoPinConfirmEnabled(mUserId);
}
private void setPinAutoConfirmSettingState(boolean state) {
mLockPatternUtils.setAutoPinConfirm(state, mUserId);
}
}

View File

@@ -75,6 +75,8 @@ public class ScreenLockSettings extends DashboardFragment
context, MY_USER_ID, lockPatternUtils));
controllers.add(new LockAfterTimeoutPreferenceController(
context, MY_USER_ID, lockPatternUtils));
controllers.add(new AutoPinConfirmPreferenceController(
context, MY_USER_ID, lockPatternUtils));
controllers.add(new OwnerInfoPreferenceController(context, parent));
return controllers;
}

View File

@@ -27,8 +27,10 @@ import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
import static android.provider.DeviceConfig.NAMESPACE_AUTO_PIN_CONFIRMATION;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
import static com.android.internal.widget.LockPatternUtils.FLAG_ENABLE_AUTO_PIN_CONFIRMATION;
import static com.android.internal.widget.LockPatternUtils.PASSWORD_TYPE_KEY;
import static com.android.settings.password.ChooseLockGeneric.CONFIRM_CREDENTIALS;
@@ -43,15 +45,21 @@ import android.app.admin.PasswordMetrics;
import android.app.admin.PasswordPolicy;
import android.content.Intent;
import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.view.View;
import android.widget.CheckBox;
import android.widget.TextView;
import com.android.internal.widget.LockscreenCredential;
import com.android.settings.R;
import com.android.settings.password.ChooseLockPassword.ChooseLockPasswordFragment;
import com.android.settings.password.ChooseLockPassword.IntentBuilder;
import com.android.settings.testutils.shadow.SettingsShadowResources;
import com.android.settings.testutils.shadow.ShadowDeviceConfig;
import com.android.settings.testutils.shadow.ShadowDevicePolicyManager;
import com.android.settings.testutils.shadow.ShadowLockPatternUtils;
import com.android.settings.testutils.shadow.ShadowUtils;
import com.android.settings.widget.ScrollToParentEditText;
import com.google.android.setupdesign.GlifLayout;
@@ -71,9 +79,9 @@ import org.robolectric.shadows.ShadowDrawable;
ShadowLockPatternUtils.class,
ShadowUtils.class,
ShadowDevicePolicyManager.class,
ShadowDeviceConfig.class,
})
public class ChooseLockPasswordTest {
@Before
public void setUp() {
SettingsShadowResources.overrideResource(
@@ -387,7 +395,9 @@ public class ChooseLockPasswordTest {
}
@Test
public void processAndValidatePasswordRequirements_defaultPinMinimumLength() {
public void processAndValidatePasswordRequirements_autoPinDisabled_defaultPinMinimumLength() {
DeviceConfig.setProperty(NAMESPACE_AUTO_PIN_CONFIRMATION, FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
/* value= */ "false", /* makeDefault= */ false);
PasswordPolicy policy = new PasswordPolicy();
policy.quality = PASSWORD_QUALITY_UNSPECIFIED;
@@ -399,6 +409,22 @@ public class ChooseLockPasswordTest {
"PIN must be at least 4 digits");
}
@Test
public void processAndValidatePasswordRequirements_autoPinEnabled_defaultPinMinimumLength() {
DeviceConfig.setProperty(NAMESPACE_AUTO_PIN_CONFIRMATION, FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
/* value= */ "true", /* makeDefault= */ false);
PasswordPolicy policy = new PasswordPolicy();
policy.quality = PASSWORD_QUALITY_UNSPECIFIED;
assertPasswordValidationResult(
/* minMetrics */ policy.getMinMetrics(),
/* minComplexity= */ PASSWORD_COMPLEXITY_NONE,
/* passwordType= */ PASSWORD_QUALITY_NUMERIC,
/* userEnteredPassword= */ LockscreenCredential.createPassword("11"),
"PIN must be at least 4 digits"
+ ", but a 6-digit PIN is recommended for added security");
}
@Test
public void processAndValidatePasswordRequirements_maximumLength() {
PasswordPolicy policy = new PasswordPolicy();
@@ -424,6 +450,123 @@ public class ChooseLockPasswordTest {
"PIN must be at least 8 digits");
}
@Test
public void autoPinConfirmOption_featureEnabledAndUntouchedByUser_changeStateAsPerRules() {
DeviceConfig.setProperty(NAMESPACE_AUTO_PIN_CONFIRMATION, FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
/* value= */ "true", /* makeDefault= */ false);
ChooseLockPassword passwordActivity = setupActivityWithPinTypeAndDefaultPolicy();
ChooseLockPasswordFragment fragment = getChooseLockPasswordFragment(passwordActivity);
ScrollToParentEditText passwordEntry = passwordActivity.findViewById(R.id.password_entry);
CheckBox pinAutoConfirmOption = passwordActivity
.findViewById(R.id.auto_pin_confirm_enabler);
TextView securityMessage =
passwordActivity.findViewById(R.id.auto_pin_confirm_security_message);
passwordEntry.setText("1234");
fragment.updateUi();
assertThat(pinAutoConfirmOption.getVisibility()).isEqualTo(View.GONE);
assertThat(securityMessage.getVisibility()).isEqualTo(View.GONE);
assertThat(pinAutoConfirmOption.isChecked()).isFalse();
passwordEntry.setText("123456");
fragment.updateUi();
assertThat(pinAutoConfirmOption.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(securityMessage.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(pinAutoConfirmOption.isChecked()).isTrue();
passwordEntry.setText("12345678");
fragment.updateUi();
assertThat(pinAutoConfirmOption.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(securityMessage.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(pinAutoConfirmOption.isChecked()).isFalse();
passwordEntry.setText("123456");
fragment.updateUi();
assertThat(pinAutoConfirmOption.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(securityMessage.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(pinAutoConfirmOption.isChecked()).isTrue();
}
@Test
public void autoPinConfirmOption_featureEnabledAndModifiedByUser_shouldChangeStateAsPerRules() {
DeviceConfig.setProperty(NAMESPACE_AUTO_PIN_CONFIRMATION, FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
/* value= */ "true", /* makeDefault= */ false);
ChooseLockPassword passwordActivity = setupActivityWithPinTypeAndDefaultPolicy();
ChooseLockPasswordFragment fragment = getChooseLockPasswordFragment(passwordActivity);
ScrollToParentEditText passwordEntry = passwordActivity.findViewById(R.id.password_entry);
CheckBox pinAutoConfirmOption = passwordActivity
.findViewById(R.id.auto_pin_confirm_enabler);
TextView securityMessage =
passwordActivity.findViewById(R.id.auto_pin_confirm_security_message);
passwordEntry.setText("123456");
fragment.updateUi();
assertThat(pinAutoConfirmOption.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(securityMessage.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(pinAutoConfirmOption.isChecked()).isTrue();
pinAutoConfirmOption.performClick();
assertThat(pinAutoConfirmOption.isChecked()).isFalse();
passwordEntry.setText("12345678");
fragment.updateUi();
assertThat(pinAutoConfirmOption.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(securityMessage.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(pinAutoConfirmOption.isChecked()).isFalse();
passwordEntry.setText("123456");
fragment.updateUi();
assertThat(pinAutoConfirmOption.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(securityMessage.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(pinAutoConfirmOption.isChecked()).isFalse();
}
@Test
public void autoPinConfirmOption_featureDisabled_shouldRemainInvisibleAndUnchecked() {
DeviceConfig.setProperty(NAMESPACE_AUTO_PIN_CONFIRMATION, FLAG_ENABLE_AUTO_PIN_CONFIRMATION,
/* value= */ "false", /* makeDefault= */ false);
ChooseLockPassword passwordActivity = setupActivityWithPinTypeAndDefaultPolicy();
ChooseLockPasswordFragment fragment = getChooseLockPasswordFragment(passwordActivity);
ScrollToParentEditText passwordEntry = passwordActivity.findViewById(R.id.password_entry);
CheckBox pinAutoConfirmOption = passwordActivity
.findViewById(R.id.auto_pin_confirm_enabler);
TextView securityMessage =
passwordActivity.findViewById(R.id.auto_pin_confirm_security_message);
passwordEntry.setText("1234");
fragment.updateUi();
assertThat(pinAutoConfirmOption.getVisibility()).isEqualTo(View.GONE);
assertThat(securityMessage.getVisibility()).isEqualTo(View.GONE);
assertThat(pinAutoConfirmOption.isChecked()).isFalse();
passwordEntry.setText("123456");
fragment.updateUi();
assertThat(pinAutoConfirmOption.getVisibility()).isEqualTo(View.GONE);
assertThat(securityMessage.getVisibility()).isEqualTo(View.GONE);
assertThat(pinAutoConfirmOption.isChecked()).isFalse();
passwordEntry.setText("12345678");
fragment.updateUi();
assertThat(pinAutoConfirmOption.getVisibility()).isEqualTo(View.GONE);
assertThat(securityMessage.getVisibility()).isEqualTo(View.GONE);
assertThat(pinAutoConfirmOption.isChecked()).isFalse();
}
private ChooseLockPassword setupActivityWithPinTypeAndDefaultPolicy() {
PasswordPolicy policy = new PasswordPolicy();
policy.quality = PASSWORD_QUALITY_UNSPECIFIED;
return buildChooseLockPasswordActivity(
new IntentBuilder(application)
.setUserId(UserHandle.myUserId())
.setPasswordType(PASSWORD_QUALITY_NUMERIC)
.setPasswordRequirement(PASSWORD_COMPLEXITY_NONE, policy.getMinMetrics())
.build());
}
private ChooseLockPassword buildChooseLockPasswordActivity(Intent intent) {
return Robolectric.buildActivity(ChooseLockPassword.class, intent).setup().get();
}

View File

@@ -0,0 +1,124 @@
/*
* Copyright (C) 2023 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.settings.security.screenlock;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import androidx.preference.SwitchPreference;
import androidx.test.core.app.ApplicationProvider;
import com.android.internal.widget.LockPatternUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
@RunWith(RobolectricTestRunner.class)
public class AutoPinConfirmPreferenceControllerTest {
private static final Integer TEST_USER_ID = 1;
@Mock
private LockPatternUtils mLockPatternUtils;
private AutoPinConfirmPreferenceController mController;
private SwitchPreference mPreference;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
Context context = ApplicationProvider.getApplicationContext();
mController =
new AutoPinConfirmPreferenceController(context, TEST_USER_ID, mLockPatternUtils);
mPreference = new SwitchPreference(context);
}
@Test
public void isAvailable_featureEnabledAndLockSetToNone_shouldReturnFalse() {
when(mLockPatternUtils.isSecure(TEST_USER_ID)).thenReturn(true);
when(mLockPatternUtils.isAutoPinConfirmFeatureAvailable()).thenReturn(true);
assertThat(mController.isAvailable()).isFalse();
}
@Test
public void isAvailable_featureEnabledAndLockSetToPassword_shouldReturnFalse() {
when(mLockPatternUtils.isSecure(TEST_USER_ID)).thenReturn(true);
when(mLockPatternUtils.isAutoPinConfirmFeatureAvailable()).thenReturn(true);
when(mLockPatternUtils.getCredentialTypeForUser(TEST_USER_ID))
.thenReturn(LockPatternUtils.CREDENTIAL_TYPE_PASSWORD);
assertThat(mController.isAvailable()).isFalse();
}
@Test
public void isAvailable_featureEnabledAndLockSetToPIN_lengthLessThanSix_shouldReturnFalse() {
when(mLockPatternUtils.isAutoPinConfirmFeatureAvailable()).thenReturn(true);
when(mLockPatternUtils.getCredentialTypeForUser(TEST_USER_ID))
.thenReturn(LockPatternUtils.CREDENTIAL_TYPE_PIN);
when(mLockPatternUtils.getPinLength(TEST_USER_ID)).thenReturn(5L);
assertThat(mController.isAvailable()).isFalse();
}
@Test
public void isAvailable_featureEnabledAndLockSetToPIN_lengthMoreThanEqSix_shouldReturnTrue() {
when(mLockPatternUtils.isSecure(TEST_USER_ID)).thenReturn(true);
when(mLockPatternUtils.isAutoPinConfirmFeatureAvailable()).thenReturn(true);
when(mLockPatternUtils.getCredentialTypeForUser(TEST_USER_ID))
.thenReturn(LockPatternUtils.CREDENTIAL_TYPE_PIN);
when(mLockPatternUtils.getPinLength(TEST_USER_ID)).thenReturn(6L);
assertThat(mController.isAvailable()).isTrue();
}
@Test
public void isAvailable_featureDisabledAndLockSetToPIN_shouldReturnFalse() {
when(mLockPatternUtils.isAutoPinConfirmFeatureAvailable()).thenReturn(false);
when(mLockPatternUtils.isSecure(TEST_USER_ID)).thenReturn(true);
when(mLockPatternUtils.getCredentialTypeForUser(TEST_USER_ID))
.thenReturn(LockPatternUtils.CREDENTIAL_TYPE_PIN);
assertThat(mController.isAvailable()).isFalse();
}
@Test
public void updateState_ChangingSettingState_shouldSetPreferenceToAppropriateCheckedState() {
when(mLockPatternUtils.isAutoPinConfirmFeatureAvailable()).thenReturn(true);
// When auto_pin_confirm setting is disabled, switchPreference is unchecked
when(mLockPatternUtils.isAutoPinConfirmEnabled(TEST_USER_ID)).thenReturn(false);
mController.updateState(mPreference);
assertThat(mPreference.isChecked()).isFalse();
// When auto_pin_confirm setting is enabled, switchPreference is checked
when(mLockPatternUtils.isAutoPinConfirmEnabled(TEST_USER_ID)).thenReturn(true);
mController.updateState(mPreference);
assertThat(mPreference.isChecked()).isTrue();
}
@Test
public void onPreferenceChange_shouldUpdatePinAutoConfirmSetting() {
when(mLockPatternUtils.isAutoPinConfirmFeatureAvailable()).thenReturn(true);
mController.onPreferenceChange(mPreference, /* newValue= */ true);
verify(mLockPatternUtils).setAutoPinConfirm(true, TEST_USER_ID);
}
}