Support remote device credentials validation in UI.

Test: m RunSettingsRoboTests -j ROBOTEST_FILTER=com.android.settings.password
Test: Manual
Bug: 258505917

Change-Id: Ifb9f15728eb8396b34c844d28f71a8e6e1aad837
This commit is contained in:
Brian Lee
2022-12-05 15:58:01 -08:00
parent dff3e4ed74
commit d4f8e5802e
18 changed files with 1582 additions and 71 deletions

View File

@@ -87,6 +87,7 @@ android_library {
"fuelgauge-log-protos-lite", "fuelgauge-log-protos-lite",
"fuelgauge-usage-state-protos-lite", "fuelgauge-usage-state-protos-lite",
"contextualcards", "contextualcards",
"securebox",
"settings-logtags", "settings-logtags",
"statslog-settings", "statslog-settings",
"zxing-core-1.7", "zxing-core-1.7",

View File

@@ -88,6 +88,7 @@
<uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" /> <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" />
<uses-permission android:name="android.permission.READ_SEARCH_INDEXABLES" /> <uses-permission android:name="android.permission.READ_SEARCH_INDEXABLES" />
<uses-permission android:name="android.permission.BIND_SETTINGS_SUGGESTIONS_SERVICE" /> <uses-permission android:name="android.permission.BIND_SETTINGS_SUGGESTIONS_SERVICE" />
<uses-permission android:name="android.permission.BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE" />
<uses-permission android:name="android.permission.OEM_UNLOCK_STATE" /> <uses-permission android:name="android.permission.OEM_UNLOCK_STATE" />
<uses-permission android:name="android.permission.MANAGE_USER_OEM_UNLOCK_STATE" /> <uses-permission android:name="android.permission.MANAGE_USER_OEM_UNLOCK_STATE" />
<uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG" /> <uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG" />
@@ -2370,6 +2371,17 @@
<activity-alias android:name=".ConfirmDeviceCredentialActivity" <activity-alias android:name=".ConfirmDeviceCredentialActivity"
android:targetActivity=".password.ConfirmDeviceCredentialActivity" android:targetActivity=".password.ConfirmDeviceCredentialActivity"
android:exported="true" /> android:exported="true" />
<!-- Activity alias for remote lockscreen validation. Enforces required permission -->
<activity-alias
android:name=".ConfirmRemoteDeviceCredentialActivity"
android:targetActivity=".password.ConfirmDeviceCredentialActivity"
android:permission="android.permission.CHECK_REMOTE_LOCKSCREEN"
android:exported="true">
<intent-filter>
<action android:name="android.app.action.CONFIRM_REMOTE_DEVICE_CREDENTIAL"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity-alias>
<!-- Note this must not be exported since it authenticates the given user --> <!-- Note this must not be exported since it authenticates the given user -->
<activity android:name=".password.ConfirmDeviceCredentialActivity$InternalActivity" <activity android:name=".password.ConfirmDeviceCredentialActivity$InternalActivity"

View File

@@ -15,10 +15,12 @@
--> -->
<com.google.android.setupdesign.GlifLayout <com.google.android.setupdesign.GlifLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/setup_wizard_layout" android:id="@+id/setup_wizard_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:icon="@drawable/ic_lock"> android:icon="@drawable/ic_lock"
app:sudUseBottomProgressBar="true">
<com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient <com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient
android:id="@+id/topLayout" android:id="@+id/topLayout"
@@ -60,6 +62,16 @@
android:layout_marginEnd="?attr/sudMarginEnd" android:layout_marginEnd="?attr/sudMarginEnd"
android:gravity="center_vertical"/> android:gravity="center_vertical"/>
<CheckBox
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="?attr/sudMarginStart"
android:layout_marginEnd="?attr/sudMarginEnd"
android:layout_marginTop="12dp"
android:visibility="gone"
android:checked="true" />
<Button <Button
android:id="@+id/cancelButton" android:id="@+id/cancelButton"
style="@style/SudGlifButton.Secondary" style="@style/SudGlifButton.Secondary"

View File

@@ -15,11 +15,13 @@
--> -->
<com.google.android.setupdesign.GlifLayout <com.google.android.setupdesign.GlifLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/setup_wizard_layout" android:id="@+id/setup_wizard_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:icon="@drawable/ic_lock" android:icon="@drawable/ic_lock"
android:importantForAutofill="noExcludeDescendants"> android:importantForAutofill="noExcludeDescendants"
app:sudUseBottomProgressBar="true">
<com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient <com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient
android:id="@+id/topLayout" android:id="@+id/topLayout"
@@ -27,14 +29,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<TextView
android:id="@+id/sud_layout_description"
style="@style/SudDescription.Glif"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="?attr/sudMarginStart"
android:layout_marginEnd="?attr/sudMarginEnd" />
<Space <Space
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
@@ -73,6 +67,17 @@
android:layout_height="0dp" android:layout_height="0dp"
android:layout_weight="1" /> android:layout_weight="1" />
<CheckBox
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="?attr/sudMarginStart"
android:layout_marginEnd="?attr/sudMarginEnd"
android:layout_marginTop="12dp"
android:layout_gravity="center_horizontal"
android:visibility="gone"
android:checked="true" />
<Button <Button
android:id="@+id/cancelButton" android:id="@+id/cancelButton"
style="@style/SudGlifButton.Secondary" style="@style/SudGlifButton.Secondary"

View File

@@ -15,10 +15,12 @@
--> -->
<com.google.android.setupdesign.GlifLayout <com.google.android.setupdesign.GlifLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/setup_wizard_layout" android:id="@+id/setup_wizard_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:icon="@drawable/ic_lock"> android:icon="@drawable/ic_lock"
app:sudUseBottomProgressBar="true">
<com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient <com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient
android:id="@+id/topLayout" android:id="@+id/topLayout"
@@ -61,6 +63,16 @@
android:layout_marginTop="12dp" android:layout_marginTop="12dp"
android:gravity="center_vertical"/> android:gravity="center_vertical"/>
<CheckBox
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="?attr/sudMarginStart"
android:layout_marginEnd="?attr/sudMarginEnd"
android:layout_marginTop="12dp"
android:visibility="gone"
android:checked="true" />
<Button <Button
android:id="@+id/cancelButton" android:id="@+id/cancelButton"
style="@style/SudGlifButton.Secondary" style="@style/SudGlifButton.Secondary"
@@ -68,7 +80,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="?attr/sudMarginStart" android:layout_marginStart="?attr/sudMarginStart"
android:layout_marginEnd="?attr/sudMarginEnd" android:layout_marginEnd="?attr/sudMarginEnd"
android:layout_marginBottom="80dp"
android:text="@string/cancel" /> android:text="@string/cancel" />
<Button <Button

View File

@@ -3339,6 +3339,24 @@
<!-- Header shown when the password needs to be solved because the device was factory reset. [CHAR LIMIT=100] --> <!-- Header shown when the password needs to be solved because the device was factory reset. [CHAR LIMIT=100] -->
<string name="lockpassword_confirm_your_password_header_frp">Verify password</string> <string name="lockpassword_confirm_your_password_header_frp">Verify password</string>
<!-- Header shown when prompted for remote device credential validation. [CHAR LIMIT=17] -->
<string name="lockpassword_remote_validation_header">Verify it\u0027s you</string>
<!-- Details shown when pattern is prompted for remote device credential validation. [CHAR LIMIT=100] -->
<string name="lockpassword_remote_validation_pattern_details">Enter your other device\u0027s pattern to securely transfer Google Accounts, settings, and more. Your pattern is encrypted.</string>
<!-- Details shown when PIN is prompted for remote device credential validation. [CHAR LIMIT=100] -->
<string name="lockpassword_remote_validation_pin_details">Enter your other device\u0027s PIN to securely transfer Google Accounts, settings, and more. Your PIN is encrypted.</string>
<!-- Details shown when password is prompted for remote device credential validation. [CHAR LIMIT=100] -->
<string name="lockpassword_remote_validation_password_details">Enter your other device\u0027s password to securely transfer Google Accounts, settings, and more. Your password is encrypted.</string>
<!-- Checkbox label to set pattern as new screen lock if remote device credential validation succeeds. [CHAR LIMIT=43] -->
<string name="lockpassword_remote_validation_set_pattern_as_screenlock">Also use pattern to unlock this device</string>
<!-- Checkbox label to set PIN as new screen lock if remote device credential validation succeeds. [CHAR LIMIT=43] -->
<string name="lockpassword_remote_validation_set_pin_as_screenlock">Also use PIN to unlock this device</string>
<!-- Checkbox label to set password as new screen lock if remote device credential validation succeeds. [CHAR LIMIT=43] -->
<string name="lockpassword_remote_validation_set_password_as_screenlock">Also use password to unlock this device</string>
<!-- Security & location settings screen, change security method screen instruction if user <!-- Security & location settings screen, change security method screen instruction if user
enters incorrect PIN [CHAR LIMIT=30] --> enters incorrect PIN [CHAR LIMIT=30] -->
<string name="lockpassword_invalid_pin">Wrong PIN</string> <string name="lockpassword_invalid_pin">Wrong PIN</string>

View File

@@ -22,7 +22,9 @@ import android.annotation.NonNull;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.app.Activity; import android.app.Activity;
import android.app.KeyguardManager; import android.app.KeyguardManager;
import android.app.StartLockscreenValidationRequest;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Intent; import android.content.Intent;
import android.content.IntentSender; import android.content.IntentSender;
import android.os.UserManager; import android.os.UserManager;
@@ -41,6 +43,8 @@ import com.android.settingslib.transition.SettingsTransitionHelper;
import com.google.android.setupcompat.util.WizardManagerHelper; import com.google.android.setupcompat.util.WizardManagerHelper;
import java.util.Optional;
public final class ChooseLockSettingsHelper { public final class ChooseLockSettingsHelper {
private static final String TAG = "ChooseLockSettingsHelper"; private static final String TAG = "ChooseLockSettingsHelper";
@@ -132,6 +136,7 @@ public final class ChooseLockSettingsHelper {
@Nullable private CharSequence mHeader; @Nullable private CharSequence mHeader;
@Nullable private CharSequence mDescription; @Nullable private CharSequence mDescription;
@Nullable private CharSequence mAlternateButton; @Nullable private CharSequence mAlternateButton;
@Nullable private CharSequence mCheckBoxLabel;
private boolean mReturnCredentials; private boolean mReturnCredentials;
private boolean mExternal; private boolean mExternal;
private boolean mForegroundOnly; private boolean mForegroundOnly;
@@ -139,6 +144,9 @@ public final class ChooseLockSettingsHelper {
private int mUserId; private int mUserId;
private boolean mAllowAnyUserId; private boolean mAllowAnyUserId;
private boolean mForceVerifyPath; private boolean mForceVerifyPath;
private boolean mRemoteLockscreenValidation;
@Nullable private StartLockscreenValidationRequest mStartLockscreenValidationRequest;
@Nullable private ComponentName mRemoteLockscreenValidationServiceComponent;
boolean mRequestGatekeeperPasswordHandle; boolean mRequestGatekeeperPasswordHandle;
public Builder(@NonNull Activity activity) { public Builder(@NonNull Activity activity) {
@@ -191,6 +199,15 @@ public final class ChooseLockSettingsHelper {
return this; return this;
} }
/**
* @param checkboxLabel text for the checkbox
*/
@NonNull
public Builder setCheckboxLabel(@Nullable CharSequence checkboxLabel) {
mCheckBoxLabel = checkboxLabel;
return this;
}
/** /**
* @param returnCredentials if true, puts the following credentials into intent for * @param returnCredentials if true, puts the following credentials into intent for
* onActivityResult with the following keys: * onActivityResult with the following keys:
@@ -253,6 +270,42 @@ public final class ChooseLockSettingsHelper {
return this; return this;
} }
/**
* @param isRemoteLockscreenValidation if true, remote device validation flow will be
* started. {@link #setStartLockscreenValidationRequest} and
* {@link #setRemoteLockscreenValidationServiceComponent}
* must also be used to set the required data.
*/
@NonNull public Builder setRemoteLockscreenValidation(
boolean isRemoteLockscreenValidation) {
mRemoteLockscreenValidation = isRemoteLockscreenValidation;
return this;
}
/**
* @param startLockScreenValidationRequest contains information necessary to perform remote
* lockscreen validation such as the remote device's
* lockscreen type, public key to be used for
* encryption, and remaining attempts.
*/
@NonNull public Builder setStartLockscreenValidationRequest(
StartLockscreenValidationRequest startLockScreenValidationRequest) {
mStartLockscreenValidationRequest = startLockScreenValidationRequest;
return this;
}
/**
* @param remoteLockscreenValidationServiceComponent the {@link ComponentName} of the
* {@link android.service.remotelockscreenvalidation.RemoteLockscreenValidationService}
* that will be used to validate the lockscreen guess.
*/
@NonNull public Builder setRemoteLockscreenValidationServiceComponent(
ComponentName remoteLockscreenValidationServiceComponent) {
mRemoteLockscreenValidationServiceComponent =
remoteLockscreenValidationServiceComponent;
return this;
}
/** /**
* Requests that LockSettingsService return a handle to the Gatekeeper Password (instead of * Requests that LockSettingsService return a handle to the Gatekeeper Password (instead of
* the Gatekeeper HAT). This allows us to use a single entry of the user's credential * the Gatekeeper HAT). This allows us to use a single entry of the user's credential
@@ -315,49 +368,41 @@ public final class ChooseLockSettingsHelper {
return launchConfirmationActivity(mBuilder.mRequestCode, mBuilder.mTitle, mBuilder.mHeader, return launchConfirmationActivity(mBuilder.mRequestCode, mBuilder.mTitle, mBuilder.mHeader,
mBuilder.mDescription, mBuilder.mReturnCredentials, mBuilder.mExternal, mBuilder.mDescription, mBuilder.mReturnCredentials, mBuilder.mExternal,
mBuilder.mForceVerifyPath, mBuilder.mUserId, mBuilder.mAlternateButton, mBuilder.mForceVerifyPath, mBuilder.mUserId, mBuilder.mAlternateButton,
mBuilder.mAllowAnyUserId, mBuilder.mForegroundOnly, mBuilder.mCheckBoxLabel, mBuilder.mRemoteLockscreenValidation,
mBuilder.mRequestGatekeeperPasswordHandle); mBuilder.mStartLockscreenValidationRequest,
mBuilder.mRemoteLockscreenValidationServiceComponent, mBuilder.mAllowAnyUserId,
mBuilder.mForegroundOnly, mBuilder.mRequestGatekeeperPasswordHandle);
} }
private boolean launchConfirmationActivity(int request, @Nullable CharSequence title, private boolean launchConfirmationActivity(int request, @Nullable CharSequence title,
@Nullable CharSequence header, @Nullable CharSequence description, @Nullable CharSequence header, @Nullable CharSequence description,
boolean returnCredentials, boolean external, boolean forceVerifyPath, boolean returnCredentials, boolean external, boolean forceVerifyPath,
int userId, @Nullable CharSequence alternateButton, boolean allowAnyUser, int userId, @Nullable CharSequence alternateButton,
boolean foregroundOnly, boolean requestGatekeeperPasswordHandle) { @Nullable CharSequence checkboxLabel, boolean remoteLockscreenValidation,
final int effectiveUserId = UserManager.get(mActivity).getCredentialOwnerProfile(userId); @Nullable StartLockscreenValidationRequest startLockScreenValidationRequest,
boolean launched = false; @Nullable ComponentName remoteLockscreenValidationServiceComponent,
boolean allowAnyUser, boolean foregroundOnly, boolean requestGatekeeperPasswordHandle) {
switch (mLockPatternUtils.getKeyguardStoredPasswordQuality(effectiveUserId)) { Optional<Class<?>> activityClass = determineAppropriateActivityClass(
case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: returnCredentials, forceVerifyPath, userId, startLockScreenValidationRequest);
launched = launchConfirmationActivity(request, title, header, description, if (activityClass.isEmpty()) {
returnCredentials || forceVerifyPath return false;
? ConfirmLockPattern.InternalActivity.class
: ConfirmLockPattern.class, returnCredentials, external,
forceVerifyPath, userId, alternateButton, allowAnyUser,
foregroundOnly, requestGatekeeperPasswordHandle);
break;
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
case DevicePolicyManager.PASSWORD_QUALITY_MANAGED:
launched = launchConfirmationActivity(request, title, header, description,
returnCredentials || forceVerifyPath
? ConfirmLockPassword.InternalActivity.class
: ConfirmLockPassword.class, returnCredentials, external,
forceVerifyPath, userId, alternateButton, allowAnyUser,
foregroundOnly, requestGatekeeperPasswordHandle);
break;
} }
return launched;
return launchConfirmationActivity(request, title, header, description, activityClass.get(),
returnCredentials, external, forceVerifyPath, userId, alternateButton,
checkboxLabel, remoteLockscreenValidation, startLockScreenValidationRequest,
remoteLockscreenValidationServiceComponent, allowAnyUser, foregroundOnly,
requestGatekeeperPasswordHandle);
} }
private boolean launchConfirmationActivity(int request, CharSequence title, CharSequence header, private boolean launchConfirmationActivity(int request, CharSequence title, CharSequence header,
CharSequence message, Class<?> activityClass, boolean returnCredentials, CharSequence message, Class<?> activityClass, boolean returnCredentials,
boolean external, boolean forceVerifyPath, int userId, boolean external, boolean forceVerifyPath, int userId,
@Nullable CharSequence alternateButton, boolean allowAnyUser, @Nullable CharSequence alternateButton, @Nullable CharSequence checkbox,
boolean foregroundOnly, boolean requestGatekeeperPasswordHandle) { boolean remoteLockscreenValidation,
@Nullable StartLockscreenValidationRequest startLockScreenValidationRequest,
@Nullable ComponentName remoteLockscreenValidationServiceComponent,
boolean allowAnyUser, boolean foregroundOnly, boolean requestGatekeeperPasswordHandle) {
final Intent intent = new Intent(); final Intent intent = new Intent();
intent.putExtra(ConfirmDeviceCredentialBaseFragment.TITLE_TEXT, title); intent.putExtra(ConfirmDeviceCredentialBaseFragment.TITLE_TEXT, title);
intent.putExtra(ConfirmDeviceCredentialBaseFragment.HEADER_TEXT, header); intent.putExtra(ConfirmDeviceCredentialBaseFragment.HEADER_TEXT, header);
@@ -367,10 +412,16 @@ public final class ChooseLockSettingsHelper {
intent.putExtra(ConfirmDeviceCredentialBaseFragment.SHOW_CANCEL_BUTTON, false); intent.putExtra(ConfirmDeviceCredentialBaseFragment.SHOW_CANCEL_BUTTON, false);
intent.putExtra(ConfirmDeviceCredentialBaseFragment.SHOW_WHEN_LOCKED, external); intent.putExtra(ConfirmDeviceCredentialBaseFragment.SHOW_WHEN_LOCKED, external);
intent.putExtra(ConfirmDeviceCredentialBaseFragment.USE_FADE_ANIMATION, external); intent.putExtra(ConfirmDeviceCredentialBaseFragment.USE_FADE_ANIMATION, external);
intent.putExtra(ConfirmDeviceCredentialBaseFragment.IS_REMOTE_LOCKSCREEN_VALIDATION,
remoteLockscreenValidation);
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_RETURN_CREDENTIALS, returnCredentials); intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_RETURN_CREDENTIALS, returnCredentials);
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FORCE_VERIFY, forceVerifyPath); intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FORCE_VERIFY, forceVerifyPath);
intent.putExtra(Intent.EXTRA_USER_ID, userId); intent.putExtra(Intent.EXTRA_USER_ID, userId);
intent.putExtra(KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL, alternateButton); intent.putExtra(KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL, alternateButton);
intent.putExtra(KeyguardManager.EXTRA_CHECKBOX_LABEL, checkbox);
intent.putExtra(KeyguardManager.EXTRA_START_LOCKSCREEN_VALIDATION_REQUEST,
startLockScreenValidationRequest);
intent.putExtra(Intent.EXTRA_COMPONENT_NAME, remoteLockscreenValidationServiceComponent);
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOREGROUND_ONLY, foregroundOnly); intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOREGROUND_ONLY, foregroundOnly);
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_ALLOW_ANY_USER, allowAnyUser); intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_ALLOW_ANY_USER, allowAnyUser);
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE,
@@ -405,6 +456,58 @@ public final class ChooseLockSettingsHelper {
return true; return true;
} }
private Optional<Integer> passwordQualityToLockTypes(int quality) {
switch (quality) {
case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
return Optional.of(KeyguardManager.PATTERN);
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
return Optional.of(KeyguardManager.PIN);
case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
case DevicePolicyManager.PASSWORD_QUALITY_MANAGED:
return Optional.of(KeyguardManager.PASSWORD);
}
Log.e(TAG, String.format(
"Cannot determine appropriate activity class for password quality %d",
quality));
return Optional.empty();
}
private Optional<Class<?>> determineAppropriateActivityClass(boolean returnCredentials,
boolean forceVerifyPath, int userId,
@Nullable StartLockscreenValidationRequest startLockscreenValidationRequest) {
int lockType;
if (startLockscreenValidationRequest != null) {
lockType = startLockscreenValidationRequest.getLockscreenUiType();
} else {
final int effectiveUserId = UserManager
.get(mActivity).getCredentialOwnerProfile(userId);
Optional<Integer> lockTypeOptional = passwordQualityToLockTypes(
mLockPatternUtils.getKeyguardStoredPasswordQuality(effectiveUserId));
if (lockTypeOptional.isEmpty()) {
return Optional.empty();
}
lockType = lockTypeOptional.get();
}
switch (lockType) {
case KeyguardManager.PASSWORD:
case KeyguardManager.PIN:
return Optional.of(returnCredentials || forceVerifyPath
? ConfirmLockPassword.InternalActivity.class
: ConfirmLockPassword.class);
case KeyguardManager.PATTERN:
return Optional.of(returnCredentials || forceVerifyPath
? ConfirmLockPattern.InternalActivity.class
: ConfirmLockPattern.class);
}
Log.e(TAG, String.format("Cannot determine appropriate activity class for lock type %d",
lockType));
return Optional.empty();
}
private void copyOptionalExtras(Intent inIntent, Intent outIntent) { private void copyOptionalExtras(Intent inIntent, Intent outIntent) {
IntentSender intentSender = inIntent.getParcelableExtra(Intent.EXTRA_INTENT); IntentSender intentSender = inIntent.getParcelableExtra(Intent.EXTRA_INTENT);
if (intentSender != null) { if (intentSender != null) {

View File

@@ -28,8 +28,10 @@ import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
import android.app.Activity; import android.app.Activity;
import android.app.KeyguardManager; import android.app.KeyguardManager;
import android.app.StartLockscreenValidationRequest;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager; import android.app.trust.TrustManager;
import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Color; import android.graphics.Color;
@@ -97,7 +99,7 @@ public class ConfirmDeviceCredentialActivity extends FragmentActivity {
private boolean mCheckDevicePolicyManager; private boolean mCheckDevicePolicyManager;
private String mTitle; private String mTitle;
private String mDetails; private CharSequence mDetails;
private int mUserId; private int mUserId;
private int mCredentialMode; private int mCredentialMode;
private boolean mGoingToBackground; private boolean mGoingToBackground;
@@ -178,10 +180,12 @@ public class ConfirmDeviceCredentialActivity extends FragmentActivity {
mCheckDevicePolicyManager = intent mCheckDevicePolicyManager = intent
.getBooleanExtra(KeyguardManager.EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS, false); .getBooleanExtra(KeyguardManager.EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS, false);
mTitle = intent.getStringExtra(KeyguardManager.EXTRA_TITLE); mTitle = intent.getStringExtra(KeyguardManager.EXTRA_TITLE);
mDetails = intent.getStringExtra(KeyguardManager.EXTRA_DESCRIPTION); mDetails = intent.getCharSequenceExtra(KeyguardManager.EXTRA_DESCRIPTION);
String alternateButton = intent.getStringExtra( String alternateButton = intent.getStringExtra(
KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL); KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL);
boolean frp = KeyguardManager.ACTION_CONFIRM_FRP_CREDENTIAL.equals(intent.getAction()); boolean frp = KeyguardManager.ACTION_CONFIRM_FRP_CREDENTIAL.equals(intent.getAction());
boolean remoteValidation =
KeyguardManager.ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL.equals(intent.getAction());
mUserId = UserHandle.myUserId(); mUserId = UserHandle.myUserId();
if (isInternalActivity()) { if (isInternalActivity()) {
@@ -230,6 +234,28 @@ public class ConfirmDeviceCredentialActivity extends FragmentActivity {
.setExternal(true) .setExternal(true)
.setUserId(LockPatternUtils.USER_FRP) .setUserId(LockPatternUtils.USER_FRP)
.show(); .show();
} else if (remoteValidation) {
StartLockscreenValidationRequest startLockScreenValidationRequest =
intent.getParcelableExtra(
KeyguardManager.EXTRA_START_LOCKSCREEN_VALIDATION_REQUEST,
StartLockscreenValidationRequest.class);
ComponentName remoteLockscreenValidationServiceComponent =
intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName.class);
String checkboxLabel = intent.getStringExtra(KeyguardManager.EXTRA_CHECKBOX_LABEL);
final ChooseLockSettingsHelper.Builder builder =
new ChooseLockSettingsHelper.Builder(this);
launchedCDC = builder
.setRemoteLockscreenValidation(true)
.setStartLockscreenValidationRequest(startLockScreenValidationRequest)
.setRemoteLockscreenValidationServiceComponent(
remoteLockscreenValidationServiceComponent)
.setHeader(mTitle) // Show the title in the header location
.setDescription(mDetails)
.setCheckboxLabel(checkboxLabel)
.setAlternateButton(alternateButton)
.setExternal(true)
.show();
} else if (isEffectiveUserManagedProfile && isInternalActivity()) { } else if (isEffectiveUserManagedProfile && isInternalActivity()) {
mCredentialMode = CREDENTIAL_MANAGED; mCredentialMode = CREDENTIAL_MANAGED;
if (isBiometricAllowed(effectiveUserId, mUserId)) { if (isBiometricAllowed(effectiveUserId, mUserId)) {

View File

@@ -24,8 +24,11 @@ import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.app.Dialog; import android.app.Dialog;
import android.app.KeyguardManager; import android.app.KeyguardManager;
import android.app.RemoteLockscreenValidationResult;
import android.app.StartLockscreenValidationRequest;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.app.admin.ManagedSubscriptionsPolicy; import android.app.admin.ManagedSubscriptionsPolicy;
import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
@@ -35,11 +38,15 @@ import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.UserHandle; import android.os.UserHandle;
import android.os.UserManager; import android.os.UserManager;
import android.service.remotelockscreenvalidation.IRemoteLockscreenValidationCallback;
import android.service.remotelockscreenvalidation.RemoteLockscreenValidationClient;
import android.telecom.TelecomManager; import android.telecom.TelecomManager;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.FeatureFlagUtils;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.CheckBox;
import android.widget.TextView; import android.widget.TextView;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
@@ -47,10 +54,16 @@ import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
import com.android.security.SecureBox;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.Utils; import com.android.settings.Utils;
import com.android.settings.core.InstrumentedFragment; import com.android.settings.core.InstrumentedFragment;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
/** /**
* Base fragment to be shared for PIN/Pattern/Password confirmation fragments. * Base fragment to be shared for PIN/Pattern/Password confirmation fragments.
*/ */
@@ -66,6 +79,8 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.showWhenLocked"; SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.showWhenLocked";
public static final String USE_FADE_ANIMATION = public static final String USE_FADE_ANIMATION =
SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.useFadeAnimation"; SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.useFadeAnimation";
public static final String IS_REMOTE_LOCKSCREEN_VALIDATION =
SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.isRemoteLockscreenValidation";
protected static final int USER_TYPE_PRIMARY = 1; protected static final int USER_TYPE_PRIMARY = 1;
protected static final int USER_TYPE_MANAGED_PROFILE = 2; protected static final int USER_TYPE_MANAGED_PROFILE = 2;
@@ -77,6 +92,7 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
protected boolean mReturnCredentials = false; protected boolean mReturnCredentials = false;
protected boolean mReturnGatekeeperPassword = false; protected boolean mReturnGatekeeperPassword = false;
protected boolean mForceVerifyPath = false; protected boolean mForceVerifyPath = false;
protected CheckBox mCheckBox;
protected Button mCancelButton; protected Button mCancelButton;
/** Button allowing managed profile password reset, null when is not shown. */ /** Button allowing managed profile password reset, null when is not shown. */
@Nullable protected Button mForgotButton; @Nullable protected Button mForgotButton;
@@ -88,8 +104,13 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
protected TextView mErrorTextView; protected TextView mErrorTextView;
protected final Handler mHandler = new Handler(); protected final Handler mHandler = new Handler();
protected boolean mFrp; protected boolean mFrp;
private CharSequence mFrpAlternateButtonText; protected boolean mRemoteValidation;
protected CharSequence mAlternateButtonText;
protected BiometricManager mBiometricManager; protected BiometricManager mBiometricManager;
@Nullable protected StartLockscreenValidationRequest mStartLockscreenValidationRequest;
/** Credential saved so the credential can be set for device if remote validation passes */
@Nullable protected LockscreenCredential mDeviceCredentialGuess;
@Nullable protected RemoteLockscreenValidationClient mRemoteLockscreenValidationClient;
private boolean isInternalActivity() { private boolean isInternalActivity() {
return (getActivity() instanceof ConfirmLockPassword.InternalActivity) return (getActivity() instanceof ConfirmLockPassword.InternalActivity)
@@ -100,7 +121,7 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
final Intent intent = getActivity().getIntent(); final Intent intent = getActivity().getIntent();
mFrpAlternateButtonText = intent.getCharSequenceExtra( mAlternateButtonText = intent.getCharSequenceExtra(
KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL); KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL);
mReturnCredentials = intent.getBooleanExtra( mReturnCredentials = intent.getBooleanExtra(
ChooseLockSettingsHelper.EXTRA_KEY_RETURN_CREDENTIALS, false); ChooseLockSettingsHelper.EXTRA_KEY_RETURN_CREDENTIALS, false);
@@ -110,6 +131,41 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
mForceVerifyPath = intent.getBooleanExtra( mForceVerifyPath = intent.getBooleanExtra(
ChooseLockSettingsHelper.EXTRA_KEY_FORCE_VERIFY, false); ChooseLockSettingsHelper.EXTRA_KEY_FORCE_VERIFY, false);
if (intent.getBooleanExtra(IS_REMOTE_LOCKSCREEN_VALIDATION, false)) {
if (FeatureFlagUtils.isEnabled(getContext(),
FeatureFlagUtils.SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION)) {
mRemoteValidation = true;
} else {
Log.e(TAG, "Remote device credential validation not enabled.");
getActivity().finish();
}
}
if (mRemoteValidation) {
mStartLockscreenValidationRequest = intent.getParcelableExtra(
KeyguardManager.EXTRA_START_LOCKSCREEN_VALIDATION_REQUEST,
StartLockscreenValidationRequest.class);
if (mStartLockscreenValidationRequest == null
|| mStartLockscreenValidationRequest.getRemainingAttempts() == 0) {
Log.e(TAG, "StartLockscreenValidationRequest is null or "
+ "no more attempts for remote lockscreen validation.");
getActivity().finish();
}
ComponentName remoteLockscreenValidationServiceComponent =
intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName.class);
if (remoteLockscreenValidationServiceComponent == null) {
Log.e(TAG, "RemoteLockscreenValidationService ComponentName is null");
getActivity().finish();
}
mRemoteLockscreenValidationClient = RemoteLockscreenValidationClient
.create(getContext(), remoteLockscreenValidationServiceComponent);
if (!mRemoteLockscreenValidationClient.isServiceAvailable()) {
Log.e(TAG, String.format("RemoteLockscreenValidationService at %s is not available",
remoteLockscreenValidationServiceComponent.getClassName()));
getActivity().finish();
}
}
// Only take this argument into account if it belongs to the current profile. // Only take this argument into account if it belongs to the current profile.
mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras(), mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras(),
isInternalActivity()); isInternalActivity());
@@ -126,13 +182,14 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
mCancelButton = view.findViewById(R.id.cancelButton); mCancelButton = view.findViewById(R.id.cancelButton);
boolean showCancelButton = getActivity().getIntent().getBooleanExtra( boolean showCancelButton = mRemoteValidation || getActivity().getIntent().getBooleanExtra(
SHOW_CANCEL_BUTTON, false); SHOW_CANCEL_BUTTON, false);
boolean hasAlternateButton = mFrp && !TextUtils.isEmpty(mFrpAlternateButtonText); boolean hasAlternateButton = (mFrp || mRemoteValidation) && !TextUtils.isEmpty(
mAlternateButtonText);
mCancelButton.setVisibility(showCancelButton || hasAlternateButton mCancelButton.setVisibility(showCancelButton || hasAlternateButton
? View.VISIBLE : View.GONE); ? View.VISIBLE : View.GONE);
if (hasAlternateButton) { if (hasAlternateButton) {
mCancelButton.setText(mFrpAlternateButtonText); mCancelButton.setText(mAlternateButtonText);
} }
mCancelButton.setOnClickListener(v -> { mCancelButton.setOnClickListener(v -> {
if (hasAlternateButton) { if (hasAlternateButton) {
@@ -141,6 +198,11 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
getActivity().finish(); getActivity().finish();
}); });
setupForgotButtonIfManagedProfile(view); setupForgotButtonIfManagedProfile(view);
mCheckBox = view.findViewById(R.id.checkbox);
if (mCheckBox != null && mRemoteValidation) {
mCheckBox.setVisibility(View.VISIBLE);
}
setupEmergencyCallButtonIfManagedSubscription(view); setupEmergencyCallButtonIfManagedSubscription(view);
} }
@@ -232,8 +294,21 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
super.onPause(); super.onPause();
} }
@Override
public void onDestroy() {
if (mRemoteLockscreenValidationClient != null) {
mRemoteLockscreenValidationClient.disconnect();
}
if (mDeviceCredentialGuess != null) {
mDeviceCredentialGuess.zeroize();
}
super.onDestroy();
}
protected abstract void authenticationSucceeded(); protected abstract void authenticationSucceeded();
protected abstract void onRemoteDeviceCredentialValidationResult(
RemoteLockscreenValidationResult result);
public void prepareEnterAnimation() { public void prepareEnterAnimation() {
} }
@@ -335,6 +410,46 @@ public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFr
} }
} }
protected void validateGuess(LockscreenCredential credentialGuess) {
if (mCheckBox.isChecked()) {
// Keep credential in memory since user wants to set guess as screen lock.
mDeviceCredentialGuess = credentialGuess;
} else if (mDeviceCredentialGuess != null) {
mDeviceCredentialGuess.zeroize();
}
mRemoteLockscreenValidationClient.validateLockscreenGuess(
encryptDeviceCredentialGuess(credentialGuess.getCredential()),
new IRemoteLockscreenValidationCallback.Stub() {
@Override
public void onSuccess(RemoteLockscreenValidationResult result) {
mHandler.post(()->onRemoteDeviceCredentialValidationResult(result));
}
@Override
public void onFailure(String message) {
Log.e(TAG, "A failure occurred while trying "
+ "to validate lockscreen guess: " + message);
mHandler.post(()->getActivity().finish());
}
});
}
private byte[] encryptDeviceCredentialGuess(byte[] guess) {
try {
byte[] encodedPublicKey = mStartLockscreenValidationRequest.getSourcePublicKey();
PublicKey publicKey = SecureBox.decodePublicKey(encodedPublicKey);
return SecureBox.encrypt(
publicKey,
/* sharedSecret= */ null,
LockPatternUtils.ENCRYPTED_REMOTE_CREDENTIALS_HEADER,
guess);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
Log.w(TAG, "Error encrypting device credential guess. Returning empty byte[].", e);
return new byte[0];
}
}
protected abstract void onShowError(); protected abstract void onShowError();
protected void showError(int msg, long timeout) { protected void showError(int msg, long timeout) {

View File

@@ -27,6 +27,8 @@ import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROF
import static android.app.admin.DevicePolicyResources.UNDEFINED; import static android.app.admin.DevicePolicyResources.UNDEFINED;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.app.KeyguardManager;
import android.app.RemoteLockscreenValidationResult;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.content.Context; import android.content.Context;
@@ -42,6 +44,7 @@ import android.os.UserManager;
import android.text.Editable; import android.text.Editable;
import android.text.InputType; import android.text.InputType;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@@ -120,7 +123,7 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
public static class ConfirmLockPasswordFragment extends ConfirmDeviceCredentialBaseFragment public static class ConfirmLockPasswordFragment extends ConfirmDeviceCredentialBaseFragment
implements OnClickListener, OnEditorActionListener, implements OnClickListener, OnEditorActionListener,
CredentialCheckResultTracker.Listener { CredentialCheckResultTracker.Listener, SaveChosenLockWorkerBase.Listener {
private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result"; private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result";
private ImeAwareEditText mPasswordEntry; private ImeAwareEditText mPasswordEntry;
private TextViewInputDisabler mPasswordEntryInputDisabler; private TextViewInputDisabler mPasswordEntryInputDisabler;
@@ -134,6 +137,7 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
private DisappearAnimationUtils mDisappearAnimationUtils; private DisappearAnimationUtils mDisappearAnimationUtils;
private boolean mIsManagedProfile; private boolean mIsManagedProfile;
private GlifLayout mGlifLayout; private GlifLayout mGlifLayout;
private CharSequence mCheckBoxLabel;
// required constructor for fragments // required constructor for fragments
public ConfirmLockPasswordFragment() { public ConfirmLockPasswordFragment() {
@@ -160,11 +164,19 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
mPasswordEntry.requestFocus(); mPasswordEntry.requestFocus();
mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry); mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry);
mErrorTextView = (TextView) view.findViewById(R.id.errorText); mErrorTextView = (TextView) view.findViewById(R.id.errorText);
if (mRemoteValidation) {
mIsAlpha = mStartLockscreenValidationRequest.getLockscreenUiType()
== KeyguardManager.PASSWORD;
// ProgressBar visibility is set to GONE until interacted with.
// Set progress bar to INVISIBLE, so the EditText does not get bumped down later.
mGlifLayout.setProgressBarShown(false);
} else {
mIsAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == storedQuality mIsAlpha = DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC == storedQuality
|| DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == storedQuality || DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == storedQuality
|| DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == storedQuality || DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == storedQuality
|| DevicePolicyManager.PASSWORD_QUALITY_MANAGED == storedQuality; || DevicePolicyManager.PASSWORD_QUALITY_MANAGED == storedQuality;
}
mImm = (InputMethodManager) getActivity().getSystemService( mImm = (InputMethodManager) getActivity().getSystemService(
Context.INPUT_METHOD_SERVICE); Context.INPUT_METHOD_SERVICE);
@@ -187,6 +199,7 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
} }
mGlifLayout.setHeaderText(headerMessage); mGlifLayout.setHeaderText(headerMessage);
mGlifLayout.setDescriptionText(detailsMessage); mGlifLayout.setDescriptionText(detailsMessage);
mCheckBoxLabel = intent.getCharSequenceExtra(KeyguardManager.EXTRA_CHECKBOX_LABEL);
} }
int currentType = mPasswordEntry.getInputType(); int currentType = mPasswordEntry.getInputType();
if (mIsAlpha) { if (mIsAlpha) {
@@ -227,6 +240,19 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
@Override @Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
if (mRemoteValidation) {
if (mCheckBox != null) {
mCheckBox.setText(TextUtils.isEmpty(mCheckBoxLabel)
? getDefaultCheckboxLabel()
: mCheckBoxLabel);
}
if (mCancelButton != null && TextUtils.isEmpty(mAlternateButtonText)) {
mCancelButton.setText(mIsAlpha
? R.string.lockpassword_forgot_password
: R.string.lockpassword_forgot_pin);
}
}
if (mForgotButton != null) { if (mForgotButton != null) {
mForgotButton.setText(mIsAlpha mForgotButton.setText(mIsAlpha
? R.string.lockpassword_forgot_password ? R.string.lockpassword_forgot_password
@@ -237,7 +263,9 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
@Override @Override
public void onDestroy() { public void onDestroy() {
super.onDestroy(); super.onDestroy();
if (mPasswordEntry != null) {
mPasswordEntry.setText(null); mPasswordEntry.setText(null);
}
// Force a garbage collection to remove remnant of user password shards from memory. // Force a garbage collection to remove remnant of user password shards from memory.
// Execute this with a slight delay to allow the activity lifecycle to complete and // Execute this with a slight delay to allow the activity lifecycle to complete and
// the instance to become gc-able. // the instance to become gc-able.
@@ -253,6 +281,9 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
return mIsAlpha ? getString(R.string.lockpassword_confirm_your_password_header_frp) return mIsAlpha ? getString(R.string.lockpassword_confirm_your_password_header_frp)
: getString(R.string.lockpassword_confirm_your_pin_header_frp); : getString(R.string.lockpassword_confirm_your_pin_header_frp);
} }
if (mRemoteValidation) {
return getString(R.string.lockpassword_remote_validation_header);
}
if (mIsManagedProfile) { if (mIsManagedProfile) {
if (mIsAlpha) { if (mIsAlpha) {
return mDevicePolicyManager.getResources().getString( return mDevicePolicyManager.getResources().getString(
@@ -273,6 +304,11 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
return mIsAlpha ? getString(R.string.lockpassword_confirm_your_password_details_frp) return mIsAlpha ? getString(R.string.lockpassword_confirm_your_password_details_frp)
: getString(R.string.lockpassword_confirm_your_pin_details_frp); : getString(R.string.lockpassword_confirm_your_pin_details_frp);
} }
if (mRemoteValidation) {
return getContext().getString(mIsAlpha
? R.string.lockpassword_remote_validation_password_details
: R.string.lockpassword_remote_validation_pin_details);
}
boolean isStrongAuthRequired = isStrongAuthRequired(); boolean isStrongAuthRequired = isStrongAuthRequired();
// Map boolean flags to an index by isStrongAuth << 2 + isManagedProfile << 1 + isAlpha. // Map boolean flags to an index by isStrongAuth << 2 + isManagedProfile << 1 + isAlpha.
int index = ((isStrongAuthRequired ? 1 : 0) << 2) + ((mIsManagedProfile ? 1 : 0) << 1) int index = ((isStrongAuthRequired ? 1 : 0) << 2) + ((mIsManagedProfile ? 1 : 0) << 1)
@@ -281,6 +317,16 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
DETAIL_TEXT_OVERRIDES[index], () -> getString(DETAIL_TEXTS[index])); DETAIL_TEXT_OVERRIDES[index], () -> getString(DETAIL_TEXTS[index]));
} }
private String getDefaultCheckboxLabel() {
if (mRemoteValidation) {
return getString(mIsAlpha
? R.string.lockpassword_remote_validation_set_password_as_screenlock
: R.string.lockpassword_remote_validation_set_pin_as_screenlock);
}
throw new IllegalStateException(
"Trying to get default checkbox label for illegal flow");
}
private int getErrorMessage() { private int getErrorMessage() {
return mIsAlpha ? R.string.lockpassword_invalid_password return mIsAlpha ? R.string.lockpassword_invalid_password
: R.string.lockpassword_invalid_pin; : R.string.lockpassword_invalid_pin;
@@ -392,6 +438,7 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
mImm.hideSoftInputFromWindow(mPasswordEntry.getWindowToken(), 0 /*flags*/); mImm.hideSoftInputFromWindow(mPasswordEntry.getWindowToken(), 0 /*flags*/);
} else { } else {
mPasswordEntry.scheduleShowSoftInput(); mPasswordEntry.scheduleShowSoftInput();
mPasswordEntry.requestFocus();
} }
} }
@@ -413,12 +460,18 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
if (TextUtils.isEmpty(passwordText)) { if (TextUtils.isEmpty(passwordText)) {
return; return;
} }
final LockscreenCredential credential = final LockscreenCredential credential = mIsAlpha
mIsAlpha ? LockscreenCredential.createPassword(passwordText) ? LockscreenCredential.createPassword(passwordText)
: LockscreenCredential.createPin(passwordText); : LockscreenCredential.createPin(passwordText);
mPasswordEntryInputDisabler.setInputEnabled(false); mPasswordEntryInputDisabler.setInputEnabled(false);
if (mRemoteValidation) {
validateGuess(credential);
mGlifLayout.setProgressBarShown(true);
return;
}
Intent intent = new Intent(); Intent intent = new Intent();
// TODO(b/161956762): Sanitize this // TODO(b/161956762): Sanitize this
if (mReturnGatekeeperPassword) { if (mReturnGatekeeperPassword) {
@@ -546,6 +599,44 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
} }
} }
@Override
protected void onRemoteDeviceCredentialValidationResult(
RemoteLockscreenValidationResult result) {
switch (result.getResultCode()) {
case RemoteLockscreenValidationResult.RESULT_GUESS_VALID:
if (mCheckBox.isChecked()) {
ChooseLockPassword.SaveAndFinishWorker saveAndFinishWorker =
new ChooseLockPassword.SaveAndFinishWorker();
Log.i(TAG, "Setting device screen lock to the other device's screen lock.");
getFragmentManager().beginTransaction().add(saveAndFinishWorker, null)
.commit();
getFragmentManager().executePendingTransactions();
saveAndFinishWorker.setListener(this);
saveAndFinishWorker.start(
mLockPatternUtils,
/* requestGatekeeperPassword= */ false,
mDeviceCredentialGuess,
/* currentCredential= */ null,
mEffectiveUserId);
return;
}
mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(),
/* timeoutMs= */ 0, mEffectiveUserId);
break;
case RemoteLockscreenValidationResult.RESULT_GUESS_INVALID:
mCredentialCheckResultTracker.setResult(/* matched= */ false, new Intent(),
/* timeoutMs= */ 0, mEffectiveUserId);
break;
case RemoteLockscreenValidationResult.RESULT_LOCKOUT:
mCredentialCheckResultTracker.setResult(/* matched= */ false, new Intent(),
(int) result.getTimeoutMillis(), mEffectiveUserId);
break;
case RemoteLockscreenValidationResult.RESULT_NO_REMAINING_ATTEMPTS:
getActivity().finish();
}
mGlifLayout.setProgressBarShown(false);
}
@Override @Override
public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs, public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs,
int effectiveUserId, boolean newResult) { int effectiveUserId, boolean newResult) {
@@ -601,5 +692,19 @@ public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
} }
return false; return false;
} }
/**
* Callback for when the device credential guess used for remote validation was set as the
* current device's device credential.
*/
@Override
public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
if (mDeviceCredentialGuess != null) {
mDeviceCredentialGuess.zeroize();
}
mGlifLayout.setProgressBarShown(false);
mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(),
/* timeoutMs= */ 0, mEffectiveUserId);
}
} }
} }

