Files
app_Settings/src/com/android/settings/password/ChooseLockGenericController.java
Pavel Grafov 36120b8a4c Respect PASSWORD_QUALITY_MANAGED for biometrics
Bug: 195488124
Test: make RunSettingsRoboTests -j ROBOTEST_FILTER=\
    com.android.settings.password.ChooseLockGenericControllerTest
Test: manual with a modified TestDPC
Change-Id: Ifa15877c735bb0f396051af04a4e012a606bf9e1
2021-09-13 11:50:06 +01:00

304 lines
13 KiB
Java

/*
* Copyright (C) 2017 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 android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.app.admin.PasswordMetrics;
import android.content.Context;
import android.os.UserHandle;
import android.os.UserManager;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.android.internal.widget.LockPatternUtils;
import com.android.settings.R;
import java.util.ArrayList;
import java.util.List;
/**
* A controller for ChooseLockGeneric, and other similar classes which shows a list of possible
* screen lock types for the user to choose from. This is the main place where different
* restrictions on allowed screen lock types are aggregated in Settings.
*
* Each screen lock type has two states: whether it is visible and whether it is enabled.
* Visibility is affected by things like resource configs, whether it's for a managed profile,
* or whether the caller allows it or not. This is determined by
* {@link #isScreenLockVisible(ScreenLockType)}. For visible screen lock types, they can be disabled
* by a combination of admin policies and request from the calling app, which is determined by
* {@link #isScreenLockEnabled(ScreenLockType}.
*/
public class ChooseLockGenericController {
private final Context mContext;
private final int mUserId;
private final boolean mHideInsecureScreenLockTypes;
@PasswordComplexity private final int mAppRequestedMinComplexity;
private final boolean mDevicePasswordRequirementOnly;
private final int mUnificationProfileId;
private final ManagedLockPasswordProvider mManagedPasswordProvider;
private final LockPatternUtils mLockPatternUtils;
public ChooseLockGenericController(Context context, int userId,
ManagedLockPasswordProvider managedPasswordProvider, LockPatternUtils lockPatternUtils,
boolean hideInsecureScreenLockTypes, int appRequestedMinComplexity,
boolean devicePasswordRequirementOnly, int unificationProfileId) {
mContext = context;
mUserId = userId;
mManagedPasswordProvider = managedPasswordProvider;
mLockPatternUtils = lockPatternUtils;
mHideInsecureScreenLockTypes = hideInsecureScreenLockTypes;
mAppRequestedMinComplexity = appRequestedMinComplexity;
mDevicePasswordRequirementOnly = devicePasswordRequirementOnly;
mUnificationProfileId = unificationProfileId;
}
/** Builder class for {@link ChooseLockGenericController} */
public static class Builder {
private final Context mContext;
private final int mUserId;
private final ManagedLockPasswordProvider mManagedPasswordProvider;
private final LockPatternUtils mLockPatternUtils;
private boolean mHideInsecureScreenLockTypes = false;
@PasswordComplexity private int mAppRequestedMinComplexity = PASSWORD_COMPLEXITY_NONE;
private boolean mDevicePasswordRequirementOnly = false;
private int mUnificationProfileId = UserHandle.USER_NULL;
public Builder(Context context, int userId) {
this(context, userId, new LockPatternUtils(context));
}
public Builder(Context context, int userId,
LockPatternUtils lockPatternUtils) {
this(
context,
userId,
ManagedLockPasswordProvider.get(context, userId),
lockPatternUtils);
}
@VisibleForTesting
Builder(
Context context,
int userId,
ManagedLockPasswordProvider managedLockPasswordProvider,
LockPatternUtils lockPatternUtils) {
mContext = context;
mUserId = userId;
mManagedPasswordProvider = managedLockPasswordProvider;
mLockPatternUtils = lockPatternUtils;
}
/**
* Sets the password complexity requested by the calling app via
* {@link android.app.admin.DevicePolicyManager#EXTRA_PASSWORD_COMPLEXITY}.
*/
public Builder setAppRequestedMinComplexity(int complexity) {
mAppRequestedMinComplexity = complexity;
return this;
}
/**
* Sets whether the enrolment flow should discard any password policies originating from the
* work profile, even if the work profile currently has unified challenge. This can be
* requested by the calling app via
* {@link android.app.admin.DevicePolicyManager#EXTRA_DEVICE_PASSWORD_REQUIREMENT_ONLY}.
*/
public Builder setEnforceDevicePasswordRequirementOnly(boolean deviceOnly) {
mDevicePasswordRequirementOnly = deviceOnly;
return this;
}
/**
* Sets the user ID of any profile whose work challenge should be unified at the end of this
* enrolment flow. This will lead to all password policies from that profile to be taken
* into consideration by this class, so that we are enrolling a compliant password. This is
* because once unified, the profile's password policy will be enforced on the new
* credential.
*/
public Builder setProfileToUnify(int profileId) {
mUnificationProfileId = profileId;
return this;
}
/**
* Sets whether insecure screen lock types (NONE and SWIPE) should be hidden in the UI.
*/
public Builder setHideInsecureScreenLockTypes(boolean hide) {
mHideInsecureScreenLockTypes = hide;
return this;
}
/** Creates {@link ChooseLockGenericController} instance. */
public ChooseLockGenericController build() {
return new ChooseLockGenericController(mContext, mUserId, mManagedPasswordProvider,
mLockPatternUtils, mHideInsecureScreenLockTypes, mAppRequestedMinComplexity,
mDevicePasswordRequirementOnly, mUnificationProfileId);
}
}
/**
* Returns whether the given screen lock type should be visible in the given context.
*/
public boolean isScreenLockVisible(ScreenLockType type) {
final boolean managedProfile = mContext.getSystemService(UserManager.class)
.isManagedProfile(mUserId);
switch (type) {
case NONE:
return !mHideInsecureScreenLockTypes
&& !mContext.getResources().getBoolean(R.bool.config_hide_none_security_option)
&& !managedProfile; // Profiles should use unified challenge instead.
case SWIPE:
return !mHideInsecureScreenLockTypes
&& !mContext.getResources().getBoolean(R.bool.config_hide_swipe_security_option)
&& !managedProfile; // Swipe doesn't make sense for profiles.
case MANAGED:
return mManagedPasswordProvider.isManagedPasswordChoosable();
case PIN:
case PATTERN:
case PASSWORD:
// Hide the secure lock screen options if the device doesn't support the secure lock
// screen feature.
return mLockPatternUtils.hasSecureLockScreen();
}
return true;
}
/**
* Whether screen lock with {@code type} should be enabled assuming all relevant password
* requirements. The lock's visibility ({@link #isScreenLockVisible}) is not considered here.
*/
public boolean isScreenLockEnabled(ScreenLockType type) {
return !mLockPatternUtils.isCredentialsDisabledForUser(mUserId)
&& type.maxQuality >= upgradeQuality(PASSWORD_QUALITY_UNSPECIFIED);
}
/**
* Increases the given quality to be as high as the combined quality from all relevant
* password requirements.
*/
// TODO(b/142781408): convert from quality to credential type once PIN is supported.
public int upgradeQuality(int quality) {
return Math.max(quality,
Math.max(
LockPatternUtils.credentialTypeToPasswordQuality(
getAggregatedPasswordMetrics().credType),
PasswordMetrics.complexityLevelToMinQuality(
getAggregatedPasswordComplexity())
)
);
}
/**
* User friendly title for the given screen lock type.
*/
public CharSequence getTitle(ScreenLockType type) {
switch (type) {
case NONE:
return mContext.getText(R.string.unlock_set_unlock_off_title);
case SWIPE:
return mContext.getText(R.string.unlock_set_unlock_none_title);
case PATTERN:
return mContext.getText(R.string.unlock_set_unlock_pattern_title);
case PIN:
return mContext.getText(R.string.unlock_set_unlock_pin_title);
case PASSWORD:
return mContext.getText(R.string.unlock_set_unlock_password_title);
case MANAGED:
return mManagedPasswordProvider.getPickerOptionTitle(false);
}
return null;
}
/**
* Gets a list of screen lock types that should be visible for the given quality. The returned
* list is ordered in the natural order of the enum (the order those enums were defined). Screen
* locks disabled by password policy will not be returned.
*/
@NonNull
public List<ScreenLockType> getVisibleAndEnabledScreenLockTypes() {
List<ScreenLockType> locks = new ArrayList<>();
// EnumSet's iterator guarantees the natural order of the enums
for (ScreenLockType lock : ScreenLockType.values()) {
if (isScreenLockVisible(lock) && isScreenLockEnabled(lock)) {
locks.add(lock);
}
}
return locks;
}
/**
* Returns the combined password metrics from all relevant policies which affects the current
* user. Normally password policies set on the current user's work profile instance will be
* taken into consideration here iff the work profile doesn't have its own work challenge.
* By setting {@link #mUnificationProfileId}, the work profile's password policy will always
* be combined here. Alternatively, by setting {@link #mDevicePasswordRequirementOnly}, its
* password policy will always be disregarded here.
*/
public PasswordMetrics getAggregatedPasswordMetrics() {
PasswordMetrics metrics = mLockPatternUtils.getRequestedPasswordMetrics(mUserId,
mDevicePasswordRequirementOnly);
if (mUnificationProfileId != UserHandle.USER_NULL) {
metrics.maxWith(mLockPatternUtils.getRequestedPasswordMetrics(mUnificationProfileId));
}
return metrics;
}
/**
* Returns the combined password complexity from all relevant policies which affects the current
* user. The same logic of handling work profile password policies as
* {@link #getAggregatedPasswordMetrics} applies here.
*/
public int getAggregatedPasswordComplexity() {
int complexity = Math.max(mAppRequestedMinComplexity,
mLockPatternUtils.getRequestedPasswordComplexity(
mUserId, mDevicePasswordRequirementOnly));
if (mUnificationProfileId != UserHandle.USER_NULL) {
complexity = Math.max(complexity,
mLockPatternUtils.getRequestedPasswordComplexity(mUnificationProfileId));
}
return complexity;
}
/**
* Returns whether any screen lock type has been disabled only due to password policy
* from the admin. Will return {@code false} if the restriction is purely due to calling
* app's request.
*/
public boolean isScreenLockRestrictedByAdmin() {
return getAggregatedPasswordMetrics().credType != CREDENTIAL_TYPE_NONE
|| isComplexityProvidedByAdmin();
}
/**
* Returns whether the aggregated password complexity is non-zero and comes from
* admin policy.
*/
public boolean isComplexityProvidedByAdmin() {
final int aggregatedComplexity = getAggregatedPasswordComplexity();
return aggregatedComplexity > mAppRequestedMinComplexity
&& aggregatedComplexity > PASSWORD_COMPLEXITY_NONE;
}
}