View File

@@ -25,6 +25,8 @@ import static android.app.admin.DevicePolicyResources.UNDEFINED;
import android.annotation.Nullable; import android.annotation.Nullable;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
import android.app.KeyguardManager;
import android.app.RemoteLockscreenValidationResult;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.content.Intent; import android.content.Intent;
import android.os.AsyncTask; import android.os.AsyncTask;
@@ -33,6 +35,7 @@ import android.os.CountDownTimer;
import android.os.SystemClock; import android.os.SystemClock;
import android.os.UserManager; import android.os.UserManager;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
@@ -89,7 +92,8 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
} }
public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBaseFragment public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBaseFragment
implements AppearAnimationCreator<Object>, CredentialCheckResultTracker.Listener { implements AppearAnimationCreator<Object>, CredentialCheckResultTracker.Listener,
SaveChosenLockWorkerBase.Listener {
private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result"; private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result";
@@ -105,6 +109,7 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
// caller-supplied text for various prompts // caller-supplied text for various prompts
private CharSequence mHeaderText; private CharSequence mHeaderText;
private CharSequence mDetailsText; private CharSequence mDetailsText;
private CharSequence mCheckBoxLabel;
private AppearAnimationUtils mAppearAnimationUtils; private AppearAnimationUtils mAppearAnimationUtils;
private DisappearAnimationUtils mDisappearAnimationUtils; private DisappearAnimationUtils mDisappearAnimationUtils;
@@ -148,6 +153,7 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
ConfirmDeviceCredentialBaseFragment.HEADER_TEXT); ConfirmDeviceCredentialBaseFragment.HEADER_TEXT);
mDetailsText = intent.getCharSequenceExtra( mDetailsText = intent.getCharSequenceExtra(
ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT); ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT);
mCheckBoxLabel = intent.getCharSequenceExtra(KeyguardManager.EXTRA_CHECKBOX_LABEL);
} }
if (TextUtils.isEmpty(mHeaderText) && mIsManagedProfile) { if (TextUtils.isEmpty(mHeaderText) && mIsManagedProfile) {
mHeaderText = mDevicePolicyManager.getOrganizationNameForUser(mUserId); mHeaderText = mDevicePolicyManager.getOrganizationNameForUser(mUserId);
@@ -174,7 +180,8 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
// ability to disable the pattern in L. Remove this block after // ability to disable the pattern in L. Remove this block after
// ensuring it's safe to do so. (Note that ConfirmLockPassword // ensuring it's safe to do so. (Note that ConfirmLockPassword
// doesn't have this). // doesn't have this).
if (!mFrp && !mLockPatternUtils.isLockPatternEnabled(mEffectiveUserId)) { if (!mFrp && !mRemoteValidation
&& !mLockPatternUtils.isLockPatternEnabled(mEffectiveUserId)) {
getActivity().setResult(Activity.RESULT_OK); getActivity().setResult(Activity.RESULT_OK);
getActivity().finish(); getActivity().finish();
} }
@@ -203,12 +210,33 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
FRAGMENT_TAG_CHECK_LOCK_RESULT).commit(); FRAGMENT_TAG_CHECK_LOCK_RESULT).commit();
} }
if (mRemoteValidation) {
// ProgressBar visibility is set to GONE until interacted with.
// Set progress bar to INVISIBLE, so the pattern does not get bumped down later.
mGlifLayout.setProgressBarShown(false);
// Lock pattern is generally not visible until the user has set a lockscreen for the
// first time. For a new user, this means that the pattern will always be hidden.
// Despite this prerequisite, we want to show the pattern anyway for this flow.
mLockPatternView.setInStealthMode(false);
}
return view; return view;
} }
@Override @Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
if (mRemoteValidation) {
if (mCheckBox != null) {
mCheckBox.setText(TextUtils.isEmpty(mCheckBoxLabel)
? getDefaultCheckboxLabel()
: mCheckBoxLabel);
}
if (mCancelButton != null && TextUtils.isEmpty(mAlternateButtonText)) {
mCancelButton.setText(R.string.lockpassword_forgot_pattern);
}
}
if (mForgotButton != null) { if (mForgotButton != null) {
mForgotButton.setText(R.string.lockpassword_forgot_pattern); mForgotButton.setText(R.string.lockpassword_forgot_pattern);
} }
@@ -271,6 +299,10 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
if (mFrp) { if (mFrp) {
return getString(R.string.lockpassword_confirm_your_pattern_details_frp); return getString(R.string.lockpassword_confirm_your_pattern_details_frp);
} }
if (mRemoteValidation) {
return getString(
R.string.lockpassword_remote_validation_pattern_details);
}
final boolean isStrongAuthRequired = isStrongAuthRequired(); final boolean isStrongAuthRequired = isStrongAuthRequired();
if (mIsManagedProfile) { if (mIsManagedProfile) {
if (isStrongAuthRequired) { if (isStrongAuthRequired) {
@@ -335,11 +367,11 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
} else { } else {
mGlifLayout.setHeaderText(getDefaultHeader()); mGlifLayout.setHeaderText(getDefaultHeader());
} }
if (mDetailsText != null) {
mGlifLayout.setDescriptionText(mDetailsText); CharSequence detailsText =
} else { mDetailsText == null ? getDefaultDetails() : mDetailsText;
mGlifLayout.setDescriptionText(getDefaultDetails()); mGlifLayout.setDescriptionText(detailsText);
}
mErrorTextView.setText(""); mErrorTextView.setText("");
updateErrorMessage( updateErrorMessage(
mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId)); mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId));
@@ -371,7 +403,9 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
private String getDefaultHeader() { private String getDefaultHeader() {
if (mFrp) return getString(R.string.lockpassword_confirm_your_pattern_header_frp); if (mFrp) return getString(R.string.lockpassword_confirm_your_pattern_header_frp);
if (mRemoteValidation) {
return getString(R.string.lockpassword_remote_validation_header);
}
if (mIsManagedProfile) { if (mIsManagedProfile) {
return mDevicePolicyManager.getResources().getString( return mDevicePolicyManager.getResources().getString(
CONFIRM_WORK_PROFILE_PATTERN_HEADER, CONFIRM_WORK_PROFILE_PATTERN_HEADER,
@@ -381,6 +415,14 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
return getString(R.string.lockpassword_confirm_your_pattern_header); return getString(R.string.lockpassword_confirm_your_pattern_header);
} }
private String getDefaultCheckboxLabel() {
if (mRemoteValidation) {
return getString(R.string.lockpassword_remote_validation_set_pattern_as_screenlock);
}
throw new IllegalStateException(
"Trying to get default checkbox label for illegal flow");
}
private Runnable mClearPatternRunnable = new Runnable() { private Runnable mClearPatternRunnable = new Runnable() {
public void run() { public void run() {
mLockPatternView.clearPattern(); mLockPatternView.clearPattern();
@@ -453,6 +495,13 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
mLockPatternView.setEnabled(false); mLockPatternView.setEnabled(false);
final LockscreenCredential credential = LockscreenCredential.createPattern(pattern); final LockscreenCredential credential = LockscreenCredential.createPattern(pattern);
if (mRemoteValidation) {
validateGuess(credential);
mGlifLayout.setProgressBarShown(true);
return;
}
// TODO(b/161956762): Sanitize this // TODO(b/161956762): Sanitize this
Intent intent = new Intent(); Intent intent = new Intent();
if (mReturnGatekeeperPassword) { if (mReturnGatekeeperPassword) {
@@ -563,6 +612,44 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
} }
} }
@Override
protected void onRemoteDeviceCredentialValidationResult(
RemoteLockscreenValidationResult result) {
switch (result.getResultCode()) {
case RemoteLockscreenValidationResult.RESULT_GUESS_VALID:
if (mCheckBox.isChecked()) {
Log.i(TAG, "Setting device screen lock to the other device's screen lock.");
ChooseLockPattern.SaveAndFinishWorker saveAndFinishWorker =
new ChooseLockPattern.SaveAndFinishWorker();
getFragmentManager().beginTransaction().add(saveAndFinishWorker, null)
.commit();
getFragmentManager().executePendingTransactions();
saveAndFinishWorker.setListener(this);
saveAndFinishWorker.start(
mLockPatternUtils,
/* requestGatekeeperPassword= */ false,
mDeviceCredentialGuess,
/* currentCredential= */ null,
mEffectiveUserId);
return;
}
mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(),
/* timeoutMs= */ 0, mEffectiveUserId);
break;
case RemoteLockscreenValidationResult.RESULT_GUESS_INVALID:
mCredentialCheckResultTracker.setResult(/* matched= */ false, new Intent(),
/* timeoutMs= */ 0, mEffectiveUserId);
break;
case RemoteLockscreenValidationResult.RESULT_LOCKOUT:
mCredentialCheckResultTracker.setResult(/* matched= */ false, new Intent(),
(int) result.getTimeoutMillis(), mEffectiveUserId);
break;
case RemoteLockscreenValidationResult.RESULT_NO_REMAINING_ATTEMPTS:
getActivity().finish();
}
mGlifLayout.setProgressBarShown(false);
}
@Override @Override
public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs, public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs,
int effectiveUserId, boolean newResult) { int effectiveUserId, boolean newResult) {
@@ -632,5 +719,19 @@ public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
appearing, interpolator, finishListener); appearing, interpolator, finishListener);
} }
} }
/**
* Callback for when the device credential guess used for remote validation was set as the
* current device's device credential.
*/
@Override
public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
if (mDeviceCredentialGuess != null) {
mDeviceCredentialGuess.zeroize();
}
mGlifLayout.setProgressBarShown(false);
mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(),
/* timeoutMs= */ 0, mEffectiveUserId);
}
} }
} }

View File

@@ -1,5 +1,9 @@
package com.android.settings.password; package com.android.settings.password;
import static com.android.settings.password.TestUtils.COMPONENT_NAME;
import static com.android.settings.password.TestUtils.VALID_REMAINING_ATTEMPTS;
import static com.android.settings.password.TestUtils.createStartLockscreenValidationRequest;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@@ -10,6 +14,8 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import android.app.Activity; import android.app.Activity;
import android.app.KeyguardManager;
import android.app.StartLockscreenValidationRequest;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Intent; import android.content.Intent;
@@ -160,6 +166,93 @@ public class ChooseLockSettingsHelperTest {
startedIntent.getComponent()); startedIntent.getComponent());
} }
@Test
public void launchConfirmPassword_remoteValidation_passwordLockType() throws Exception {
Activity activity = Robolectric.setupActivity(Activity.class);
ShadowActivity shadowActivity = Shadows.shadowOf(activity);
StartLockscreenValidationRequest request = createStartLockscreenValidationRequest(
KeyguardManager.PASSWORD, VALID_REMAINING_ATTEMPTS);
ChooseLockSettingsHelper chooseLockSettingsHelper = getChooseLockSettingsHelper(
new ChooseLockSettingsHelper.Builder(activity)
.setRemoteLockscreenValidation(true)
.setStartLockscreenValidationRequest(request)
.setRemoteLockscreenValidationServiceComponent(COMPONENT_NAME));
chooseLockSettingsHelper.launch();
Intent startedIntent = shadowActivity.getNextStartedActivity();
assertEquals(new ComponentName("com.android.settings",
ConfirmLockPassword.class.getName()), startedIntent.getComponent());
assertThat(startedIntent.getBooleanExtra(
ConfirmDeviceCredentialBaseFragment.IS_REMOTE_LOCKSCREEN_VALIDATION, false)
).isTrue();
assertThat(startedIntent.getParcelableExtra(
KeyguardManager.EXTRA_START_LOCKSCREEN_VALIDATION_REQUEST,
StartLockscreenValidationRequest.class)
).isEqualTo(request);
assertThat(startedIntent.getParcelableExtra(
Intent.EXTRA_COMPONENT_NAME, ComponentName.class)
).isEqualTo(COMPONENT_NAME);
}
@Test
public void launchConfirmPassword_remoteValidation_pinLockType() throws Exception {
Activity activity = Robolectric.setupActivity(Activity.class);
ShadowActivity shadowActivity = Shadows.shadowOf(activity);
StartLockscreenValidationRequest request = createStartLockscreenValidationRequest(
KeyguardManager.PIN, VALID_REMAINING_ATTEMPTS);
ChooseLockSettingsHelper chooseLockSettingsHelper = getChooseLockSettingsHelper(
new ChooseLockSettingsHelper.Builder(activity)
.setRemoteLockscreenValidation(true)
.setStartLockscreenValidationRequest(request)
.setRemoteLockscreenValidationServiceComponent(COMPONENT_NAME));
chooseLockSettingsHelper.launch();
Intent startedIntent = shadowActivity.getNextStartedActivity();
assertEquals(new ComponentName("com.android.settings",
ConfirmLockPassword.class.getName()), startedIntent.getComponent());
assertThat(startedIntent.getBooleanExtra(
ConfirmDeviceCredentialBaseFragment.IS_REMOTE_LOCKSCREEN_VALIDATION, false)
).isTrue();
assertThat(startedIntent.getParcelableExtra(
KeyguardManager.EXTRA_START_LOCKSCREEN_VALIDATION_REQUEST,
StartLockscreenValidationRequest.class)
).isEqualTo(request);
assertThat(startedIntent.getParcelableExtra(
Intent.EXTRA_COMPONENT_NAME, ComponentName.class)
).isEqualTo(COMPONENT_NAME);
}
@Test
public void launchConfirmPattern_remoteValidation_patternLockType() throws Exception {
Activity activity = Robolectric.setupActivity(Activity.class);
ShadowActivity shadowActivity = Shadows.shadowOf(activity);
StartLockscreenValidationRequest request = createStartLockscreenValidationRequest(
KeyguardManager.PATTERN, VALID_REMAINING_ATTEMPTS);
ChooseLockSettingsHelper chooseLockSettingsHelper = getChooseLockSettingsHelper(
new ChooseLockSettingsHelper.Builder(activity)
.setRemoteLockscreenValidation(true)
.setStartLockscreenValidationRequest(request)
.setRemoteLockscreenValidationServiceComponent(COMPONENT_NAME));
chooseLockSettingsHelper.launch();
Intent startedIntent = shadowActivity.getNextStartedActivity();
assertEquals(new ComponentName("com.android.settings",
ConfirmLockPattern.class.getName()), startedIntent.getComponent());
assertThat(startedIntent.getBooleanExtra(
ConfirmDeviceCredentialBaseFragment.IS_REMOTE_LOCKSCREEN_VALIDATION, false)
).isTrue();
assertThat(startedIntent.getParcelableExtra(
KeyguardManager.EXTRA_START_LOCKSCREEN_VALIDATION_REQUEST,
StartLockscreenValidationRequest.class)
).isEqualTo(request);
assertThat(startedIntent.getParcelableExtra(
Intent.EXTRA_COMPONENT_NAME, ComponentName.class)
).isEqualTo(COMPONENT_NAME);
}
private ChooseLockSettingsHelper getChooseLockSettingsHelper( private ChooseLockSettingsHelper getChooseLockSettingsHelper(
ChooseLockSettingsHelper.Builder builder) { ChooseLockSettingsHelper.Builder builder) {
LockPatternUtils mockLockPatternUtils = mock(LockPatternUtils.class); LockPatternUtils mockLockPatternUtils = mock(LockPatternUtils.class);

View File

@@ -16,25 +16,162 @@
package com.android.settings.password; package com.android.settings.password;
import static com.android.settings.password.TestUtils.NO_MORE_REMAINING_ATTEMPTS;
import static com.android.settings.password.TestUtils.PACKAGE_NAME;
import static com.android.settings.password.TestUtils.SERVICE_NAME;
import static com.android.settings.password.TestUtils.VALID_REMAINING_ATTEMPTS;
import static com.android.settings.password.TestUtils.buildConfirmDeviceCredentialBaseActivity;
import static com.android.settings.password.TestUtils.createPackageInfoWithService;
import static com.android.settings.password.TestUtils.createRemoteLockscreenValidationIntent;
import static com.android.settings.password.TestUtils.createStartLockscreenValidationRequest;
import static com.android.settings.password.TestUtils.getConfirmDeviceCredentialBaseFragment;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import android.Manifest;
import android.app.KeyguardManager;
import android.app.admin.ManagedSubscriptionsPolicy;
import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.util.FeatureFlagUtils;
import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.test.core.app.ApplicationProvider;
import com.android.settings.password.ConfirmDeviceCredentialBaseFragment.LastTryDialog; import com.android.settings.password.ConfirmDeviceCredentialBaseFragment.LastTryDialog;
import com.android.settings.testutils.shadow.ShadowDevicePolicyManager;
import com.android.settings.testutils.shadow.ShadowLockPatternUtils;
import com.android.settings.testutils.shadow.ShadowUserManager;
import com.android.settings.testutils.shadow.ShadowUtils;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric; import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment; import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplicationPackageManager;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@Config(shadows = {
ShadowLockPatternUtils.class,
ShadowUtils.class,
ShadowDevicePolicyManager.class,
ShadowUserManager.class,
ShadowApplicationPackageManager.class
})
public class ConfirmCredentialTest { public class ConfirmCredentialTest {
private Context mContext = RuntimeEnvironment.application; private Context mContext;
private ShadowApplicationPackageManager mShadowApplicationPackageManager;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = ApplicationProvider.getApplicationContext();
mShadowApplicationPackageManager =
(ShadowApplicationPackageManager) Shadows.shadowOf(mContext.getPackageManager());
mShadowApplicationPackageManager.addPackageNoDefaults(
TestUtils.createPackageInfoWithService(
PACKAGE_NAME, SERVICE_NAME,
Manifest.permission.BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE));
final ShadowDevicePolicyManager shadowDpm = ShadowDevicePolicyManager.getShadow();
shadowDpm.setManagedSubscriptionsPolicy(
new ManagedSubscriptionsPolicy(
ManagedSubscriptionsPolicy.TYPE_ALL_PERSONAL_SUBSCRIPTIONS));
FeatureFlagUtils.setEnabled(mContext,
FeatureFlagUtils.SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION, true);
}
@Test
public void onCreate_successfullyStart() {
ConfirmDeviceCredentialBaseActivity activity =
buildConfirmDeviceCredentialBaseActivity(ConfirmLockPassword.class, new Intent());
ConfirmDeviceCredentialBaseFragment fragment =
getConfirmDeviceCredentialBaseFragment(activity);
assertThat(activity.isFinishing()).isFalse();
assertThat(fragment.mRemoteValidation).isFalse();
}
@Test
public void onCreate_remoteValidation_successfullyStart() throws Exception {
ConfirmDeviceCredentialBaseActivity activity = buildConfirmDeviceCredentialBaseActivity(
ConfirmLockPassword.class, createRemoteLockscreenValidationIntent(
KeyguardManager.PASSWORD, VALID_REMAINING_ATTEMPTS));
ConfirmDeviceCredentialBaseFragment fragment =
getConfirmDeviceCredentialBaseFragment(activity);
assertThat(activity.isFinishing()).isFalse();
assertThat(fragment.mRemoteValidation).isTrue();
}
@Test
public void onCreate_remoteValidation_flagDisabled_finishActivity() throws Exception {
FeatureFlagUtils.setEnabled(mContext,
FeatureFlagUtils.SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION, false);
ConfirmDeviceCredentialBaseActivity activity = buildConfirmDeviceCredentialBaseActivity(
ConfirmLockPassword.class,
createRemoteLockscreenValidationIntent(
KeyguardManager.PASSWORD, VALID_REMAINING_ATTEMPTS));
assertThat(activity.isFinishing()).isTrue();
}
@Test
public void onCreate_remoteValidation_invalidServiceComponentName_finishActivity()
throws Exception {
Intent intentWithInvalidComponentName = new Intent()
.putExtra(ConfirmDeviceCredentialBaseFragment.IS_REMOTE_LOCKSCREEN_VALIDATION, true)
.putExtra(KeyguardManager.EXTRA_START_LOCKSCREEN_VALIDATION_REQUEST,
createStartLockscreenValidationRequest(
KeyguardManager.PASSWORD, VALID_REMAINING_ATTEMPTS))
.putExtra(Intent.EXTRA_COMPONENT_NAME, new ComponentName("pkg", "cls"));
ConfirmDeviceCredentialBaseActivity activity = buildConfirmDeviceCredentialBaseActivity(
ConfirmLockPassword.class, intentWithInvalidComponentName);
assertThat(activity.isFinishing()).isTrue();
}
@Test
public void onCreate_remoteValidation_serviceDoesNotRequestCorrectPermission_finishActivity()
throws Exception {
// Remove package with valid ServiceInfo
mShadowApplicationPackageManager.removePackage(PACKAGE_NAME);
// Add a service that does not request the BIND_REMOTE_LOCKSCREEN_SERVICE permission
mShadowApplicationPackageManager.addPackageNoDefaults(
createPackageInfoWithService(
PACKAGE_NAME,
SERVICE_NAME,
Manifest.permission.BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE));
ConfirmDeviceCredentialBaseActivity activity = buildConfirmDeviceCredentialBaseActivity(
ConfirmLockPassword.class,
createRemoteLockscreenValidationIntent(
KeyguardManager.PASSWORD, VALID_REMAINING_ATTEMPTS));
assertThat(activity.isFinishing()).isTrue();
}
@Test
public void onCreate_remoteValidation_noMoreAttempts_finishActivity() throws Exception {
ConfirmDeviceCredentialBaseActivity activity = buildConfirmDeviceCredentialBaseActivity(
ConfirmLockPassword.class,
createRemoteLockscreenValidationIntent(
KeyguardManager.PASSWORD, NO_MORE_REMAINING_ATTEMPTS));
assertThat(activity.isFinishing()).isTrue();
}
@Test @Test
public void testLastTryDialogShownExactlyOnce() { public void testLastTryDialogShownExactlyOnce() {

View File

@@ -0,0 +1,308 @@
/*
* 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.password;
import static com.android.settings.password.ConfirmLockPassword.ConfirmLockPasswordFragment;
import static com.android.settings.password.TestUtils.GUESS_INVALID_RESULT;
import static com.android.settings.password.TestUtils.GUESS_VALID_RESULT;
import static com.android.settings.password.TestUtils.LOCKOUT_RESULT;
import static com.android.settings.password.TestUtils.NO_REMAINING_ATTEMPTS_RESULT;
import static com.android.settings.password.TestUtils.PACKAGE_NAME;
import static com.android.settings.password.TestUtils.SERVICE_NAME;
import static com.android.settings.password.TestUtils.TIMEOUT_MS;
import static com.android.settings.password.TestUtils.VALID_REMAINING_ATTEMPTS;
import static com.android.settings.password.TestUtils.buildConfirmDeviceCredentialBaseActivity;
import static com.android.settings.password.TestUtils.createRemoteLockscreenValidationIntent;
import static com.android.settings.password.TestUtils.getConfirmDeviceCredentialBaseFragment;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import android.Manifest;
import android.app.KeyguardManager;
import android.app.admin.ManagedSubscriptionsPolicy;
import android.content.Context;
import android.content.Intent;
import android.os.UserHandle;
import android.service.remotelockscreenvalidation.IRemoteLockscreenValidationCallback;
import android.service.remotelockscreenvalidation.RemoteLockscreenValidationClient;
import android.text.InputType;
import android.util.FeatureFlagUtils;
import android.widget.ImeAwareEditText;
import androidx.test.core.app.ApplicationProvider;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import com.android.settings.testutils.shadow.ShadowDevicePolicyManager;
import com.android.settings.testutils.shadow.ShadowLockPatternUtils;
import com.android.settings.testutils.shadow.ShadowUserManager;
import com.android.settings.testutils.shadow.ShadowUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplicationPackageManager;
import org.robolectric.util.ReflectionHelpers;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {
ShadowLockPatternUtils.class,
ShadowUtils.class,
ShadowDevicePolicyManager.class,
ShadowUserManager.class,
ShadowApplicationPackageManager.class
})
public class ConfirmLockPasswordTest {
@Mock
CredentialCheckResultTracker mCredentialCheckResultTracker;
@Mock
RemoteLockscreenValidationClient mRemoteLockscreenValidationClient;
@Captor
ArgumentCaptor<IRemoteLockscreenValidationCallback> mCallbackCaptor;
private Context mContext;
private LockPatternUtils mLockPatternUtils;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = ApplicationProvider.getApplicationContext();
mLockPatternUtils = new LockPatternUtils(mContext);
ShadowApplicationPackageManager shadowApplicationPackageManager =
(ShadowApplicationPackageManager) Shadows.shadowOf(mContext.getPackageManager());
shadowApplicationPackageManager.addPackageNoDefaults(
TestUtils.createPackageInfoWithService(
PACKAGE_NAME,
SERVICE_NAME,
Manifest.permission.BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE));
final ShadowDevicePolicyManager shadowDpm = ShadowDevicePolicyManager.getShadow();
shadowDpm.setManagedSubscriptionsPolicy(
new ManagedSubscriptionsPolicy(
ManagedSubscriptionsPolicy.TYPE_ALL_PERSONAL_SUBSCRIPTIONS));
// Set false by default so we can check if lock was set when remote validation succeeds.
ShadowLockPatternUtils.setIsSecure(UserHandle.myUserId(), false);
FeatureFlagUtils.setEnabled(mContext,
FeatureFlagUtils.SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION, true);
}
@After
public void tearDown() {
ShadowLockPatternUtils.reset();
}
@Test
public void onCreate_remoteValidation_password_successfullyStart() throws Exception {
ConfirmDeviceCredentialBaseActivity activity =
buildConfirmDeviceCredentialBaseActivity(
ConfirmLockPassword.class,
createRemoteLockscreenValidationIntent(
KeyguardManager.PASSWORD, VALID_REMAINING_ATTEMPTS));
ConfirmLockPasswordFragment fragment =
(ConfirmLockPasswordFragment) getConfirmDeviceCredentialBaseFragment(activity);
assertThat(activity.isFinishing()).isFalse();
assertThat(fragment.mRemoteValidation).isTrue();
ImeAwareEditText editText = (ImeAwareEditText) activity.findViewById(R.id.password_entry);
assertThat(editText.getInputType()).isEqualTo(
InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
}
@Test
public void onCreate_remoteValidation_pin_successfullyStart() throws Exception {
ConfirmDeviceCredentialBaseActivity activity =
buildConfirmDeviceCredentialBaseActivity(
ConfirmLockPassword.class,
createRemoteLockscreenValidationIntent(
KeyguardManager.PIN, VALID_REMAINING_ATTEMPTS));
ConfirmLockPasswordFragment fragment =
(ConfirmLockPasswordFragment) getConfirmDeviceCredentialBaseFragment(activity);
assertThat(activity.isFinishing()).isFalse();
assertThat(fragment.mRemoteValidation).isTrue();
ImeAwareEditText editText = (ImeAwareEditText) activity.findViewById(R.id.password_entry);
assertThat(editText.getInputType()).isEqualTo(
InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
}
@Test
public void handleNext_normalFlow_doesNotAttemptRemoteLockscreenValidation() {
ConfirmLockPassword activity = Robolectric.buildActivity(
ConfirmLockPassword.class, new Intent()).setup().get();
ConfirmLockPasswordFragment fragment =
(ConfirmLockPasswordFragment) getConfirmDeviceCredentialBaseFragment(activity);
ImeAwareEditText passwordEntry = activity.findViewById(R.id.password_entry);
fragment.mRemoteLockscreenValidationClient = mRemoteLockscreenValidationClient;
triggerHandleNext(fragment, passwordEntry);
verifyNoInteractions(mRemoteLockscreenValidationClient);
}
@Test
public void handleNext_remoteValidation_correctGuess_checkboxChecked() throws Exception {
ConfirmDeviceCredentialBaseActivity activity =
buildConfirmDeviceCredentialBaseActivity(
ConfirmLockPassword.class,
createRemoteLockscreenValidationIntent(
KeyguardManager.PASSWORD, VALID_REMAINING_ATTEMPTS));
ConfirmLockPasswordFragment fragment =
(ConfirmLockPasswordFragment) getConfirmDeviceCredentialBaseFragment(activity);
ReflectionHelpers.setField(fragment,
"mCredentialCheckResultTracker", mCredentialCheckResultTracker);
ImeAwareEditText passwordEntry = activity.findViewById(R.id.password_entry);
fragment.mRemoteLockscreenValidationClient = mRemoteLockscreenValidationClient;
triggerHandleNext(fragment, passwordEntry);
verify(mRemoteLockscreenValidationClient)
.validateLockscreenGuess(any(), mCallbackCaptor.capture());
mCallbackCaptor.getValue().onSuccess(GUESS_VALID_RESULT);
verify(mCredentialCheckResultTracker).setResult(
eq(true), any(), eq(0), eq(fragment.mEffectiveUserId));
assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isTrue();
assertThat(fragment.mDeviceCredentialGuess).isNotNull();
}
@Test
public void handleNext_remoteValidation_correctGuess_checkboxUnchecked() throws Exception {
ConfirmDeviceCredentialBaseActivity activity =
buildConfirmDeviceCredentialBaseActivity(
ConfirmLockPassword.class,
createRemoteLockscreenValidationIntent(
KeyguardManager.PASSWORD, VALID_REMAINING_ATTEMPTS));
ConfirmLockPasswordFragment fragment =
(ConfirmLockPasswordFragment) getConfirmDeviceCredentialBaseFragment(activity);
ReflectionHelpers.setField(fragment,
"mCredentialCheckResultTracker", mCredentialCheckResultTracker);
fragment.mCheckBox.setChecked(false);
ImeAwareEditText passwordEntry = activity.findViewById(R.id.password_entry);
fragment.mRemoteLockscreenValidationClient = mRemoteLockscreenValidationClient;
triggerHandleNext(fragment, passwordEntry);
verify(mRemoteLockscreenValidationClient)
.validateLockscreenGuess(any(), mCallbackCaptor.capture());
mCallbackCaptor.getValue().onSuccess(GUESS_VALID_RESULT);
verify(mCredentialCheckResultTracker).setResult(
eq(true), any(), eq(0), eq(fragment.mEffectiveUserId));
assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isFalse();
assertThat(fragment.mDeviceCredentialGuess).isNull();
}
@Test
public void handleNext_remoteValidation_guessInvalid() throws Exception {
ConfirmDeviceCredentialBaseActivity activity =
buildConfirmDeviceCredentialBaseActivity(
ConfirmLockPassword.class,
createRemoteLockscreenValidationIntent(
KeyguardManager.PASSWORD, VALID_REMAINING_ATTEMPTS));
ConfirmLockPasswordFragment fragment =
(ConfirmLockPasswordFragment) getConfirmDeviceCredentialBaseFragment(activity);
ReflectionHelpers.setField(fragment,
"mCredentialCheckResultTracker", mCredentialCheckResultTracker);
ImeAwareEditText passwordEntry = activity.findViewById(R.id.password_entry);
fragment.mRemoteLockscreenValidationClient = mRemoteLockscreenValidationClient;
triggerHandleNext(fragment, passwordEntry);
verify(mRemoteLockscreenValidationClient)
.validateLockscreenGuess(any(), mCallbackCaptor.capture());
mCallbackCaptor.getValue().onSuccess(GUESS_INVALID_RESULT);
verify(mCredentialCheckResultTracker).setResult(
eq(false), any(), eq(0), eq(fragment.mEffectiveUserId));
assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isFalse();
}
@Test
public void handleNext_remoteValidation_lockout() throws Exception {
ConfirmDeviceCredentialBaseActivity activity =
buildConfirmDeviceCredentialBaseActivity(
ConfirmLockPassword.class,
createRemoteLockscreenValidationIntent(
KeyguardManager.PASSWORD, VALID_REMAINING_ATTEMPTS));
ConfirmLockPasswordFragment fragment =
(ConfirmLockPasswordFragment) getConfirmDeviceCredentialBaseFragment(activity);
ReflectionHelpers.setField(fragment,
"mCredentialCheckResultTracker", mCredentialCheckResultTracker);
ImeAwareEditText passwordEntry = activity.findViewById(R.id.password_entry);
fragment.mRemoteLockscreenValidationClient = mRemoteLockscreenValidationClient;
triggerHandleNext(fragment, passwordEntry);
verify(mRemoteLockscreenValidationClient)
.validateLockscreenGuess(any(), mCallbackCaptor.capture());
mCallbackCaptor.getValue().onSuccess(LOCKOUT_RESULT);
verify(mCredentialCheckResultTracker).setResult(
eq(false), any(), eq(TIMEOUT_MS), eq(fragment.mEffectiveUserId));
assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isFalse();
}
@Test
public void handleNext_remoteValidation_noRemainingAttempts_finishActivity() throws Exception {
ConfirmDeviceCredentialBaseActivity activity =
buildConfirmDeviceCredentialBaseActivity(
ConfirmLockPassword.class,
createRemoteLockscreenValidationIntent(
KeyguardManager.PASSWORD, VALID_REMAINING_ATTEMPTS));
ConfirmLockPasswordFragment fragment =
(ConfirmLockPasswordFragment) getConfirmDeviceCredentialBaseFragment(activity);
ReflectionHelpers.setField(fragment,
"mCredentialCheckResultTracker", mCredentialCheckResultTracker);
ImeAwareEditText passwordEntry = activity.findViewById(R.id.password_entry);
fragment.mRemoteLockscreenValidationClient = mRemoteLockscreenValidationClient;
triggerHandleNext(fragment, passwordEntry);
verify(mRemoteLockscreenValidationClient)
.validateLockscreenGuess(any(), mCallbackCaptor.capture());
mCallbackCaptor.getValue().onSuccess(NO_REMAINING_ATTEMPTS_RESULT);
assertThat(activity.isFinishing()).isTrue();
verify(mCredentialCheckResultTracker, never())
.setResult(anyBoolean(), any(), anyInt(), anyInt());
assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isFalse();
}
private void triggerHandleNext(
ConfirmLockPasswordFragment fragment, ImeAwareEditText passwordEntry) {
passwordEntry.setText("Password");
ReflectionHelpers.callInstanceMethod(fragment, "handleNext");
}
}

View File

@@ -0,0 +1,286 @@
/*
* 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.password;
import static com.android.settings.password.TestUtils.GUESS_INVALID_RESULT;
import static com.android.settings.password.TestUtils.GUESS_VALID_RESULT;
import static com.android.settings.password.TestUtils.LOCKOUT_RESULT;
import static com.android.settings.password.TestUtils.NO_REMAINING_ATTEMPTS_RESULT;
import static com.android.settings.password.TestUtils.PACKAGE_NAME;
import static com.android.settings.password.TestUtils.SERVICE_NAME;
import static com.android.settings.password.TestUtils.TIMEOUT_MS;
import static com.android.settings.password.TestUtils.VALID_REMAINING_ATTEMPTS;
import static com.android.settings.password.TestUtils.buildConfirmDeviceCredentialBaseActivity;
import static com.android.settings.password.TestUtils.createPackageInfoWithService;
import static com.android.settings.password.TestUtils.createRemoteLockscreenValidationIntent;
import static com.android.settings.password.TestUtils.getConfirmDeviceCredentialBaseFragment;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import android.Manifest;
import android.app.KeyguardManager;
import android.app.admin.ManagedSubscriptionsPolicy;
import android.content.Context;
import android.content.Intent;
import android.os.UserHandle;
import android.service.remotelockscreenvalidation.IRemoteLockscreenValidationCallback;
import android.service.remotelockscreenvalidation.RemoteLockscreenValidationClient;
import android.util.FeatureFlagUtils;
import androidx.test.core.app.ApplicationProvider;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternView;
import com.android.settings.R;
import com.android.settings.testutils.shadow.ShadowDevicePolicyManager;
import com.android.settings.testutils.shadow.ShadowLockPatternUtils;
import com.android.settings.testutils.shadow.ShadowUserManager;
import com.android.settings.testutils.shadow.ShadowUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplicationPackageManager;
import org.robolectric.util.ReflectionHelpers;
import java.util.List;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {
ShadowLockPatternUtils.class,
ShadowUtils.class,
ShadowDevicePolicyManager.class,
ShadowUserManager.class,
ShadowApplicationPackageManager.class
})
public class ConfirmLockPatternTest {
@Mock
CredentialCheckResultTracker mCredentialCheckResultTracker;
@Mock
RemoteLockscreenValidationClient mRemoteLockscreenValidationClient;
@Captor
ArgumentCaptor<IRemoteLockscreenValidationCallback> mCallbackCaptor;
private Context mContext;
private LockPatternUtils mLockPatternUtils;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = ApplicationProvider.getApplicationContext();
mLockPatternUtils = new LockPatternUtils(mContext);
ShadowApplicationPackageManager shadowApplicationPackageManager =
(ShadowApplicationPackageManager) Shadows.shadowOf(mContext.getPackageManager());
shadowApplicationPackageManager.addPackageNoDefaults(
createPackageInfoWithService(
PACKAGE_NAME,
SERVICE_NAME,
Manifest.permission.BIND_REMOTE_LOCKSCREEN_VALIDATION_SERVICE));
final ShadowDevicePolicyManager shadowDpm = ShadowDevicePolicyManager.getShadow();
shadowDpm.setManagedSubscriptionsPolicy(
new ManagedSubscriptionsPolicy(
ManagedSubscriptionsPolicy.TYPE_ALL_PERSONAL_SUBSCRIPTIONS));
// Set false by default so we can check if lock was set when remote validation succeeds.
ShadowLockPatternUtils.setIsSecure(UserHandle.myUserId(), false);
FeatureFlagUtils.setEnabled(mContext,
FeatureFlagUtils.SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION, true);
}
@After
public void tearDown() {
ShadowLockPatternUtils.reset();
}
@Test
public void onCreate_remoteValidation_successfullyStart() throws Exception {
ConfirmDeviceCredentialBaseActivity activity =
buildConfirmDeviceCredentialBaseActivity(
ConfirmLockPattern.class,
createRemoteLockscreenValidationIntent(
KeyguardManager.PATTERN, VALID_REMAINING_ATTEMPTS));
ConfirmDeviceCredentialBaseFragment fragment =
getConfirmDeviceCredentialBaseFragment(activity);
assertThat(activity.isFinishing()).isFalse();
assertThat(fragment.mRemoteValidation).isTrue();
LockPatternView lockPatternView = (LockPatternView) activity.findViewById(R.id.lockPattern);
assertThat(lockPatternView.isInStealthMode()).isFalse();
}
@Test
public void onPatternDetected_normalFlow_doesNotAttemptRemoteLockscreenValidation() {
ConfirmLockPattern activity = Robolectric.buildActivity(
ConfirmLockPattern.class, new Intent()).setup().get();
ConfirmDeviceCredentialBaseFragment fragment =
getConfirmDeviceCredentialBaseFragment(activity);
LockPatternView lockPatternView = activity.findViewById(R.id.lockPattern);
fragment.mRemoteLockscreenValidationClient = mRemoteLockscreenValidationClient;
triggerOnPatternDetected(lockPatternView);
verifyNoInteractions(mRemoteLockscreenValidationClient);
}
@Test
public void onPatternDetected_remoteValidation_guessValid_checkboxChecked() throws Exception {
ConfirmDeviceCredentialBaseActivity activity =
buildConfirmDeviceCredentialBaseActivity(
ConfirmLockPattern.class,
createRemoteLockscreenValidationIntent(
KeyguardManager.PATTERN, VALID_REMAINING_ATTEMPTS));
ConfirmDeviceCredentialBaseFragment fragment =
getConfirmDeviceCredentialBaseFragment(activity);
LockPatternView lockPatternView = activity.findViewById(R.id.lockPattern);
ReflectionHelpers.setField(fragment,
"mCredentialCheckResultTracker", mCredentialCheckResultTracker);
fragment.mRemoteLockscreenValidationClient = mRemoteLockscreenValidationClient;
triggerOnPatternDetected(lockPatternView);
verify(mRemoteLockscreenValidationClient)
.validateLockscreenGuess(any(), mCallbackCaptor.capture());
mCallbackCaptor.getValue().onSuccess(GUESS_VALID_RESULT);
verify(mCredentialCheckResultTracker).setResult(
eq(true), any(), eq(0), eq(fragment.mEffectiveUserId));
assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isTrue();
assertThat(fragment.mDeviceCredentialGuess).isNotNull();
}
@Test
public void onPatternDetected_remoteValidation_guessValid_checkboxUnchecked() throws Exception {
ConfirmDeviceCredentialBaseActivity activity =
buildConfirmDeviceCredentialBaseActivity(
ConfirmLockPattern.class,
createRemoteLockscreenValidationIntent(
KeyguardManager.PATTERN, VALID_REMAINING_ATTEMPTS));
ConfirmDeviceCredentialBaseFragment fragment =
getConfirmDeviceCredentialBaseFragment(activity);
LockPatternView lockPatternView = activity.findViewById(R.id.lockPattern);
ReflectionHelpers.setField(fragment,
"mCredentialCheckResultTracker", mCredentialCheckResultTracker);
fragment.mCheckBox.setChecked(false);
fragment.mRemoteLockscreenValidationClient = mRemoteLockscreenValidationClient;
triggerOnPatternDetected(lockPatternView);
verify(mRemoteLockscreenValidationClient)
.validateLockscreenGuess(any(), mCallbackCaptor.capture());
mCallbackCaptor.getValue().onSuccess(GUESS_VALID_RESULT);
verify(mCredentialCheckResultTracker).setResult(
eq(true), any(), eq(0), eq(fragment.mEffectiveUserId));
assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isFalse();
assertThat(fragment.mDeviceCredentialGuess).isNull();
}
@Test
public void onPatternDetected_remoteValidation_guessInvalid() throws Exception {
ConfirmDeviceCredentialBaseActivity activity =
buildConfirmDeviceCredentialBaseActivity(
ConfirmLockPattern.class,
createRemoteLockscreenValidationIntent(
KeyguardManager.PATTERN, VALID_REMAINING_ATTEMPTS));
ConfirmDeviceCredentialBaseFragment fragment =
getConfirmDeviceCredentialBaseFragment(activity);
LockPatternView lockPatternView = activity.findViewById(R.id.lockPattern);
ReflectionHelpers.setField(fragment,
"mCredentialCheckResultTracker", mCredentialCheckResultTracker);
fragment.mRemoteLockscreenValidationClient = mRemoteLockscreenValidationClient;
triggerOnPatternDetected(lockPatternView);
verify(mRemoteLockscreenValidationClient)
.validateLockscreenGuess(any(), mCallbackCaptor.capture());
mCallbackCaptor.getValue().onSuccess(GUESS_INVALID_RESULT);
verify(mCredentialCheckResultTracker).setResult(
eq(false), any(), eq(0), eq(fragment.mEffectiveUserId));
assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isFalse();
}
@Test
public void onPatternDetected_remoteValidation_lockout() throws Exception {
ConfirmDeviceCredentialBaseActivity activity =
buildConfirmDeviceCredentialBaseActivity(
ConfirmLockPattern.class,
createRemoteLockscreenValidationIntent(
KeyguardManager.PATTERN, VALID_REMAINING_ATTEMPTS));
ConfirmDeviceCredentialBaseFragment fragment =
getConfirmDeviceCredentialBaseFragment(activity);
LockPatternView lockPatternView = activity.findViewById(R.id.lockPattern);
ReflectionHelpers.setField(fragment,
"mCredentialCheckResultTracker", mCredentialCheckResultTracker);
fragment.mRemoteLockscreenValidationClient = mRemoteLockscreenValidationClient;
triggerOnPatternDetected(lockPatternView);
verify(mRemoteLockscreenValidationClient)
.validateLockscreenGuess(any(), mCallbackCaptor.capture());
mCallbackCaptor.getValue().onSuccess(LOCKOUT_RESULT);
verify(mCredentialCheckResultTracker).setResult(
eq(false), any(), eq(TIMEOUT_MS), eq(fragment.mEffectiveUserId));
assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isFalse();
}
@Test
public void onPatternDetected_noRemainingAttempts_finishActivity() throws Exception {
ConfirmDeviceCredentialBaseActivity activity =
buildConfirmDeviceCredentialBaseActivity(
ConfirmLockPattern.class,
createRemoteLockscreenValidationIntent(
KeyguardManager.PATTERN, VALID_REMAINING_ATTEMPTS));
ConfirmDeviceCredentialBaseFragment fragment =
getConfirmDeviceCredentialBaseFragment(activity);
LockPatternView lockPatternView = activity.findViewById(R.id.lockPattern);
ReflectionHelpers.setField(fragment,
"mCredentialCheckResultTracker", mCredentialCheckResultTracker);
fragment.mRemoteLockscreenValidationClient = mRemoteLockscreenValidationClient;
triggerOnPatternDetected(lockPatternView);
verify(mRemoteLockscreenValidationClient)
.validateLockscreenGuess(any(), mCallbackCaptor.capture());
mCallbackCaptor.getValue().onSuccess(NO_REMAINING_ATTEMPTS_RESULT);
assertThat(activity.isFinishing()).isTrue();
verifyNoInteractions(mCredentialCheckResultTracker);
assertThat(mLockPatternUtils.isSecure(fragment.mEffectiveUserId)).isFalse();
}
private void triggerOnPatternDetected(LockPatternView lockPatternView) {
List<LockPatternView.Cell> pattern = List.of(LockPatternView.Cell.of(0, 0));
lockPatternView.setPattern(LockPatternView.DisplayMode.Correct, pattern);
ReflectionHelpers.callInstanceMethod(lockPatternView, "notifyPatternDetected");
}
}

View File

@@ -0,0 +1,107 @@
/*
* 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.password;
import android.app.KeyguardManager;
import android.app.RemoteLockscreenValidationResult;
import android.app.StartLockscreenValidationRequest;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.ServiceInfo;
import com.android.security.SecureBox;
import com.android.settings.R;
import org.robolectric.Robolectric;
import java.security.NoSuchAlgorithmException;
public final class TestUtils {
public static final String SERVICE_NAME = "SERVICE_NAME";
public static final String PACKAGE_NAME = "PACKAGE_NAME";
public static final ComponentName COMPONENT_NAME =
new ComponentName(PACKAGE_NAME, SERVICE_NAME);
public static final int VALID_REMAINING_ATTEMPTS = 5;
public static final int NO_MORE_REMAINING_ATTEMPTS = 0;
public static final int TIMEOUT_MS = 10000;
public static final RemoteLockscreenValidationResult GUESS_VALID_RESULT =
new RemoteLockscreenValidationResult.Builder()
.setResultCode(RemoteLockscreenValidationResult.RESULT_GUESS_VALID)
.build();
public static final RemoteLockscreenValidationResult GUESS_INVALID_RESULT =
new RemoteLockscreenValidationResult.Builder()
.setResultCode(RemoteLockscreenValidationResult.RESULT_GUESS_INVALID)
.build();
public static final RemoteLockscreenValidationResult LOCKOUT_RESULT =
new RemoteLockscreenValidationResult.Builder()
.setResultCode(RemoteLockscreenValidationResult.RESULT_LOCKOUT)
.setTimeoutMillis(TIMEOUT_MS)
.build();
public static final RemoteLockscreenValidationResult NO_REMAINING_ATTEMPTS_RESULT =
new RemoteLockscreenValidationResult.Builder()
.setResultCode(RemoteLockscreenValidationResult.RESULT_NO_REMAINING_ATTEMPTS)
.build();
private TestUtils() {
}
public static PackageInfo createPackageInfoWithService(
String packageName, String serviceName, String requiredServicePermission) {
ServiceInfo serviceInfo = new ServiceInfo();
serviceInfo.name = serviceName;
serviceInfo.applicationInfo = new ApplicationInfo();
serviceInfo.permission = requiredServicePermission;
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = packageName;
packageInfo.services = new ServiceInfo[]{serviceInfo};
return packageInfo;
}
public static Intent createRemoteLockscreenValidationIntent(
int lockscreenType, int remainingAttempts) throws Exception {
return new Intent()
.putExtra(ConfirmDeviceCredentialBaseFragment.IS_REMOTE_LOCKSCREEN_VALIDATION, true)
.putExtra(KeyguardManager.EXTRA_START_LOCKSCREEN_VALIDATION_REQUEST,
createStartLockscreenValidationRequest(lockscreenType, remainingAttempts))
.putExtra(Intent.EXTRA_COMPONENT_NAME, COMPONENT_NAME);
}
public static StartLockscreenValidationRequest createStartLockscreenValidationRequest(
int lockscreenType, int remainingAttempts) throws NoSuchAlgorithmException {
return new StartLockscreenValidationRequest.Builder()
.setLockscreenUiType(lockscreenType)
.setRemainingAttempts(remainingAttempts)
.setSourcePublicKey(SecureBox.genKeyPair().getPublic().getEncoded())
.build();
}
public static ConfirmDeviceCredentialBaseActivity buildConfirmDeviceCredentialBaseActivity(
Class<? extends ConfirmDeviceCredentialBaseActivity> impl, Intent intent) {
return Robolectric.buildActivity(impl, intent).setup().get();
}
public static ConfirmDeviceCredentialBaseFragment getConfirmDeviceCredentialBaseFragment(
ConfirmDeviceCredentialBaseActivity activity) {
return (ConfirmDeviceCredentialBaseFragment)
activity.getSupportFragmentManager().findFragmentById(R.id.main_content);
}
}

View File

@@ -8,6 +8,7 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt; import android.annotation.UserIdInt;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManager.DeviceOwnerType; import android.app.admin.DevicePolicyManager.DeviceOwnerType;
import android.app.admin.ManagedSubscriptionsPolicy;
import android.app.admin.PasswordMetrics; import android.app.admin.PasswordMetrics;
import android.app.admin.PasswordPolicy; import android.app.admin.PasswordPolicy;
import android.content.ComponentName; import android.content.ComponentName;
@@ -29,6 +30,7 @@ public class ShadowDevicePolicyManager extends org.robolectric.shadows.ShadowDev
private Map<Integer, CharSequence> mSupportMessagesMap = new HashMap<>(); private Map<Integer, CharSequence> mSupportMessagesMap = new HashMap<>();
private boolean mIsAdminActiveAsUser = false; private boolean mIsAdminActiveAsUser = false;
private ComponentName mDeviceOwnerComponentName; private ComponentName mDeviceOwnerComponentName;
private ManagedSubscriptionsPolicy mManagedSubscriptionsPolicy;
private int mDeviceOwnerUserId = -1; private int mDeviceOwnerUserId = -1;
private int mPasswordMinQuality = PASSWORD_QUALITY_UNSPECIFIED; private int mPasswordMinQuality = PASSWORD_QUALITY_UNSPECIFIED;
private int mPasswordMinLength = 0; private int mPasswordMinLength = 0;
@@ -85,6 +87,10 @@ public class ShadowDevicePolicyManager extends org.robolectric.shadows.ShadowDev
mDeviceOwnerTypes.put(admin.getPackageName(), deviceOwnerType); mDeviceOwnerTypes.put(admin.getPackageName(), deviceOwnerType);
} }
public void setManagedSubscriptionsPolicy(ManagedSubscriptionsPolicy policy) {
mManagedSubscriptionsPolicy = policy;
}
@DeviceOwnerType @DeviceOwnerType
public int getDeviceOwnerType(@NonNull ComponentName admin) { public int getDeviceOwnerType(@NonNull ComponentName admin) {
return mDeviceOwnerTypes.getOrDefault(admin.getPackageName(), DEVICE_OWNER_TYPE_DEFAULT); return mDeviceOwnerTypes.getOrDefault(admin.getPackageName(), DEVICE_OWNER_TYPE_DEFAULT);
@@ -99,6 +105,11 @@ public class ShadowDevicePolicyManager extends org.robolectric.shadows.ShadowDev
return policy.getMinMetrics(); return policy.getMinMetrics();
} }
@Implementation
public ManagedSubscriptionsPolicy getManagedSubscriptionsPolicy() {
return mManagedSubscriptionsPolicy;
}
public void setPasswordQuality(int quality) { public void setPasswordQuality(int quality) {
mPasswordMinQuality = quality; mPasswordMinQuality = quality;
} }

View File

@@ -16,6 +16,8 @@
package com.android.settings.testutils.shadow; package com.android.settings.testutils.shadow;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManager;
import android.app.admin.PasswordMetrics; import android.app.admin.PasswordMetrics;
import android.content.ComponentName; import android.content.ComponentName;
@@ -43,6 +45,10 @@ public class ShadowLockPatternUtils {
private static Map<Integer, PasswordMetrics> sUserToMetricsMap = new HashMap<>(); private static Map<Integer, PasswordMetrics> sUserToMetricsMap = new HashMap<>();
private static Map<Integer, PasswordMetrics> sUserToProfileMetricsMap = new HashMap<>(); private static Map<Integer, PasswordMetrics> sUserToProfileMetricsMap = new HashMap<>();
private static Map<Integer, Boolean> sUserToIsSecureMap = new HashMap<>(); private static Map<Integer, Boolean> sUserToIsSecureMap = new HashMap<>();
private static Map<Integer, Boolean> sUserToPatternEverChosenMap = new HashMap<>();
private static Map<Integer, Boolean> sUserToVisiblePatternEnabledMap = new HashMap<>();
private static Map<Integer, Boolean> sUserToBiometricAllowedMap = new HashMap<>();
private static Map<Integer, Boolean> sUserToLockPatternEnabledMap = new HashMap<>();
private static boolean sIsUserOwnsFrpCredential; private static boolean sIsUserOwnsFrpCredential;
@@ -53,6 +59,10 @@ public class ShadowLockPatternUtils {
sUserToMetricsMap.clear(); sUserToMetricsMap.clear();
sUserToProfileMetricsMap.clear(); sUserToProfileMetricsMap.clear();
sUserToIsSecureMap.clear(); sUserToIsSecureMap.clear();
sUserToPatternEverChosenMap.clear();
sUserToVisiblePatternEnabledMap.clear();
sUserToBiometricAllowedMap.clear();
sUserToLockPatternEnabledMap.clear();
sDeviceEncryptionEnabled = false; sDeviceEncryptionEnabled = false;
sIsUserOwnsFrpCredential = false; sIsUserOwnsFrpCredential = false;
} }
@@ -136,6 +146,56 @@ public class ShadowLockPatternUtils {
sIsUserOwnsFrpCredential = isUserOwnsFrpCredential; sIsUserOwnsFrpCredential = isUserOwnsFrpCredential;
} }
@Implementation
public boolean isVisiblePatternEnabled(int userId) {
return sUserToVisiblePatternEnabledMap.getOrDefault(userId, false);
}
public static void setIsVisiblePatternEnabled(int userId, boolean isVisiblePatternEnabled) {
sUserToVisiblePatternEnabledMap.put(userId, isVisiblePatternEnabled);
}
@Implementation
public boolean isPatternEverChosen(int userId) {
return sUserToPatternEverChosenMap.getOrDefault(userId, true);
}
public static void setIsPatternEverChosen(int userId, boolean isPatternEverChosen) {
sUserToPatternEverChosenMap.put(userId, isPatternEverChosen);
}
@Implementation
public boolean isBiometricAllowedForUser(int userId) {
return sUserToBiometricAllowedMap.getOrDefault(userId, false);
}
public static void setIsBiometricAllowedForUser(int userId, boolean isBiometricAllowed) {
sUserToBiometricAllowedMap.put(userId, isBiometricAllowed);
}
@Implementation
public boolean isLockPatternEnabled(int userId) {
return sUserToBiometricAllowedMap.getOrDefault(userId, false);
}
public static void setIsLockPatternEnabled(int userId, boolean isLockPatternEnabled) {
sUserToLockPatternEnabledMap.put(userId, isLockPatternEnabled);
}
@Implementation
public boolean setLockCredential(@NonNull LockscreenCredential newCredential,
@NonNull LockscreenCredential savedCredential, int userHandle) {
setIsSecure(userHandle, true);
return true;
}
@Implementation
public boolean checkCredential(@NonNull LockscreenCredential credential, int userId,
@Nullable LockPatternUtils.CheckCredentialProgressCallback progressCallback)
throws LockPatternUtils.RequestThrottledException {
return true;
}
public static void setRequiredPasswordComplexity(int userHandle, int complexity) { public static void setRequiredPasswordComplexity(int userHandle, int complexity) {
sUserToComplexityMap.put(userHandle, complexity); sUserToComplexityMap.put(userHandle, complexity);
} }