fix: 引入Settings的Module
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.settings.SettingsEnums;
|
||||
import android.hardware.biometrics.BiometricPrompt;
|
||||
import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback;
|
||||
import android.hardware.biometrics.BiometricPrompt.AuthenticationResult;
|
||||
import android.hardware.biometrics.PromptInfo;
|
||||
import android.multiuser.Flags;
|
||||
import android.os.Bundle;
|
||||
import android.os.CancellationSignal;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.settings.core.InstrumentedFragment;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* A fragment that wraps the BiometricPrompt and manages its lifecycle.
|
||||
*/
|
||||
public class BiometricFragment extends InstrumentedFragment {
|
||||
|
||||
private static final String TAG = "ConfirmDeviceCredential/BiometricFragment";
|
||||
|
||||
private static final String KEY_PROMPT_INFO = "prompt_info";
|
||||
|
||||
// Re-set by the application. Should be done upon orientation changes, etc
|
||||
private Executor mClientExecutor;
|
||||
private AuthenticationCallback mClientCallback;
|
||||
|
||||
// Re-settable by the application.
|
||||
private int mUserId;
|
||||
|
||||
// Created/Initialized once and retained
|
||||
private BiometricPrompt mBiometricPrompt;
|
||||
private CancellationSignal mCancellationSignal;
|
||||
|
||||
private AuthenticationCallback mAuthenticationCallback =
|
||||
new AuthenticationCallback() {
|
||||
@Override
|
||||
public void onAuthenticationError(int error, @NonNull CharSequence message) {
|
||||
mClientExecutor.execute(() -> {
|
||||
mClientCallback.onAuthenticationError(error, message);
|
||||
});
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(AuthenticationResult result) {
|
||||
mClientExecutor.execute(() -> {
|
||||
mClientCallback.onAuthenticationSucceeded(result);
|
||||
});
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
mClientExecutor.execute(() -> {
|
||||
mClientCallback.onAuthenticationFailed();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSystemEvent(int event) {
|
||||
mClientExecutor.execute(() -> {
|
||||
mClientCallback.onSystemEvent(event);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param promptInfo
|
||||
* @return
|
||||
*/
|
||||
public static BiometricFragment newInstance(PromptInfo promptInfo) {
|
||||
BiometricFragment biometricFragment = new BiometricFragment();
|
||||
final Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(KEY_PROMPT_INFO, promptInfo);
|
||||
biometricFragment.setArguments(bundle);
|
||||
return biometricFragment;
|
||||
}
|
||||
|
||||
public void setCallbacks(Executor executor, AuthenticationCallback callback) {
|
||||
mClientExecutor = executor;
|
||||
mClientCallback = callback;
|
||||
}
|
||||
|
||||
public void setUser(int userId) {
|
||||
mUserId = userId;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
if (mCancellationSignal != null) {
|
||||
mCancellationSignal.cancel();
|
||||
}
|
||||
cleanup();
|
||||
}
|
||||
|
||||
private void cleanup() {
|
||||
if (getActivity() != null) {
|
||||
getActivity().getSupportFragmentManager().beginTransaction().remove(this)
|
||||
.commitAllowingStateLoss();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setRetainInstance(true);
|
||||
|
||||
final Bundle bundle = getArguments();
|
||||
final PromptInfo promptInfo = bundle.getParcelable(KEY_PROMPT_INFO);
|
||||
|
||||
BiometricPrompt.Builder promptBuilder = new BiometricPrompt.Builder(getContext())
|
||||
.setTitle(promptInfo.getTitle())
|
||||
.setUseDefaultTitle() // use default title if title is null/empty
|
||||
.setDeviceCredentialAllowed(true)
|
||||
.setSubtitle(promptInfo.getSubtitle())
|
||||
.setDescription(promptInfo.getDescription())
|
||||
.setTextForDeviceCredential(
|
||||
promptInfo.getDeviceCredentialTitle(),
|
||||
promptInfo.getDeviceCredentialSubtitle(),
|
||||
promptInfo.getDeviceCredentialDescription())
|
||||
.setConfirmationRequired(promptInfo.isConfirmationRequested())
|
||||
.setDisallowBiometricsIfPolicyExists(
|
||||
promptInfo.isDisallowBiometricsIfPolicyExists())
|
||||
.setShowEmergencyCallButton(promptInfo.isShowEmergencyCallButton())
|
||||
.setReceiveSystemEvents(true);
|
||||
|
||||
if (Flags.enableBiometricsToUnlockPrivateSpace()) {
|
||||
promptBuilder = promptBuilder.setAllowBackgroundAuthentication(true /* allow */,
|
||||
promptInfo.shouldUseParentProfileForDeviceCredential());
|
||||
} else {
|
||||
promptBuilder = promptBuilder.setAllowBackgroundAuthentication(true /* allow */);
|
||||
}
|
||||
|
||||
// Check if the default subtitle should be used if subtitle is null/empty
|
||||
if (promptInfo.isUseDefaultSubtitle()) {
|
||||
promptBuilder.setUseDefaultSubtitle();
|
||||
}
|
||||
mBiometricPrompt = promptBuilder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (mCancellationSignal == null) {
|
||||
mCancellationSignal = new CancellationSignal();
|
||||
mBiometricPrompt.authenticateUser(mCancellationSignal, mClientExecutor,
|
||||
mAuthenticationCallback, mUserId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.BIOMETRIC_FRAGMENT;
|
||||
}
|
||||
}
|
||||
1090
Settings/src/com/android/settings/password/ChooseLockGeneric.java
Normal file
1090
Settings/src/com/android/settings/password/ChooseLockGeneric.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
1158
Settings/src/com/android/settings/password/ChooseLockPassword.java
Normal file
1158
Settings/src/com/android/settings/password/ChooseLockPassword.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,895 @@
|
||||
/*
|
||||
* Copyright (C) 2007 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.DevicePolicyResources.Strings.Settings.SET_WORK_PROFILE_PATTERN_HEADER;
|
||||
import static android.view.View.ACCESSIBILITY_LIVE_REGION_POLITE;
|
||||
|
||||
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL;
|
||||
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_UNIFICATION_PROFILE_ID;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Resources.Theme;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.internal.widget.LockPatternView;
|
||||
import com.android.internal.widget.LockPatternView.Cell;
|
||||
import com.android.internal.widget.LockPatternView.DisplayMode;
|
||||
import com.android.internal.widget.LockscreenCredential;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SettingsActivity;
|
||||
import com.android.settings.SetupWizardUtils;
|
||||
import com.android.settings.Utils;
|
||||
import com.android.settings.core.InstrumentedFragment;
|
||||
import com.android.settings.notification.RedactionInterstitial;
|
||||
|
||||
import com.google.android.collect.Lists;
|
||||
import com.google.android.setupcompat.template.FooterBarMixin;
|
||||
import com.google.android.setupcompat.template.FooterButton;
|
||||
import com.google.android.setupdesign.GlifLayout;
|
||||
import com.google.android.setupdesign.template.IconMixin;
|
||||
import com.google.android.setupdesign.util.ThemeHelper;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* If the user has a lock pattern set already, makes them confirm the existing one.
|
||||
*
|
||||
* Then, prompts the user to choose a lock pattern:
|
||||
* - prompts for initial pattern
|
||||
* - asks for confirmation / restart
|
||||
* - saves chosen password when confirmed
|
||||
*/
|
||||
public class ChooseLockPattern extends SettingsActivity {
|
||||
/**
|
||||
* Used by the choose lock pattern wizard to indicate the wizard is
|
||||
* finished, and each activity in the wizard should finish.
|
||||
* <p>
|
||||
* Previously, each activity in the wizard would finish itself after
|
||||
* starting the next activity. However, this leads to broken 'Back'
|
||||
* behavior. So, now an activity does not finish itself until it gets this
|
||||
* result.
|
||||
*/
|
||||
public static final int RESULT_FINISHED = RESULT_FIRST_USER;
|
||||
|
||||
private static final String TAG = "ChooseLockPattern";
|
||||
|
||||
@Override
|
||||
public Intent getIntent() {
|
||||
Intent modIntent = new Intent(super.getIntent());
|
||||
modIntent.putExtra(EXTRA_SHOW_FRAGMENT, getFragmentClass().getName());
|
||||
return modIntent;
|
||||
}
|
||||
|
||||
public static class IntentBuilder {
|
||||
private final Intent mIntent;
|
||||
|
||||
public IntentBuilder(Context context) {
|
||||
mIntent = new Intent(context, ChooseLockPattern.class);
|
||||
mIntent.putExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, false);
|
||||
}
|
||||
|
||||
public IntentBuilder setUserId(int userId) {
|
||||
mIntent.putExtra(Intent.EXTRA_USER_ID, userId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IntentBuilder setRequestGatekeeperPasswordHandle(
|
||||
boolean requestGatekeeperPasswordHandle) {
|
||||
mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE,
|
||||
requestGatekeeperPasswordHandle);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IntentBuilder setPattern(LockscreenCredential pattern) {
|
||||
mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pattern);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IntentBuilder setForFingerprint(boolean forFingerprint) {
|
||||
mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, forFingerprint);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IntentBuilder setForFace(boolean forFace) {
|
||||
mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, forFace);
|
||||
return this;
|
||||
}
|
||||
|
||||
public IntentBuilder setForBiometrics(boolean forBiometrics) {
|
||||
mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, forBiometrics);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the launch such that at the end of the pattern enrollment, one of its
|
||||
* managed profile (specified by {@code profileId}) will have its lockscreen unified
|
||||
* to the parent user. The profile's current lockscreen credential needs to be specified by
|
||||
* {@code credential}.
|
||||
*/
|
||||
public IntentBuilder setProfileToUnify(int profileId, LockscreenCredential credential) {
|
||||
mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_UNIFICATION_PROFILE_ID, profileId);
|
||||
mIntent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL,
|
||||
credential);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Intent build() {
|
||||
return mIntent;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isValidFragment(String fragmentName) {
|
||||
if (ChooseLockPatternFragment.class.getName().equals(fragmentName)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/* package */ Class<? extends Fragment> getFragmentClass() {
|
||||
return ChooseLockPatternFragment.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
setTheme(SetupWizardUtils.getTheme(this, getIntent()));
|
||||
ThemeHelper.trySetDynamicColor(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
findViewById(R.id.content_parent).setFitsSystemWindows(false);
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
// *** TODO ***
|
||||
// chooseLockPatternFragment.onKeyDown(keyCode, event);
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isToolbarEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static class ChooseLockPatternFragment extends InstrumentedFragment
|
||||
implements SaveAndFinishWorker.Listener {
|
||||
|
||||
public static final int CONFIRM_EXISTING_REQUEST = 55;
|
||||
|
||||
// how long after a confirmation message is shown before moving on
|
||||
static final int INFORMATION_MSG_TIMEOUT_MS = 3000;
|
||||
|
||||
// how long we wait to clear a wrong pattern
|
||||
private static final int WRONG_PATTERN_CLEAR_TIMEOUT_MS = 2000;
|
||||
|
||||
protected static final int ID_EMPTY_MESSAGE = -1;
|
||||
|
||||
private static final String FRAGMENT_TAG_SAVE_AND_FINISH = "save_and_finish_worker";
|
||||
|
||||
private LockscreenCredential mCurrentCredential;
|
||||
private boolean mRequestGatekeeperPassword;
|
||||
private boolean mRequestWriteRepairModePassword;
|
||||
protected TextView mHeaderText;
|
||||
protected LockPatternView mLockPatternView;
|
||||
protected TextView mFooterText;
|
||||
protected FooterButton mSkipOrClearButton;
|
||||
protected FooterButton mNextButton;
|
||||
@VisibleForTesting protected LockscreenCredential mChosenPattern;
|
||||
private ColorStateList mDefaultHeaderColorList;
|
||||
private View mSudContent;
|
||||
|
||||
/**
|
||||
* The patten used during the help screen to show how to draw a pattern.
|
||||
*/
|
||||
private final List<LockPatternView.Cell> mAnimatePattern =
|
||||
Collections.unmodifiableList(Lists.newArrayList(
|
||||
LockPatternView.Cell.of(0, 0),
|
||||
LockPatternView.Cell.of(0, 1),
|
||||
LockPatternView.Cell.of(1, 1),
|
||||
LockPatternView.Cell.of(2, 1)
|
||||
));
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode,
|
||||
Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
switch (requestCode) {
|
||||
case CONFIRM_EXISTING_REQUEST:
|
||||
if (resultCode != Activity.RESULT_OK) {
|
||||
getActivity().setResult(RESULT_FINISHED);
|
||||
getActivity().finish();
|
||||
} else {
|
||||
mCurrentCredential = data.getParcelableExtra(
|
||||
ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
|
||||
}
|
||||
|
||||
updateStage(Stage.Introduction);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected void setRightButtonEnabled(boolean enabled) {
|
||||
mNextButton.setEnabled(enabled);
|
||||
}
|
||||
|
||||
protected void setRightButtonText(int text) {
|
||||
mNextButton.setText(getActivity(), text);
|
||||
}
|
||||
|
||||
/**
|
||||
* The pattern listener that responds according to a user choosing a new
|
||||
* lock pattern.
|
||||
*/
|
||||
protected LockPatternView.OnPatternListener mChooseNewLockPatternListener =
|
||||
new LockPatternView.OnPatternListener() {
|
||||
|
||||
public void onPatternStart() {
|
||||
mLockPatternView.removeCallbacks(mClearPatternRunnable);
|
||||
patternInProgress();
|
||||
}
|
||||
|
||||
public void onPatternCleared() {
|
||||
mLockPatternView.removeCallbacks(mClearPatternRunnable);
|
||||
}
|
||||
|
||||
public void onPatternDetected(List<LockPatternView.Cell> pattern) {
|
||||
if (mUiStage == Stage.NeedToConfirm || mUiStage == Stage.ConfirmWrong) {
|
||||
if (mChosenPattern == null) throw new IllegalStateException(
|
||||
"null chosen pattern in stage 'need to confirm");
|
||||
try (LockscreenCredential confirmPattern =
|
||||
LockscreenCredential.createPattern(pattern)) {
|
||||
if (mChosenPattern.equals(confirmPattern)) {
|
||||
updateStage(Stage.ChoiceConfirmed);
|
||||
} else {
|
||||
updateStage(Stage.ConfirmWrong);
|
||||
}
|
||||
}
|
||||
} else if (mUiStage == Stage.Introduction || mUiStage == Stage.ChoiceTooShort){
|
||||
if (pattern.size() < LockPatternUtils.MIN_LOCK_PATTERN_SIZE) {
|
||||
updateStage(Stage.ChoiceTooShort);
|
||||
} else {
|
||||
mChosenPattern = LockscreenCredential.createPattern(pattern);
|
||||
updateStage(Stage.FirstChoiceValid);
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected stage " + mUiStage + " when "
|
||||
+ "entering the pattern.");
|
||||
}
|
||||
}
|
||||
|
||||
public void onPatternCellAdded(List<Cell> pattern) {
|
||||
|
||||
}
|
||||
|
||||
private void patternInProgress() {
|
||||
mHeaderText.setText(R.string.lockpattern_recording_inprogress);
|
||||
if (mDefaultHeaderColorList != null) {
|
||||
mHeaderText.setTextColor(mDefaultHeaderColorList);
|
||||
}
|
||||
mFooterText.setText("");
|
||||
mNextButton.setEnabled(false);
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.CHOOSE_LOCK_PATTERN;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The states of the left footer button.
|
||||
*/
|
||||
enum LeftButtonMode {
|
||||
Retry(R.string.lockpattern_retry_button_text, true),
|
||||
RetryDisabled(R.string.lockpattern_retry_button_text, false),
|
||||
Gone(ID_EMPTY_MESSAGE, false);
|
||||
|
||||
|
||||
/**
|
||||
* @param text The displayed text for this mode.
|
||||
* @param enabled Whether the button should be enabled.
|
||||
*/
|
||||
LeftButtonMode(int text, boolean enabled) {
|
||||
this.text = text;
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
final int text;
|
||||
final boolean enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* The states of the right button.
|
||||
*/
|
||||
enum RightButtonMode {
|
||||
Continue(R.string.next_label, true),
|
||||
ContinueDisabled(R.string.next_label, false),
|
||||
Confirm(R.string.lockpattern_confirm_button_text, true),
|
||||
ConfirmDisabled(R.string.lockpattern_confirm_button_text, false),
|
||||
Ok(android.R.string.ok, true);
|
||||
|
||||
/**
|
||||
* @param text The displayed text for this mode.
|
||||
* @param enabled Whether the button should be enabled.
|
||||
*/
|
||||
RightButtonMode(int text, boolean enabled) {
|
||||
this.text = text;
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
final int text;
|
||||
final boolean enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep track internally of where the user is in choosing a pattern.
|
||||
*/
|
||||
protected enum Stage {
|
||||
|
||||
Introduction(
|
||||
R.string.lock_settings_picker_biometrics_added_security_message,
|
||||
R.string.lockpassword_choose_your_pattern_description,
|
||||
LeftButtonMode.Gone, RightButtonMode.ContinueDisabled,
|
||||
ID_EMPTY_MESSAGE, true),
|
||||
HelpScreen(
|
||||
R.string.lockpattern_settings_help_how_to_record,
|
||||
R.string.lockpattern_settings_help_how_to_record,
|
||||
LeftButtonMode.Gone, RightButtonMode.Ok, ID_EMPTY_MESSAGE, false),
|
||||
ChoiceTooShort(
|
||||
R.string.lockpattern_recording_incorrect_too_short,
|
||||
R.string.lockpattern_recording_incorrect_too_short,
|
||||
LeftButtonMode.Retry, RightButtonMode.ContinueDisabled,
|
||||
ID_EMPTY_MESSAGE, true),
|
||||
FirstChoiceValid(
|
||||
R.string.lockpattern_pattern_entered_header,
|
||||
R.string.lockpattern_pattern_entered_header,
|
||||
LeftButtonMode.Retry, RightButtonMode.Continue, ID_EMPTY_MESSAGE, false),
|
||||
NeedToConfirm(
|
||||
R.string.lockpattern_need_to_confirm, R.string.lockpattern_need_to_confirm,
|
||||
LeftButtonMode.Gone, RightButtonMode.ConfirmDisabled,
|
||||
ID_EMPTY_MESSAGE, true),
|
||||
ConfirmWrong(
|
||||
R.string.lockpattern_need_to_unlock_wrong,
|
||||
R.string.lockpattern_need_to_unlock_wrong,
|
||||
LeftButtonMode.Gone, RightButtonMode.ConfirmDisabled,
|
||||
ID_EMPTY_MESSAGE, true),
|
||||
ChoiceConfirmed(
|
||||
R.string.lockpattern_pattern_confirmed_header,
|
||||
R.string.lockpattern_pattern_confirmed_header,
|
||||
LeftButtonMode.Gone, RightButtonMode.Confirm, ID_EMPTY_MESSAGE, false);
|
||||
|
||||
|
||||
/**
|
||||
* @param messageForBiometrics The message displayed at the top, above header for
|
||||
* fingerprint flow.
|
||||
* @param headerMessage The message displayed at the top.
|
||||
* @param leftMode The mode of the left button.
|
||||
* @param rightMode The mode of the right button.
|
||||
* @param footerMessage The footer message.
|
||||
* @param patternEnabled Whether the pattern widget is enabled.
|
||||
*/
|
||||
Stage(int messageForBiometrics, int headerMessage,
|
||||
LeftButtonMode leftMode,
|
||||
RightButtonMode rightMode,
|
||||
int footerMessage, boolean patternEnabled) {
|
||||
this.headerMessage = headerMessage;
|
||||
this.messageForBiometrics = messageForBiometrics;
|
||||
this.leftMode = leftMode;
|
||||
this.rightMode = rightMode;
|
||||
this.footerMessage = footerMessage;
|
||||
this.patternEnabled = patternEnabled;
|
||||
}
|
||||
|
||||
final int headerMessage;
|
||||
final int messageForBiometrics;
|
||||
final LeftButtonMode leftMode;
|
||||
final RightButtonMode rightMode;
|
||||
final int footerMessage;
|
||||
final boolean patternEnabled;
|
||||
}
|
||||
|
||||
private Stage mUiStage = Stage.Introduction;
|
||||
|
||||
private Runnable mClearPatternRunnable = new Runnable() {
|
||||
public void run() {
|
||||
mLockPatternView.clearPattern();
|
||||
}
|
||||
};
|
||||
|
||||
private LockPatternUtils mLockPatternUtils;
|
||||
private SaveAndFinishWorker mSaveAndFinishWorker;
|
||||
protected int mUserId;
|
||||
protected boolean mIsManagedProfile;
|
||||
protected boolean mForFingerprint;
|
||||
protected boolean mForFace;
|
||||
protected boolean mForBiometrics;
|
||||
|
||||
@VisibleForTesting
|
||||
static final String KEY_UI_STAGE = "uiStage";
|
||||
private static final String KEY_PATTERN_CHOICE = "chosenPattern";
|
||||
private static final String KEY_CURRENT_PATTERN = "currentPattern";
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (!(getActivity() instanceof ChooseLockPattern)) {
|
||||
throw new SecurityException("Fragment contained in wrong activity");
|
||||
}
|
||||
Intent intent = getActivity().getIntent();
|
||||
// Only take this argument into account if it belongs to the current profile.
|
||||
mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras());
|
||||
mIsManagedProfile = UserManager.get(getActivity()).isManagedProfile(mUserId);
|
||||
|
||||
mLockPatternUtils = new LockPatternUtils(getActivity());
|
||||
|
||||
mForFingerprint = intent.getBooleanExtra(
|
||||
ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
|
||||
mForFace = intent.getBooleanExtra(
|
||||
ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
|
||||
mForBiometrics = intent.getBooleanExtra(
|
||||
ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, false);
|
||||
}
|
||||
|
||||
private void updateActivityTitle() {
|
||||
final String msg;
|
||||
if (mForFingerprint && !shouldShowGenericTitle()) {
|
||||
msg = getString(R.string.lockpassword_choose_your_pattern_header_for_fingerprint);
|
||||
} else if (mForFace && !shouldShowGenericTitle()) {
|
||||
msg = getString(R.string.lockpassword_choose_your_pattern_header_for_face);
|
||||
} else if (mIsManagedProfile) {
|
||||
msg = getContext().getSystemService(DevicePolicyManager.class).getResources()
|
||||
.getString(SET_WORK_PROFILE_PATTERN_HEADER,
|
||||
() -> getString(
|
||||
R.string.lockpassword_choose_your_profile_pattern_header));
|
||||
} else if (android.os.Flags.allowPrivateProfile() && isPrivateProfile()) {
|
||||
msg = getString(R.string.private_space_choose_your_pattern_header);
|
||||
} else {
|
||||
msg = getString(R.string.lockpassword_choose_your_pattern_header);
|
||||
}
|
||||
getActivity().setTitle(msg);
|
||||
}
|
||||
|
||||
protected boolean shouldShowGenericTitle() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
final GlifLayout layout = (GlifLayout) inflater.inflate(
|
||||
R.layout.choose_lock_pattern, container, false);
|
||||
layout.findViewById(R.id.lockPattern).setOnTouchListener((v, event) -> {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
v.getParent().requestDisallowInterceptTouchEvent(true);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
updateActivityTitle();
|
||||
layout.setHeaderText(getActivity().getTitle());
|
||||
layout.getHeaderTextView().setAccessibilityLiveRegion(ACCESSIBILITY_LIVE_REGION_POLITE);
|
||||
if (getResources().getBoolean(R.bool.config_lock_pattern_minimal_ui)) {
|
||||
View iconView = layout.findViewById(R.id.sud_layout_icon);
|
||||
if (iconView != null) {
|
||||
layout.getMixin(IconMixin.class).setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
layout.setIcon(getActivity().getDrawable(R.drawable.ic_lock));
|
||||
}
|
||||
|
||||
final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class);
|
||||
mixin.setSecondaryButton(
|
||||
new FooterButton.Builder(getActivity())
|
||||
.setText(R.string.lockpattern_tutorial_cancel_label)
|
||||
.setListener(this::onSkipOrClearButtonClick)
|
||||
.setButtonType(FooterButton.ButtonType.OTHER)
|
||||
.setTheme(
|
||||
com.google.android.setupdesign.R.style.SudGlifButton_Secondary)
|
||||
.build()
|
||||
);
|
||||
mixin.setPrimaryButton(
|
||||
new FooterButton.Builder(getActivity())
|
||||
.setText(R.string.lockpattern_tutorial_continue_label)
|
||||
.setListener(this::onNextButtonClick)
|
||||
.setButtonType(FooterButton.ButtonType.NEXT)
|
||||
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
|
||||
.build()
|
||||
);
|
||||
mSkipOrClearButton = mixin.getSecondaryButton();
|
||||
mNextButton = mixin.getPrimaryButton();
|
||||
// TODO(b/243008023) Workaround for Glif layout on 2 panel choose lock settings.
|
||||
mSudContent = layout.findViewById(
|
||||
com.google.android.setupdesign.R.id.sud_layout_content);
|
||||
mSudContent.setPadding(mSudContent.getPaddingLeft(), 0, mSudContent.getPaddingRight(),
|
||||
0);
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
final GlifLayout layout = getActivity().findViewById(R.id.setup_wizard_layout);
|
||||
mHeaderText = layout.getDescriptionTextView();
|
||||
mHeaderText.setMinLines(2);
|
||||
mDefaultHeaderColorList = mHeaderText.getTextColors();
|
||||
mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern);
|
||||
mLockPatternView.setOnPatternListener(mChooseNewLockPatternListener);
|
||||
mLockPatternView.setFadePattern(false);
|
||||
|
||||
mFooterText = (TextView) view.findViewById(R.id.footerText);
|
||||
|
||||
// make it so unhandled touch events within the unlock screen go to the
|
||||
// lock pattern view.
|
||||
final LinearLayoutWithDefaultTouchRecepient topLayout
|
||||
= (LinearLayoutWithDefaultTouchRecepient) view.findViewById(
|
||||
R.id.topLayout);
|
||||
topLayout.setDefaultTouchRecepient(mLockPatternView);
|
||||
|
||||
final boolean confirmCredentials = getActivity().getIntent()
|
||||
.getBooleanExtra(ChooseLockGeneric.CONFIRM_CREDENTIALS, true);
|
||||
Intent intent = getActivity().getIntent();
|
||||
mCurrentCredential =
|
||||
intent.getParcelableExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD);
|
||||
mRequestGatekeeperPassword = intent.getBooleanExtra(
|
||||
ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, false);
|
||||
mRequestWriteRepairModePassword = intent.getBooleanExtra(
|
||||
ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW, false);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
if (confirmCredentials) {
|
||||
// first launch. As a security measure, we're in NeedToConfirm mode until we
|
||||
// know there isn't an existing password or the user confirms their password.
|
||||
updateStage(Stage.NeedToConfirm);
|
||||
|
||||
final ChooseLockSettingsHelper.Builder builder =
|
||||
new ChooseLockSettingsHelper.Builder(getActivity());
|
||||
final boolean launched = builder.setRequestCode(CONFIRM_EXISTING_REQUEST)
|
||||
.setTitle(getString(R.string.unlock_set_unlock_launch_picker_title))
|
||||
.setReturnCredentials(true)
|
||||
.setRequestGatekeeperPasswordHandle(mRequestGatekeeperPassword)
|
||||
.setRequestWriteRepairModePassword(mRequestWriteRepairModePassword)
|
||||
.setUserId(mUserId)
|
||||
.show();
|
||||
|
||||
if (!launched) {
|
||||
updateStage(Stage.Introduction);
|
||||
}
|
||||
} else {
|
||||
updateStage(Stage.Introduction);
|
||||
}
|
||||
} else {
|
||||
// restore from previous state
|
||||
mChosenPattern = savedInstanceState.getParcelable(KEY_PATTERN_CHOICE);
|
||||
mCurrentCredential = savedInstanceState.getParcelable(KEY_CURRENT_PATTERN);
|
||||
|
||||
updateStage(Stage.values()[savedInstanceState.getInt(KEY_UI_STAGE)]);
|
||||
|
||||
// Re-attach to the exiting worker if there is one.
|
||||
mSaveAndFinishWorker = (SaveAndFinishWorker) getFragmentManager().findFragmentByTag(
|
||||
FRAGMENT_TAG_SAVE_AND_FINISH);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
updateStage(mUiStage);
|
||||
|
||||
if (mSaveAndFinishWorker != null) {
|
||||
setRightButtonEnabled(false);
|
||||
mSaveAndFinishWorker.setListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (mSaveAndFinishWorker != null) {
|
||||
mSaveAndFinishWorker.setListener(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (mCurrentCredential != null) {
|
||||
mCurrentCredential.zeroize();
|
||||
}
|
||||
// Force a garbage collection immediately to remove remnant of user password shards
|
||||
// from memory.
|
||||
System.gc();
|
||||
System.runFinalization();
|
||||
System.gc();
|
||||
}
|
||||
|
||||
protected Intent getRedactionInterstitialIntent(Context context) {
|
||||
return RedactionInterstitial.createStartIntent(context, mUserId);
|
||||
}
|
||||
|
||||
public void handleLeftButton() {
|
||||
if (mUiStage.leftMode == LeftButtonMode.Retry) {
|
||||
if (mChosenPattern != null) {
|
||||
mChosenPattern.zeroize();
|
||||
mChosenPattern = null;
|
||||
}
|
||||
mLockPatternView.clearPattern();
|
||||
updateStage(Stage.Introduction);
|
||||
} else {
|
||||
throw new IllegalStateException("left footer button pressed, but stage of " +
|
||||
mUiStage + " doesn't make sense");
|
||||
}
|
||||
}
|
||||
|
||||
public void handleRightButton() {
|
||||
if (mUiStage.rightMode == RightButtonMode.Continue) {
|
||||
if (mUiStage != Stage.FirstChoiceValid) {
|
||||
throw new IllegalStateException("expected ui stage "
|
||||
+ Stage.FirstChoiceValid + " when button is "
|
||||
+ RightButtonMode.Continue);
|
||||
}
|
||||
updateStage(Stage.NeedToConfirm);
|
||||
} else if (mUiStage.rightMode == RightButtonMode.Confirm) {
|
||||
if (mUiStage != Stage.ChoiceConfirmed) {
|
||||
throw new IllegalStateException("expected ui stage " + Stage.ChoiceConfirmed
|
||||
+ " when button is " + RightButtonMode.Confirm);
|
||||
}
|
||||
startSaveAndFinish();
|
||||
} else if (mUiStage.rightMode == RightButtonMode.Ok) {
|
||||
if (mUiStage != Stage.HelpScreen) {
|
||||
throw new IllegalStateException("Help screen is only mode with ok button, "
|
||||
+ "but stage is " + mUiStage);
|
||||
}
|
||||
mLockPatternView.clearPattern();
|
||||
mLockPatternView.setDisplayMode(DisplayMode.Correct);
|
||||
updateStage(Stage.Introduction);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onSkipOrClearButtonClick(View view) {
|
||||
handleLeftButton();
|
||||
}
|
||||
|
||||
protected void onNextButtonClick(View view) {
|
||||
handleRightButton();
|
||||
}
|
||||
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
|
||||
if (mUiStage == Stage.HelpScreen) {
|
||||
updateStage(Stage.Introduction);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (keyCode == KeyEvent.KEYCODE_MENU && mUiStage == Stage.Introduction) {
|
||||
updateStage(Stage.HelpScreen);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
outState.putInt(KEY_UI_STAGE, mUiStage.ordinal());
|
||||
if (mChosenPattern != null) {
|
||||
outState.putParcelable(KEY_PATTERN_CHOICE, mChosenPattern);
|
||||
}
|
||||
|
||||
if (mCurrentCredential != null) {
|
||||
outState.putParcelable(KEY_CURRENT_PATTERN, mCurrentCredential.duplicate());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the messages and buttons appropriate to what stage the user
|
||||
* is at in choosing a view. This doesn't handle clearing out the pattern;
|
||||
* the pattern is expected to be in the right state.
|
||||
* @param stage
|
||||
*/
|
||||
protected void updateStage(Stage stage) {
|
||||
final Stage previousStage = mUiStage;
|
||||
final GlifLayout layout = getActivity().findViewById(R.id.setup_wizard_layout);
|
||||
mUiStage = stage;
|
||||
|
||||
// header text, footer text, visibility and
|
||||
// enabled state all known from the stage
|
||||
if (stage == Stage.ChoiceTooShort) {
|
||||
layout.setDescriptionText(
|
||||
getResources().getString(
|
||||
stage.headerMessage,
|
||||
LockPatternUtils.MIN_LOCK_PATTERN_SIZE));
|
||||
} else {
|
||||
layout.setDescriptionText(stage.headerMessage);
|
||||
}
|
||||
|
||||
if (stage.footerMessage == ID_EMPTY_MESSAGE) {
|
||||
mFooterText.setText("");
|
||||
} else {
|
||||
mFooterText.setText(stage.footerMessage);
|
||||
}
|
||||
|
||||
if (stage == Stage.ConfirmWrong || stage == Stage.ChoiceTooShort) {
|
||||
TypedValue typedValue = new TypedValue();
|
||||
Theme theme = getActivity().getTheme();
|
||||
theme.resolveAttribute(androidx.appcompat.R.attr.colorError, typedValue, true);
|
||||
mHeaderText.setTextColor(typedValue.data);
|
||||
} else if (mDefaultHeaderColorList != null) {
|
||||
mHeaderText.setTextColor(mDefaultHeaderColorList);
|
||||
}
|
||||
|
||||
|
||||
if (stage == Stage.ConfirmWrong || stage == Stage.NeedToConfirm) {
|
||||
layout.setHeaderText(R.string.lockpassword_draw_your_pattern_again_header);
|
||||
}
|
||||
|
||||
updateFooterLeftButton(stage);
|
||||
|
||||
setRightButtonText(stage.rightMode.text);
|
||||
setRightButtonEnabled(stage.rightMode.enabled);
|
||||
|
||||
// same for whether the pattern is enabled
|
||||
if (stage.patternEnabled) {
|
||||
mLockPatternView.enableInput();
|
||||
} else {
|
||||
mLockPatternView.disableInput();
|
||||
}
|
||||
|
||||
// the rest of the stuff varies enough that it is easier just to handle
|
||||
// on a case by case basis.
|
||||
mLockPatternView.setDisplayMode(DisplayMode.Correct);
|
||||
boolean announceAlways = false;
|
||||
|
||||
switch (mUiStage) {
|
||||
case Introduction:
|
||||
mLockPatternView.clearPattern();
|
||||
break;
|
||||
case HelpScreen:
|
||||
mLockPatternView.setPattern(DisplayMode.Animate, mAnimatePattern);
|
||||
break;
|
||||
case ChoiceTooShort:
|
||||
case ConfirmWrong:
|
||||
mLockPatternView.setDisplayMode(DisplayMode.Wrong);
|
||||
postClearPatternRunnable();
|
||||
announceAlways = true;
|
||||
break;
|
||||
case FirstChoiceValid:
|
||||
break;
|
||||
case NeedToConfirm:
|
||||
mLockPatternView.clearPattern();
|
||||
break;
|
||||
case ChoiceConfirmed:
|
||||
break;
|
||||
}
|
||||
|
||||
// If the stage changed, announce the header for accessibility. This
|
||||
// is a no-op when accessibility is disabled.
|
||||
if (previousStage != stage || announceAlways) {
|
||||
if (stage == Stage.NeedToConfirm) {
|
||||
// If the Stage is NeedToConfirm, move the a11y focus to the header.
|
||||
mHeaderText.requestAccessibilityFocus();
|
||||
} else {
|
||||
mHeaderText.announceForAccessibility(mHeaderText.getText());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void updateFooterLeftButton(Stage stage) {
|
||||
if (stage.leftMode == LeftButtonMode.Gone) {
|
||||
mSkipOrClearButton.setVisibility(View.GONE);
|
||||
} else {
|
||||
mSkipOrClearButton.setVisibility(View.VISIBLE);
|
||||
mSkipOrClearButton.setText(getActivity(), stage.leftMode.text);
|
||||
mSkipOrClearButton.setEnabled(stage.leftMode.enabled);
|
||||
}
|
||||
}
|
||||
|
||||
// clear the wrong pattern unless they have started a new one
|
||||
// already
|
||||
private void postClearPatternRunnable() {
|
||||
mLockPatternView.removeCallbacks(mClearPatternRunnable);
|
||||
mLockPatternView.postDelayed(mClearPatternRunnable, WRONG_PATTERN_CLEAR_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
private void startSaveAndFinish() {
|
||||
if (mSaveAndFinishWorker != null) {
|
||||
Log.w(TAG, "startSaveAndFinish with an existing SaveAndFinishWorker.");
|
||||
return;
|
||||
}
|
||||
|
||||
setRightButtonEnabled(false);
|
||||
|
||||
mSaveAndFinishWorker = new SaveAndFinishWorker();
|
||||
mSaveAndFinishWorker
|
||||
.setListener(this)
|
||||
.setRequestGatekeeperPasswordHandle(mRequestGatekeeperPassword)
|
||||
.setRequestWriteRepairModePassword(mRequestWriteRepairModePassword);
|
||||
|
||||
getFragmentManager().beginTransaction().add(mSaveAndFinishWorker,
|
||||
FRAGMENT_TAG_SAVE_AND_FINISH).commit();
|
||||
getFragmentManager().executePendingTransactions();
|
||||
|
||||
final Intent intent = getActivity().getIntent();
|
||||
if (intent.hasExtra(EXTRA_KEY_UNIFICATION_PROFILE_ID)) {
|
||||
try (LockscreenCredential profileCredential = (LockscreenCredential)
|
||||
intent.getParcelableExtra(EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL)) {
|
||||
mSaveAndFinishWorker.setProfileToUnify(
|
||||
intent.getIntExtra(EXTRA_KEY_UNIFICATION_PROFILE_ID,
|
||||
UserHandle.USER_NULL),
|
||||
profileCredential);
|
||||
}
|
||||
}
|
||||
mSaveAndFinishWorker.start(mLockPatternUtils,
|
||||
mChosenPattern, mCurrentCredential, mUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
|
||||
getActivity().setResult(RESULT_FINISHED, resultData);
|
||||
|
||||
if (mChosenPattern != null) {
|
||||
mChosenPattern.zeroize();
|
||||
}
|
||||
if (mCurrentCredential != null) {
|
||||
mCurrentCredential.zeroize();
|
||||
}
|
||||
|
||||
if (!wasSecureBefore) {
|
||||
Intent intent = getRedactionInterstitialIntent(getActivity());
|
||||
if (intent != null) {
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
|
||||
if (mSudContent != null) {
|
||||
mSudContent.announceForAccessibility(
|
||||
getString(R.string.accessibility_setup_password_complete));
|
||||
}
|
||||
|
||||
getActivity().finish();
|
||||
}
|
||||
|
||||
private boolean isPrivateProfile() {
|
||||
UserManager userManager = getContext().createContextAsUser(UserHandle.of(mUserId),
|
||||
/*flags=*/0).getSystemService(UserManager.class);
|
||||
return userManager.isPrivateProfile();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,591 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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.Utils.SETTINGS_PACKAGE_NAME;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityOptions;
|
||||
import android.app.KeyguardManager;
|
||||
import android.app.RemoteLockscreenValidationSession;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserManager;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.settings.SetupWizardUtils;
|
||||
import com.android.settings.Utils;
|
||||
import com.android.settings.core.SettingsBaseActivity;
|
||||
import com.android.settings.core.SubSettingLauncher;
|
||||
import com.android.settingslib.transition.SettingsTransitionHelper;
|
||||
|
||||
import com.google.android.setupcompat.util.WizardManagerHelper;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public final class ChooseLockSettingsHelper {
|
||||
|
||||
private static final String TAG = "ChooseLockSettingsHelper";
|
||||
|
||||
public static final String EXTRA_KEY_PASSWORD = "password";
|
||||
public static final String EXTRA_KEY_RETURN_CREDENTIALS = "return_credentials";
|
||||
// Force the verifyCredential path instead of checkCredential path. This will be removed
|
||||
// after b/161956762 is resolved.
|
||||
public static final String EXTRA_KEY_FORCE_VERIFY = "force_verify";
|
||||
// Gatekeeper HardwareAuthToken
|
||||
public static final String EXTRA_KEY_CHALLENGE_TOKEN = "hw_auth_token";
|
||||
// For the fingerprint-only path
|
||||
public static final String EXTRA_KEY_FOR_FINGERPRINT = "for_fingerprint";
|
||||
// For the face-only path
|
||||
public static final String EXTRA_KEY_FOR_FACE = "for_face";
|
||||
// For the paths where multiple biometric sensors exist
|
||||
public static final String EXTRA_KEY_FOR_BIOMETRICS = "for_biometrics";
|
||||
// For the paths where setup biometrics in suw flow
|
||||
public static final String EXTRA_KEY_IS_SUW = "is_suw";
|
||||
public static final String EXTRA_KEY_FOREGROUND_ONLY = "foreground_only";
|
||||
public static final String EXTRA_KEY_REQUEST_GK_PW_HANDLE = "request_gk_pw_handle";
|
||||
// Gatekeeper password handle, which can subsequently be used to generate Gatekeeper
|
||||
// HardwareAuthToken(s) via LockSettingsService#verifyGatekeeperPasswordHandle
|
||||
public static final String EXTRA_KEY_GK_PW_HANDLE = "gk_pw_handle";
|
||||
public static final String EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW =
|
||||
"request_write_repair_mode_pw";
|
||||
public static final String EXTRA_KEY_WROTE_REPAIR_MODE_CREDENTIAL =
|
||||
"wrote_repair_mode_credential";
|
||||
|
||||
/**
|
||||
* When EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL and EXTRA_KEY_UNIFICATION_PROFILE_ID are
|
||||
* provided to ChooseLockGeneric as fragment arguments {@link SubSettingLauncher#setArguments},
|
||||
* at the end of the password change flow, the supplied profile user
|
||||
* (EXTRA_KEY_UNIFICATION_PROFILE_ID) will be unified to its parent. The current profile
|
||||
* password is supplied by EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL.
|
||||
*/
|
||||
public static final String EXTRA_KEY_UNIFICATION_PROFILE_ID = "unification_profile_id";
|
||||
public static final String EXTRA_KEY_UNIFICATION_PROFILE_CREDENTIAL =
|
||||
"unification_profile_credential";
|
||||
|
||||
/**
|
||||
* Intent extra for passing the requested min password complexity to later steps in the set new
|
||||
* screen lock flow.
|
||||
*/
|
||||
public static final String EXTRA_KEY_REQUESTED_MIN_COMPLEXITY = "requested_min_complexity";
|
||||
|
||||
/**
|
||||
* Intent extra for passing the label of the calling app to later steps in the set new screen
|
||||
* lock flow.
|
||||
*/
|
||||
public static final String EXTRA_KEY_CALLER_APP_NAME = "caller_app_name";
|
||||
|
||||
/**
|
||||
* Intent extra indicating that the calling app is an admin, such as a Device Adimn, Device
|
||||
* Owner, or Profile Owner.
|
||||
*/
|
||||
public static final String EXTRA_KEY_IS_CALLING_APP_ADMIN = "is_calling_app_admin";
|
||||
|
||||
/**
|
||||
* When invoked via {@link ConfirmLockPassword.InternalActivity}, this flag
|
||||
* controls if we relax the enforcement of
|
||||
* {@link Utils#enforceSameOwner(android.content.Context, int)}.
|
||||
*/
|
||||
public static final String EXTRA_KEY_ALLOW_ANY_USER = "allow_any_user";
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public static final String EXTRA_KEY_DEVICE_PASSWORD_REQUIREMENT_ONLY =
|
||||
"device_password_requirement_only";
|
||||
|
||||
/** Intent extra for passing the screen title resource ID to show in the set lock screen. */
|
||||
public static final String EXTRA_KEY_CHOOSE_LOCK_SCREEN_TITLE =
|
||||
"choose_lock_setup_screen_title";
|
||||
|
||||
/** Intent extra for passing the description resource ID to show in the set lock screen. */
|
||||
public static final String EXTRA_KEY_CHOOSE_LOCK_SCREEN_DESCRIPTION =
|
||||
"choose_lock_setup_screen_description";
|
||||
|
||||
@VisibleForTesting @NonNull LockPatternUtils mLockPatternUtils;
|
||||
@NonNull private final Activity mActivity;
|
||||
@Nullable private final Fragment mFragment;
|
||||
@Nullable private final ActivityResultLauncher mActivityResultLauncher;
|
||||
@NonNull private final Builder mBuilder;
|
||||
|
||||
private ChooseLockSettingsHelper(@NonNull Builder builder, @NonNull Activity activity,
|
||||
@Nullable Fragment fragment,
|
||||
@Nullable ActivityResultLauncher activityResultLauncher) {
|
||||
mBuilder = builder;
|
||||
mActivity = activity;
|
||||
mFragment = fragment;
|
||||
mActivityResultLauncher = activityResultLauncher;
|
||||
mLockPatternUtils = new LockPatternUtils(activity);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
@NonNull private final Activity mActivity;
|
||||
@Nullable private Fragment mFragment;
|
||||
@Nullable private ActivityResultLauncher mActivityResultLauncher;
|
||||
|
||||
private int mRequestCode;
|
||||
@Nullable private CharSequence mTitle;
|
||||
@Nullable private CharSequence mHeader;
|
||||
@Nullable private CharSequence mDescription;
|
||||
@Nullable private CharSequence mAlternateButton;
|
||||
@Nullable private CharSequence mCheckBoxLabel;
|
||||
private boolean mReturnCredentials;
|
||||
private boolean mExternal;
|
||||
private boolean mForegroundOnly;
|
||||
// ChooseLockSettingsHelper will determine the caller's userId if none provided.
|
||||
private int mUserId;
|
||||
private boolean mAllowAnyUserId;
|
||||
private boolean mForceVerifyPath;
|
||||
private boolean mRemoteLockscreenValidation;
|
||||
@Nullable private RemoteLockscreenValidationSession mRemoteLockscreenValidationSession;
|
||||
@Nullable private ComponentName mRemoteLockscreenValidationServiceComponent;
|
||||
private boolean mRequestGatekeeperPasswordHandle;
|
||||
private boolean mRequestWriteRepairModePassword;
|
||||
private boolean mTaskOverlay;
|
||||
|
||||
public Builder(@NonNull Activity activity) {
|
||||
mActivity = activity;
|
||||
mUserId = Utils.getCredentialOwnerUserId(mActivity);
|
||||
}
|
||||
|
||||
public Builder(@NonNull Activity activity, @NonNull Fragment fragment) {
|
||||
this(activity);
|
||||
mFragment = fragment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param requestCode for onActivityResult
|
||||
*/
|
||||
@NonNull public Builder setRequestCode(int requestCode) {
|
||||
mRequestCode = requestCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param title of the confirmation screen; shown in the action bar
|
||||
*/
|
||||
@NonNull public Builder setTitle(@Nullable CharSequence title) {
|
||||
mTitle = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param header of the confirmation screen; shown as large text
|
||||
*/
|
||||
@NonNull public Builder setHeader(@Nullable CharSequence header) {
|
||||
mHeader = header;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param description of the confirmation screen
|
||||
*/
|
||||
@NonNull public Builder setDescription(@Nullable CharSequence description) {
|
||||
mDescription = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param alternateButton text for an alternate button
|
||||
*/
|
||||
@NonNull public Builder setAlternateButton(@Nullable CharSequence alternateButton) {
|
||||
mAlternateButton = alternateButton;
|
||||
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
|
||||
* onActivityResult with the following keys:
|
||||
* {@link #EXTRA_KEY_PASSWORD},
|
||||
* {@link #EXTRA_KEY_CHALLENGE_TOKEN},
|
||||
* {@link #EXTRA_KEY_GK_PW_HANDLE}
|
||||
* Note that if this is true, this can only be called internally.
|
||||
*
|
||||
* This should also generally be set if
|
||||
* {@link #setRequestGatekeeperPasswordHandle(boolean)} is set.
|
||||
*/
|
||||
@NonNull public Builder setReturnCredentials(boolean returnCredentials) {
|
||||
mReturnCredentials = returnCredentials;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param userId for whom the credential should be confirmed.
|
||||
*/
|
||||
@NonNull public Builder setUserId(int userId) {
|
||||
mUserId = userId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param allowAnyUserId Allows the caller to prompt for credentials of any user, including
|
||||
* those which aren't associated with the current user. As an example,
|
||||
* this is useful when unlocking the storage for secondary users.
|
||||
*/
|
||||
@NonNull public Builder setAllowAnyUserId(boolean allowAnyUserId) {
|
||||
mAllowAnyUserId = allowAnyUserId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param external specifies whether this activity is launched externally, meaning that it
|
||||
* will get a dark theme, allow biometric authentication, and it will
|
||||
* forward the activity result.
|
||||
*/
|
||||
@NonNull public Builder setExternal(boolean external) {
|
||||
mExternal = external;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param taskOverlay specifies whether the activity should be launched as a task overlay.
|
||||
*/
|
||||
@NonNull public Builder setTaskOverlay(boolean taskOverlay) {
|
||||
mTaskOverlay = taskOverlay;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param foregroundOnly if true, the confirmation activity will be finished if it loses
|
||||
* foreground.
|
||||
*/
|
||||
@NonNull public Builder setForegroundOnly(boolean foregroundOnly) {
|
||||
mForegroundOnly = foregroundOnly;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param forceVerifyPath Forces the VerifyCredential path instead of the CheckCredential
|
||||
* path. This will be removed after b/161956762 is resolved.
|
||||
*/
|
||||
@NonNull public Builder setForceVerifyPath(boolean forceVerifyPath) {
|
||||
mForceVerifyPath = forceVerifyPath;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isRemoteLockscreenValidation if true, remote device validation flow will be
|
||||
* started. {@link #setRemoteLockscreenValidationSession},
|
||||
* {@link #setRemoteLockscreenValidationServiceComponent}
|
||||
* must also be used to set the required data.
|
||||
*/
|
||||
@NonNull public Builder setRemoteLockscreenValidation(
|
||||
boolean isRemoteLockscreenValidation) {
|
||||
mRemoteLockscreenValidation = isRemoteLockscreenValidation;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param remoteLockscreenValidationSession 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 setRemoteLockscreenValidationSession(
|
||||
RemoteLockscreenValidationSession remoteLockscreenValidationSession) {
|
||||
mRemoteLockscreenValidationSession = remoteLockscreenValidationSession;
|
||||
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
|
||||
* the Gatekeeper HAT). This allows us to use a single entry of the user's credential
|
||||
* to create multiple Gatekeeper HATs containing distinct challenges via
|
||||
* {@link LockPatternUtils#verifyGatekeeperPasswordHandle(long, long, int)}.
|
||||
*
|
||||
* Upon confirmation of the user's password, the Gatekeeper Password Handle will be returned
|
||||
* via onActivityResult with the key being {@link #EXTRA_KEY_GK_PW_HANDLE}.
|
||||
* @param requestGatekeeperPasswordHandle
|
||||
*/
|
||||
@NonNull public Builder setRequestGatekeeperPasswordHandle(
|
||||
boolean requestGatekeeperPasswordHandle) {
|
||||
mRequestGatekeeperPasswordHandle = requestGatekeeperPasswordHandle;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param requestWriteRepairModePassword Set {@code true} to request that
|
||||
* LockSettingsService writes the password data to the repair mode file after the user
|
||||
* credential is verified successfully.
|
||||
*/
|
||||
@NonNull public Builder setRequestWriteRepairModePassword(
|
||||
boolean requestWriteRepairModePassword) {
|
||||
mRequestWriteRepairModePassword = requestWriteRepairModePassword;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Support of ActivityResultLauncher.
|
||||
*
|
||||
* Which allowing the launch operation be controlled externally.
|
||||
* @param activityResultLauncher a launcher previously prepared.
|
||||
*/
|
||||
@NonNull public Builder setActivityResultLauncher(
|
||||
ActivityResultLauncher activityResultLauncher) {
|
||||
mActivityResultLauncher = activityResultLauncher;
|
||||
return this;
|
||||
}
|
||||
|
||||
@NonNull public ChooseLockSettingsHelper build() {
|
||||
if (!mAllowAnyUserId && mUserId != LockPatternUtils.USER_FRP
|
||||
&& mUserId != LockPatternUtils.USER_REPAIR_MODE) {
|
||||
Utils.enforceSameOwner(mActivity, mUserId);
|
||||
}
|
||||
|
||||
if (mExternal && mReturnCredentials && !mRemoteLockscreenValidation) {
|
||||
throw new IllegalArgumentException("External and ReturnCredentials specified. "
|
||||
+ " External callers should never be allowed to receive credentials in"
|
||||
+ " onActivityResult");
|
||||
}
|
||||
|
||||
if (mRequestGatekeeperPasswordHandle && !mReturnCredentials) {
|
||||
// HAT containing the signed challenge will not be available to the caller.
|
||||
Log.w(TAG, "Requested gatekeeper password handle but not requesting"
|
||||
+ " ReturnCredentials. Are you sure this is what you want?");
|
||||
}
|
||||
|
||||
return new ChooseLockSettingsHelper(this, mActivity, mFragment,
|
||||
mActivityResultLauncher);
|
||||
}
|
||||
|
||||
public boolean show() {
|
||||
return build().launch();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If a PIN, Pattern, or Password exists, prompt the user to confirm it.
|
||||
* @return true if the confirmation activity is shown (e.g. user has a credential set up)
|
||||
*/
|
||||
public boolean launch() {
|
||||
return launchConfirmationActivity(mBuilder.mRequestCode, mBuilder.mTitle, mBuilder.mHeader,
|
||||
mBuilder.mDescription, mBuilder.mReturnCredentials, mBuilder.mExternal,
|
||||
mBuilder.mForceVerifyPath, mBuilder.mUserId, mBuilder.mAlternateButton,
|
||||
mBuilder.mCheckBoxLabel, mBuilder.mRemoteLockscreenValidation,
|
||||
mBuilder.mRemoteLockscreenValidationSession,
|
||||
mBuilder.mRemoteLockscreenValidationServiceComponent, mBuilder.mAllowAnyUserId,
|
||||
mBuilder.mForegroundOnly, mBuilder.mRequestGatekeeperPasswordHandle,
|
||||
mBuilder.mRequestWriteRepairModePassword, mBuilder.mTaskOverlay);
|
||||
}
|
||||
|
||||
private boolean launchConfirmationActivity(int request, @Nullable CharSequence title,
|
||||
@Nullable CharSequence header, @Nullable CharSequence description,
|
||||
boolean returnCredentials, boolean external, boolean forceVerifyPath,
|
||||
int userId, @Nullable CharSequence alternateButton,
|
||||
@Nullable CharSequence checkboxLabel, boolean remoteLockscreenValidation,
|
||||
@Nullable RemoteLockscreenValidationSession remoteLockscreenValidationSession,
|
||||
@Nullable ComponentName remoteLockscreenValidationServiceComponent,
|
||||
boolean allowAnyUser, boolean foregroundOnly, boolean requestGatekeeperPasswordHandle,
|
||||
boolean requestWriteRepairModePassword, boolean taskOverlay) {
|
||||
Optional<Class<?>> activityClass = determineAppropriateActivityClass(
|
||||
returnCredentials, forceVerifyPath, userId, remoteLockscreenValidationSession);
|
||||
if (activityClass.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return launchConfirmationActivity(request, title, header, description, activityClass.get(),
|
||||
returnCredentials, external, forceVerifyPath, userId, alternateButton,
|
||||
checkboxLabel, remoteLockscreenValidation, remoteLockscreenValidationSession,
|
||||
remoteLockscreenValidationServiceComponent, allowAnyUser, foregroundOnly,
|
||||
requestGatekeeperPasswordHandle, requestWriteRepairModePassword, taskOverlay);
|
||||
}
|
||||
|
||||
private boolean launchConfirmationActivity(int request, CharSequence title, CharSequence header,
|
||||
CharSequence message, Class<?> activityClass, boolean returnCredentials,
|
||||
boolean external, boolean forceVerifyPath, int userId,
|
||||
@Nullable CharSequence alternateButton, @Nullable CharSequence checkbox,
|
||||
boolean remoteLockscreenValidation,
|
||||
@Nullable RemoteLockscreenValidationSession remoteLockscreenValidationSession,
|
||||
@Nullable ComponentName remoteLockscreenValidationServiceComponent,
|
||||
boolean allowAnyUser, boolean foregroundOnly, boolean requestGatekeeperPasswordHandle,
|
||||
boolean requestWriteRepairModePassword, boolean taskOverlay) {
|
||||
final Intent intent = new Intent();
|
||||
intent.putExtra(ConfirmDeviceCredentialBaseFragment.TITLE_TEXT, title);
|
||||
intent.putExtra(ConfirmDeviceCredentialBaseFragment.HEADER_TEXT, header);
|
||||
intent.putExtra(ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT, message);
|
||||
// TODO: Remove dark theme and show_cancel_button options since they are no longer used
|
||||
intent.putExtra(ConfirmDeviceCredentialBaseFragment.DARK_THEME, false);
|
||||
intent.putExtra(ConfirmDeviceCredentialBaseFragment.SHOW_CANCEL_BUTTON, false);
|
||||
intent.putExtra(ConfirmDeviceCredentialBaseFragment.SHOW_WHEN_LOCKED, 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_FORCE_VERIFY, forceVerifyPath);
|
||||
intent.putExtra(Intent.EXTRA_USER_ID, userId);
|
||||
intent.putExtra(KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL, alternateButton);
|
||||
intent.putExtra(KeyguardManager.EXTRA_CHECKBOX_LABEL, checkbox);
|
||||
intent.putExtra(KeyguardManager.EXTRA_REMOTE_LOCKSCREEN_VALIDATION_SESSION,
|
||||
remoteLockscreenValidationSession);
|
||||
intent.putExtra(Intent.EXTRA_COMPONENT_NAME, remoteLockscreenValidationServiceComponent);
|
||||
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOREGROUND_ONLY, foregroundOnly);
|
||||
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_ALLOW_ANY_USER, allowAnyUser);
|
||||
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE,
|
||||
requestGatekeeperPasswordHandle);
|
||||
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW,
|
||||
requestWriteRepairModePassword);
|
||||
|
||||
intent.setClassName(SETTINGS_PACKAGE_NAME, activityClass.getName());
|
||||
intent.putExtra(SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE,
|
||||
SettingsTransitionHelper.TransitionType.TRANSITION_SLIDE);
|
||||
|
||||
Intent inIntent = mFragment != null ? mFragment.getActivity().getIntent() :
|
||||
mActivity.getIntent();
|
||||
copyInternalExtras(inIntent, intent);
|
||||
Bundle launchOptions = createLaunchOptions(taskOverlay);
|
||||
if (external) {
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
|
||||
copyOptionalExtras(inIntent, intent);
|
||||
if (mActivityResultLauncher != null) {
|
||||
mActivityResultLauncher.launch(intent);
|
||||
} else if (mFragment != null) {
|
||||
mFragment.startActivity(intent, launchOptions);
|
||||
} else {
|
||||
mActivity.startActivity(intent, launchOptions);
|
||||
}
|
||||
} else {
|
||||
if (mActivityResultLauncher != null) {
|
||||
mActivityResultLauncher.launch(intent);
|
||||
} else if (mFragment != null) {
|
||||
mFragment.startActivityForResult(intent, request, launchOptions);
|
||||
} else {
|
||||
mActivity.startActivityForResult(intent, request, launchOptions);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private Bundle createLaunchOptions(boolean taskOverlay) {
|
||||
if (!taskOverlay) {
|
||||
return null;
|
||||
}
|
||||
ActivityOptions options = ActivityOptions.makeBasic();
|
||||
options.setLaunchTaskId(mActivity.getTaskId());
|
||||
options.setTaskOverlay(true /* taskOverlay */, true /* canResume */);
|
||||
return options.toBundle();
|
||||
}
|
||||
|
||||
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 RemoteLockscreenValidationSession remoteLockscreenValidationSession) {
|
||||
int lockType;
|
||||
if (remoteLockscreenValidationSession != null) {
|
||||
lockType = remoteLockscreenValidationSession.getLockType();
|
||||
} 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) {
|
||||
IntentSender intentSender = inIntent.getParcelableExtra(Intent.EXTRA_INTENT);
|
||||
if (intentSender != null) {
|
||||
outIntent.putExtra(Intent.EXTRA_INTENT, intentSender);
|
||||
}
|
||||
int taskId = inIntent.getIntExtra(Intent.EXTRA_TASK_ID, -1);
|
||||
if (taskId != -1) {
|
||||
outIntent.putExtra(Intent.EXTRA_TASK_ID, taskId);
|
||||
}
|
||||
// If we will launch another activity once credentials are confirmed, exclude from recents.
|
||||
// This is a workaround to a framework bug where affinity is incorrect for activities
|
||||
// that are started from a no display activity, as is ConfirmDeviceCredentialActivity.
|
||||
// TODO: Remove once that bug is fixed.
|
||||
if (intentSender != null || taskId != -1) {
|
||||
outIntent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
|
||||
outIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
|
||||
}
|
||||
}
|
||||
|
||||
private void copyInternalExtras(Intent inIntent, Intent outIntent) {
|
||||
SetupWizardUtils.copySetupExtras(inIntent, outIntent);
|
||||
String theme = inIntent.getStringExtra(WizardManagerHelper.EXTRA_THEME);
|
||||
if (theme != null) {
|
||||
outIntent.putExtra(WizardManagerHelper.EXTRA_THEME, theme);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* 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 android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog.Builder;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
|
||||
import com.android.settings.password.ChooseLockGeneric.ChooseLockGenericFragment;
|
||||
|
||||
import com.google.android.setupcompat.util.WizardManagerHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A dialog fragment similar to {@link ChooseLockGeneric} where the user can select from a few
|
||||
* lock screen types.
|
||||
*/
|
||||
public class ChooseLockTypeDialogFragment extends InstrumentedDialogFragment
|
||||
implements OnClickListener {
|
||||
|
||||
private static final String ARG_USER_ID = "userId";
|
||||
|
||||
private ScreenLockAdapter mAdapter;
|
||||
private ChooseLockGenericController mController;
|
||||
|
||||
public static ChooseLockTypeDialogFragment newInstance(int userId) {
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(ARG_USER_ID, userId);
|
||||
ChooseLockTypeDialogFragment fragment = new ChooseLockTypeDialogFragment();
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
public interface OnLockTypeSelectedListener {
|
||||
void onLockTypeSelected(ScreenLockType lock);
|
||||
|
||||
default void startChooseLockActivity(ScreenLockType selectedLockType, Activity activity) {
|
||||
Intent activityIntent = activity.getIntent();
|
||||
Intent intent = new Intent(activity, SetupChooseLockGeneric.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
|
||||
|
||||
// Copy the original extras into the new intent
|
||||
copyBooleanExtra(activityIntent, intent,
|
||||
ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, false);
|
||||
copyBooleanExtra(activityIntent, intent,
|
||||
ChooseLockGenericFragment.EXTRA_SHOW_OPTIONS_BUTTON, false);
|
||||
if (activityIntent.hasExtra(
|
||||
ChooseLockGenericFragment.EXTRA_CHOOSE_LOCK_GENERIC_EXTRAS)) {
|
||||
intent.putExtras(activityIntent.getBundleExtra(
|
||||
ChooseLockGenericFragment.EXTRA_CHOOSE_LOCK_GENERIC_EXTRAS));
|
||||
}
|
||||
intent.putExtra(LockPatternUtils.PASSWORD_TYPE_KEY, selectedLockType.defaultQuality);
|
||||
WizardManagerHelper.copyWizardManagerExtras(activityIntent, intent);
|
||||
activity.startActivity(intent);
|
||||
activity.finish();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void copyBooleanExtra(Intent from, Intent to, String name,
|
||||
boolean defaultValue) {
|
||||
to.putExtra(name, from.getBooleanExtra(name, defaultValue));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
final int userId = getArguments().getInt(ARG_USER_ID);
|
||||
mController = new ChooseLockGenericController.Builder(getContext(), userId)
|
||||
.setHideInsecureScreenLockTypes(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
OnLockTypeSelectedListener listener = null;
|
||||
Fragment parentFragment = getParentFragment();
|
||||
if (parentFragment instanceof OnLockTypeSelectedListener) {
|
||||
listener = (OnLockTypeSelectedListener) parentFragment;
|
||||
} else {
|
||||
Context context = getContext();
|
||||
if (context instanceof OnLockTypeSelectedListener) {
|
||||
listener = (OnLockTypeSelectedListener) context;
|
||||
}
|
||||
}
|
||||
if (listener != null) {
|
||||
listener.onLockTypeSelected(mAdapter.getItem(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
Context context = getContext();
|
||||
Builder builder = new Builder(context);
|
||||
List<ScreenLockType> locks = mController.getVisibleAndEnabledScreenLockTypes();
|
||||
mAdapter = new ScreenLockAdapter(context, locks, mController);
|
||||
builder.setAdapter(mAdapter, this);
|
||||
builder.setTitle(R.string.setup_lock_settings_options_dialog_title);
|
||||
Dialog alertDialog = builder.create();
|
||||
return alertDialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.SETTINGS_CHOOSE_LOCK_DIALOG;
|
||||
}
|
||||
|
||||
private static class ScreenLockAdapter extends ArrayAdapter<ScreenLockType> {
|
||||
|
||||
private final ChooseLockGenericController mController;
|
||||
|
||||
ScreenLockAdapter(
|
||||
Context context,
|
||||
List<ScreenLockType> locks,
|
||||
ChooseLockGenericController controller) {
|
||||
super(context, R.layout.choose_lock_dialog_item, locks);
|
||||
mController = controller;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View view, ViewGroup parent) {
|
||||
Context context = parent.getContext();
|
||||
if (view == null) {
|
||||
view = LayoutInflater.from(context)
|
||||
.inflate(R.layout.choose_lock_dialog_item, parent, false);
|
||||
}
|
||||
ScreenLockType lock = getItem(position);
|
||||
TextView textView = (TextView) view;
|
||||
textView.setText(mController.getTitle(lock));
|
||||
textView.setCompoundDrawablesRelativeWithIntrinsicBounds(
|
||||
getIconForScreenLock(context, lock), null, null, null);
|
||||
return view;
|
||||
}
|
||||
|
||||
private static Drawable getIconForScreenLock(Context context, ScreenLockType lock) {
|
||||
switch (lock) {
|
||||
case PATTERN:
|
||||
return context.getDrawable(R.drawable.ic_pattern);
|
||||
case PIN:
|
||||
return context.getDrawable(R.drawable.ic_pin);
|
||||
case PASSWORD:
|
||||
return context.getDrawable(R.drawable.ic_password);
|
||||
case NONE:
|
||||
case SWIPE:
|
||||
case MANAGED:
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,482 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) 2014 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.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PASSWORD_HEADER;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PATTERN_HEADER;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PIN_HEADER;
|
||||
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.KeyguardManager;
|
||||
import android.app.RemoteLockscreenValidationSession;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.app.admin.ManagedSubscriptionsPolicy;
|
||||
import android.app.trust.TrustManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.UserProperties;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Color;
|
||||
import android.hardware.biometrics.BiometricConstants;
|
||||
import android.hardware.biometrics.BiometricPrompt;
|
||||
import android.hardware.biometrics.BiometricPrompt.AuthenticationCallback;
|
||||
import android.hardware.biometrics.PromptInfo;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.os.storage.StorageManager;
|
||||
import android.util.Log;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.Utils;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Launch this when you want to confirm the user is present by asking them to enter their
|
||||
* PIN/password/pattern.
|
||||
*/
|
||||
public class ConfirmDeviceCredentialActivity extends FragmentActivity {
|
||||
public static final String TAG = ConfirmDeviceCredentialActivity.class.getSimpleName();
|
||||
|
||||
private static final String TAG_BIOMETRIC_FRAGMENT = "fragment";
|
||||
|
||||
public static class InternalActivity extends ConfirmDeviceCredentialActivity {
|
||||
}
|
||||
|
||||
private BiometricFragment mBiometricFragment;
|
||||
private DevicePolicyManager mDevicePolicyManager;
|
||||
private LockPatternUtils mLockPatternUtils;
|
||||
private UserManager mUserManager;
|
||||
private TrustManager mTrustManager;
|
||||
private Handler mHandler = new Handler(Looper.getMainLooper());
|
||||
private Context mContext;
|
||||
private boolean mCheckDevicePolicyManager;
|
||||
private boolean mTaskOverlay;
|
||||
|
||||
private String mTitle;
|
||||
private CharSequence mDetails;
|
||||
private int mUserId;
|
||||
// Used to force the verification path required to unlock profile that shares credentials with
|
||||
// with parent
|
||||
private boolean mForceVerifyPath = false;
|
||||
private boolean mGoingToBackground;
|
||||
private boolean mWaitingForBiometricCallback;
|
||||
|
||||
private Executor mExecutor = (runnable -> {
|
||||
mHandler.post(runnable);
|
||||
});
|
||||
|
||||
private AuthenticationCallback mAuthenticationCallback = new AuthenticationCallback() {
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
|
||||
if (!mGoingToBackground) {
|
||||
mWaitingForBiometricCallback = false;
|
||||
if (errorCode == BiometricPrompt.BIOMETRIC_ERROR_USER_CANCELED
|
||||
|| errorCode == BiometricPrompt.BIOMETRIC_ERROR_CANCELED) {
|
||||
finish();
|
||||
} else if (mUserManager.getUserInfo(mUserId) == null) {
|
||||
// This can happen when profile gets wiped due to too many failed auth attempts.
|
||||
Log.i(TAG, "Finishing, user no longer valid: " + mUserId);
|
||||
finish();
|
||||
} else {
|
||||
// All other errors go to some version of CC
|
||||
showConfirmCredentials();
|
||||
}
|
||||
} else if (mWaitingForBiometricCallback) { // mGoingToBackground is true
|
||||
mWaitingForBiometricCallback = false;
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
|
||||
mWaitingForBiometricCallback = false;
|
||||
mTrustManager.setDeviceLockedForUser(mUserId, false);
|
||||
final boolean isStrongAuth = result.getAuthenticationType()
|
||||
== BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL;
|
||||
ConfirmDeviceCredentialUtils.reportSuccessfulAttempt(mLockPatternUtils, mUserManager,
|
||||
mDevicePolicyManager, mUserId, isStrongAuth);
|
||||
ConfirmDeviceCredentialUtils.checkForPendingIntent(
|
||||
ConfirmDeviceCredentialActivity.this);
|
||||
|
||||
setResult(Activity.RESULT_OK);
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
mWaitingForBiometricCallback = false;
|
||||
mDevicePolicyManager.reportFailedBiometricAttempt(mUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSystemEvent(int event) {
|
||||
Log.d(TAG, "SystemEvent: " + event);
|
||||
switch (event) {
|
||||
case BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL:
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
|
||||
getWindow().setStatusBarColor(Color.TRANSPARENT);
|
||||
|
||||
mDevicePolicyManager = getSystemService(DevicePolicyManager.class);
|
||||
mUserManager = UserManager.get(this);
|
||||
mTrustManager = getSystemService(TrustManager.class);
|
||||
mLockPatternUtils = new LockPatternUtils(this);
|
||||
|
||||
Intent intent = getIntent();
|
||||
mContext = this;
|
||||
mCheckDevicePolicyManager = intent
|
||||
.getBooleanExtra(KeyguardManager.EXTRA_DISALLOW_BIOMETRICS_IF_POLICY_EXISTS, false);
|
||||
mTitle = intent.getStringExtra(KeyguardManager.EXTRA_TITLE);
|
||||
mDetails = intent.getCharSequenceExtra(KeyguardManager.EXTRA_DESCRIPTION);
|
||||
String alternateButton = intent.getStringExtra(
|
||||
KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL);
|
||||
final boolean frp =
|
||||
KeyguardManager.ACTION_CONFIRM_FRP_CREDENTIAL.equals(intent.getAction());
|
||||
final boolean repairMode =
|
||||
KeyguardManager.ACTION_CONFIRM_REPAIR_MODE_DEVICE_CREDENTIAL
|
||||
.equals(intent.getAction());
|
||||
final boolean remoteValidation =
|
||||
KeyguardManager.ACTION_CONFIRM_REMOTE_DEVICE_CREDENTIAL.equals(intent.getAction());
|
||||
mTaskOverlay = isInternalActivity()
|
||||
&& intent.getBooleanExtra(KeyguardManager.EXTRA_FORCE_TASK_OVERLAY, false);
|
||||
final boolean prepareRepairMode =
|
||||
KeyguardManager.ACTION_PREPARE_REPAIR_MODE_DEVICE_CREDENTIAL.equals(
|
||||
intent.getAction());
|
||||
|
||||
mUserId = UserHandle.myUserId();
|
||||
if (isInternalActivity()) {
|
||||
try {
|
||||
mUserId = Utils.getUserIdFromBundle(this, intent.getExtras());
|
||||
} catch (SecurityException se) {
|
||||
Log.e(TAG, "Invalid intent extra", se);
|
||||
}
|
||||
}
|
||||
final int effectiveUserId = mUserManager.getCredentialOwnerProfile(mUserId);
|
||||
final boolean isEffectiveUserManagedProfile =
|
||||
mUserManager.isManagedProfile(effectiveUserId);
|
||||
final UserProperties userProperties =
|
||||
mUserManager.getUserProperties(UserHandle.of(mUserId));
|
||||
// if the client app did not hand in a title and we are about to show the work challenge,
|
||||
// check whether there is a policy setting the organization name and use that as title
|
||||
if ((mTitle == null) && isEffectiveUserManagedProfile) {
|
||||
mTitle = getTitleFromOrganizationName(mUserId);
|
||||
}
|
||||
|
||||
final PromptInfo promptInfo = new PromptInfo();
|
||||
promptInfo.setTitle(mTitle);
|
||||
promptInfo.setDescription(mDetails);
|
||||
promptInfo.setDisallowBiometricsIfPolicyExists(mCheckDevicePolicyManager);
|
||||
|
||||
final int policyType = mDevicePolicyManager.getManagedSubscriptionsPolicy().getPolicyType();
|
||||
|
||||
if (isEffectiveUserManagedProfile
|
||||
&& (policyType == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS)) {
|
||||
promptInfo.setShowEmergencyCallButton(true);
|
||||
}
|
||||
|
||||
final @LockPatternUtils.CredentialType int credentialType = Utils.getCredentialType(
|
||||
mContext, effectiveUserId);
|
||||
if (mTitle == null) {
|
||||
promptInfo.setDeviceCredentialTitle(
|
||||
getTitleFromCredentialType(credentialType, isEffectiveUserManagedProfile));
|
||||
}
|
||||
if (mDetails == null) {
|
||||
promptInfo.setDeviceCredentialSubtitle(
|
||||
Utils.getConfirmCredentialStringForUser(this, mUserId, credentialType));
|
||||
}
|
||||
|
||||
boolean launchedBiometric = false;
|
||||
boolean launchedCDC = false;
|
||||
// If the target is a managed user and user key not unlocked yet, we will force unlock
|
||||
// tied profile so it will enable work mode and unlock managed profile, when personal
|
||||
// challenge is unlocked.
|
||||
if (frp) {
|
||||
final ChooseLockSettingsHelper.Builder builder =
|
||||
new ChooseLockSettingsHelper.Builder(this);
|
||||
launchedCDC = builder.setHeader(mTitle) // Show the title in the header location
|
||||
.setDescription(mDetails)
|
||||
.setAlternateButton(alternateButton)
|
||||
.setExternal(true)
|
||||
.setUserId(LockPatternUtils.USER_FRP)
|
||||
.show();
|
||||
} else if (repairMode) {
|
||||
final ChooseLockSettingsHelper.Builder builder =
|
||||
new ChooseLockSettingsHelper.Builder(this);
|
||||
launchedCDC = builder.setHeader(mTitle)
|
||||
.setDescription(mDetails)
|
||||
.setExternal(true)
|
||||
.setUserId(LockPatternUtils.USER_REPAIR_MODE)
|
||||
.show();
|
||||
} else if (remoteValidation) {
|
||||
RemoteLockscreenValidationSession remoteLockscreenValidationSession =
|
||||
intent.getParcelableExtra(
|
||||
KeyguardManager.EXTRA_REMOTE_LOCKSCREEN_VALIDATION_SESSION,
|
||||
RemoteLockscreenValidationSession.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)
|
||||
.setRemoteLockscreenValidationSession(remoteLockscreenValidationSession)
|
||||
.setRemoteLockscreenValidationServiceComponent(
|
||||
remoteLockscreenValidationServiceComponent)
|
||||
.setRequestGatekeeperPasswordHandle(true)
|
||||
.setReturnCredentials(true) // returns only password handle.
|
||||
.setHeader(mTitle) // Show the title in the header location
|
||||
.setDescription(mDetails)
|
||||
.setCheckboxLabel(checkboxLabel)
|
||||
.setAlternateButton(alternateButton)
|
||||
.setExternal(true)
|
||||
.show();
|
||||
return;
|
||||
} else if (prepareRepairMode) {
|
||||
final ChooseLockSettingsHelper.Builder builder =
|
||||
new ChooseLockSettingsHelper.Builder(this);
|
||||
launchedCDC = builder.setHeader(mTitle)
|
||||
.setDescription(mDetails)
|
||||
.setExternal(true)
|
||||
.setUserId(mUserId)
|
||||
.setTaskOverlay(mTaskOverlay)
|
||||
.setRequestWriteRepairModePassword(true)
|
||||
.setForceVerifyPath(true)
|
||||
.show();
|
||||
} else if (isEffectiveUserManagedProfile && isInternalActivity()) {
|
||||
// When the mForceVerifyPath is set to true, we launch the real confirm credential
|
||||
// activity with an explicit but fake challenge value (0L). This will result in
|
||||
// ConfirmLockPassword calling verifyTiedProfileChallenge() (if it's a profile with
|
||||
// unified challenge), due to the difference between
|
||||
// ConfirmLockPassword.startVerifyPassword() and
|
||||
// ConfirmLockPassword.startCheckPassword(). Calling verifyTiedProfileChallenge() here
|
||||
// is necessary when this is part of the turning on work profile flow, because it forces
|
||||
// unlocking the work profile even before the profile is running.
|
||||
// TODO: Remove the duplication of checkPassword and verifyPassword in
|
||||
// ConfirmLockPassword,
|
||||
// LockPatternChecker and LockPatternUtils. verifyPassword should be the only API to
|
||||
// use, which optionally accepts a challenge.
|
||||
mForceVerifyPath = true;
|
||||
if (isBiometricAllowed(effectiveUserId, mUserId)) {
|
||||
showBiometricPrompt(promptInfo, mUserId);
|
||||
launchedBiometric = true;
|
||||
} else {
|
||||
showConfirmCredentials();
|
||||
launchedCDC = true;
|
||||
}
|
||||
} else if (android.os.Flags.allowPrivateProfile()
|
||||
&& userProperties != null
|
||||
&& userProperties.isAuthAlwaysRequiredToDisableQuietMode()
|
||||
&& isInternalActivity()) {
|
||||
// Force verification path is required to be invoked as we might need to verify the
|
||||
// tied profile challenge if the profile is using the unified challenge mode. This
|
||||
// would result in ConfirmLockPassword.startVerifyPassword/
|
||||
// ConfirmLockPattern.startVerifyPattern being called instead of the
|
||||
// startCheckPassword/startCheckPattern
|
||||
mForceVerifyPath = userProperties.isCredentialShareableWithParent();
|
||||
if (android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()
|
||||
&& isBiometricAllowed(effectiveUserId, mUserId)) {
|
||||
promptInfo.setUseParentProfileForDeviceCredential(true);
|
||||
showBiometricPrompt(promptInfo, effectiveUserId);
|
||||
launchedBiometric = true;
|
||||
} else {
|
||||
showConfirmCredentials();
|
||||
launchedCDC = true;
|
||||
}
|
||||
} else {
|
||||
if (isBiometricAllowed(effectiveUserId, mUserId)) {
|
||||
// Don't need to check if biometrics / pin/pattern/pass are enrolled. It will go to
|
||||
// onAuthenticationError and do the right thing automatically.
|
||||
showBiometricPrompt(promptInfo, mUserId);
|
||||
launchedBiometric = true;
|
||||
} else {
|
||||
showConfirmCredentials();
|
||||
launchedCDC = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (launchedCDC) {
|
||||
finish();
|
||||
} else if (launchedBiometric) {
|
||||
// Keep this activity alive until BiometricPrompt goes away
|
||||
mWaitingForBiometricCallback = true;
|
||||
} else {
|
||||
Log.d(TAG, "No pattern, password or PIN set.");
|
||||
setResult(Activity.RESULT_OK);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
private String getTitleFromCredentialType(@LockPatternUtils.CredentialType int credentialType,
|
||||
boolean isEffectiveUserManagedProfile) {
|
||||
switch (credentialType) {
|
||||
case LockPatternUtils.CREDENTIAL_TYPE_PIN:
|
||||
if (isEffectiveUserManagedProfile) {
|
||||
return mDevicePolicyManager.getResources().getString(
|
||||
CONFIRM_WORK_PROFILE_PIN_HEADER,
|
||||
() -> getString(R.string.lockpassword_confirm_your_work_pin_header));
|
||||
}
|
||||
|
||||
return getString(R.string.lockpassword_confirm_your_pin_header);
|
||||
case LockPatternUtils.CREDENTIAL_TYPE_PATTERN:
|
||||
if (isEffectiveUserManagedProfile) {
|
||||
return mDevicePolicyManager.getResources().getString(
|
||||
CONFIRM_WORK_PROFILE_PATTERN_HEADER,
|
||||
() -> getString(
|
||||
R.string.lockpassword_confirm_your_work_pattern_header));
|
||||
}
|
||||
|
||||
return getString(R.string.lockpassword_confirm_your_pattern_header);
|
||||
case LockPatternUtils.CREDENTIAL_TYPE_PASSWORD:
|
||||
if (isEffectiveUserManagedProfile) {
|
||||
return mDevicePolicyManager.getResources().getString(
|
||||
CONFIRM_WORK_PROFILE_PASSWORD_HEADER,
|
||||
() -> getString(
|
||||
R.string.lockpassword_confirm_your_work_password_header));
|
||||
}
|
||||
|
||||
return getString(R.string.lockpassword_confirm_your_password_header);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
// Translucent activity that is "visible", so it doesn't complain about finish()
|
||||
// not being called before onResume().
|
||||
setVisible(true);
|
||||
|
||||
if ((getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
|
||||
!= Configuration.UI_MODE_NIGHT_YES) {
|
||||
getWindow().getInsetsController().setSystemBarsAppearance(
|
||||
APPEARANCE_LIGHT_STATUS_BARS, APPEARANCE_LIGHT_STATUS_BARS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (!isChangingConfigurations()) {
|
||||
mGoingToBackground = true;
|
||||
if (!mWaitingForBiometricCallback) {
|
||||
finish();
|
||||
}
|
||||
} else {
|
||||
mGoingToBackground = false;
|
||||
}
|
||||
}
|
||||
|
||||
// User could be locked while Effective user is unlocked even though the effective owns the
|
||||
// credential. Otherwise, biometric can't unlock fbe/keystore through
|
||||
// verifyTiedProfileChallenge. In such case, we also wanna show the user message that
|
||||
// biometric is disabled due to device restart.
|
||||
private boolean isStrongAuthRequired(int effectiveUserId) {
|
||||
return !mLockPatternUtils.isBiometricAllowedForUser(effectiveUserId)
|
||||
|| doesUserStateEnforceStrongAuth(mUserId);
|
||||
}
|
||||
|
||||
private boolean doesUserStateEnforceStrongAuth(int userId) {
|
||||
if (android.os.Flags.allowPrivateProfile()
|
||||
&& android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) {
|
||||
// Check if CE storage for user is locked since biometrics can't unlock fbe/keystore of
|
||||
// the profile user using verifyTiedProfileChallenge. Biometrics can still be used if
|
||||
// the user is stopped with delayed locking (i.e., with storage unlocked), so the user
|
||||
// state (whether the user is in the RUNNING_UNLOCKED state) should not be relied upon.
|
||||
return !StorageManager.isCeStorageUnlocked(userId);
|
||||
}
|
||||
return !mUserManager.isUserUnlocked(userId);
|
||||
}
|
||||
|
||||
private boolean isBiometricAllowed(int effectiveUserId, int realUserId) {
|
||||
return !isStrongAuthRequired(effectiveUserId) && !mLockPatternUtils
|
||||
.hasPendingEscrowToken(realUserId);
|
||||
}
|
||||
|
||||
private void showBiometricPrompt(PromptInfo promptInfo, int userId) {
|
||||
mBiometricFragment = (BiometricFragment) getSupportFragmentManager()
|
||||
.findFragmentByTag(TAG_BIOMETRIC_FRAGMENT);
|
||||
boolean newFragment = false;
|
||||
|
||||
if (mBiometricFragment == null) {
|
||||
mBiometricFragment = BiometricFragment.newInstance(promptInfo);
|
||||
newFragment = true;
|
||||
}
|
||||
mBiometricFragment.setCallbacks(mExecutor, mAuthenticationCallback);
|
||||
// TODO(b/315864564): Move the logic of choosing the user id against which the
|
||||
// authentication needs to happen to the BiometricPrompt API
|
||||
mBiometricFragment.setUser(userId);
|
||||
|
||||
if (newFragment) {
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.add(mBiometricFragment, TAG_BIOMETRIC_FRAGMENT).commit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows ConfirmDeviceCredentials for normal apps.
|
||||
*/
|
||||
private void showConfirmCredentials() {
|
||||
boolean launched = new ChooseLockSettingsHelper.Builder(this)
|
||||
.setHeader(mTitle)
|
||||
.setDescription(mDetails)
|
||||
.setExternal(true)
|
||||
.setUserId(mUserId)
|
||||
.setTaskOverlay(mTaskOverlay)
|
||||
.setForceVerifyPath(mForceVerifyPath)
|
||||
.show();
|
||||
|
||||
if (!launched) {
|
||||
Log.d(TAG, "No pin/pattern/pass set");
|
||||
setResult(Activity.RESULT_OK);
|
||||
}
|
||||
finish();
|
||||
}
|
||||
|
||||
private boolean isInternalActivity() {
|
||||
return this instanceof ConfirmDeviceCredentialActivity.InternalActivity;
|
||||
}
|
||||
|
||||
private String getTitleFromOrganizationName(int userId) {
|
||||
DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(
|
||||
Context.DEVICE_POLICY_SERVICE);
|
||||
CharSequence organizationNameForUser = (dpm != null)
|
||||
? dpm.getOrganizationNameForUser(userId) : null;
|
||||
return organizationNameForUser != null ? organizationNameForUser.toString() : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.UserManager;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SettingsActivity;
|
||||
import com.android.settings.SetupWizardUtils;
|
||||
import com.android.settings.Utils;
|
||||
|
||||
import com.google.android.setupdesign.util.ThemeHelper;
|
||||
|
||||
public abstract class ConfirmDeviceCredentialBaseActivity extends SettingsActivity {
|
||||
|
||||
private static final String STATE_IS_KEYGUARD_LOCKED = "STATE_IS_KEYGUARD_LOCKED";
|
||||
private static final String TAG = "ConfirmDeviceCredentialBaseActivity";
|
||||
|
||||
enum ConfirmCredentialTheme {
|
||||
NORMAL,
|
||||
DARK, // TODO(yukl): Clean up DARK theme, as it should no longer be used
|
||||
WORK
|
||||
}
|
||||
|
||||
private boolean mRestoring;
|
||||
private boolean mEnterAnimationPending;
|
||||
private boolean mFirstTimeVisible = true;
|
||||
private boolean mIsKeyguardLocked = false;
|
||||
private ConfirmCredentialTheme mConfirmCredentialTheme;
|
||||
|
||||
private boolean isInternalActivity() {
|
||||
return (this instanceof ConfirmLockPassword.InternalActivity)
|
||||
|| (this instanceof ConfirmLockPattern.InternalActivity);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedState) {
|
||||
final int credentialOwnerUserId;
|
||||
try {
|
||||
credentialOwnerUserId = Utils.getCredentialOwnerUserId(this,
|
||||
Utils.getUserIdFromBundle(this, getIntent().getExtras(), isInternalActivity()));
|
||||
} catch (SecurityException e) {
|
||||
Log.e(TAG, "Invalid user Id supplied", e);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
if (UserManager.get(this).isManagedProfile(credentialOwnerUserId)) {
|
||||
setTheme(SetupWizardUtils.getTheme(this, getIntent()));
|
||||
mConfirmCredentialTheme = ConfirmCredentialTheme.WORK;
|
||||
} else if (getIntent().getBooleanExtra(
|
||||
ConfirmDeviceCredentialBaseFragment.DARK_THEME, false)) {
|
||||
setTheme(R.style.Theme_ConfirmDeviceCredentialsDark);
|
||||
mConfirmCredentialTheme = ConfirmCredentialTheme.DARK;
|
||||
} else {
|
||||
setTheme(SetupWizardUtils.getTheme(this, getIntent()));
|
||||
mConfirmCredentialTheme = ConfirmCredentialTheme.NORMAL;
|
||||
}
|
||||
ThemeHelper.trySetDynamicColor(this);
|
||||
super.onCreate(savedState);
|
||||
|
||||
if (mConfirmCredentialTheme == ConfirmCredentialTheme.NORMAL) {
|
||||
// Prevent the content parent from consuming the window insets because GlifLayout uses
|
||||
// it to show the status bar background.
|
||||
findViewById(R.id.content_parent).setFitsSystemWindows(false);
|
||||
}
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
|
||||
mIsKeyguardLocked = savedState == null
|
||||
? getSystemService(KeyguardManager.class).isKeyguardLocked()
|
||||
: savedState.getBoolean(STATE_IS_KEYGUARD_LOCKED, false);
|
||||
// If the activity is launched, not due to config change, when keyguard is locked and the
|
||||
// flag is set, assume it's launched on top of keyguard on purpose.
|
||||
// TODO: Don't abuse SHOW_WHEN_LOCKED and don't check isKeyguardLocked.
|
||||
// Set extra SHOW_WHEN_LOCKED and WindowManager FLAG_SHOW_WHEN_LOCKED only if it's
|
||||
// truly on top of keyguard on purpose
|
||||
if (mIsKeyguardLocked && getIntent().getBooleanExtra(
|
||||
ConfirmDeviceCredentialBaseFragment.SHOW_WHEN_LOCKED, false)) {
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
|
||||
}
|
||||
CharSequence msg = getIntent().getStringExtra(
|
||||
ConfirmDeviceCredentialBaseFragment.TITLE_TEXT);
|
||||
setTitle(msg);
|
||||
if (getActionBar() != null) {
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
getActionBar().setHomeButtonEnabled(true);
|
||||
}
|
||||
mRestoring = savedState != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putBoolean(STATE_IS_KEYGUARD_LOCKED, mIsKeyguardLocked);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (!isChangingConfigurations() && !mRestoring
|
||||
&& mConfirmCredentialTheme == ConfirmCredentialTheme.DARK && mFirstTimeVisible) {
|
||||
mFirstTimeVisible = false;
|
||||
prepareEnterAnimation();
|
||||
mEnterAnimationPending = true;
|
||||
}
|
||||
}
|
||||
|
||||
private ConfirmDeviceCredentialBaseFragment getFragment() {
|
||||
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.main_content);
|
||||
if (fragment != null && fragment instanceof ConfirmDeviceCredentialBaseFragment) {
|
||||
return (ConfirmDeviceCredentialBaseFragment) fragment;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnterAnimationComplete() {
|
||||
super.onEnterAnimationComplete();
|
||||
if (mEnterAnimationPending) {
|
||||
startEnterAnimation();
|
||||
mEnterAnimationPending = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
final boolean foregroundOnly = getIntent().getBooleanExtra(
|
||||
ChooseLockSettingsHelper.EXTRA_KEY_FOREGROUND_ONLY, false);
|
||||
if (!isChangingConfigurations() && foregroundOnly) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
// 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
|
||||
// the instance to become gc-able.
|
||||
new Handler(Looper.myLooper()).postDelayed(() -> {
|
||||
System.gc();
|
||||
System.runFinalization();
|
||||
System.gc();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
super.finish();
|
||||
if (getIntent().getBooleanExtra(
|
||||
ConfirmDeviceCredentialBaseFragment.USE_FADE_ANIMATION, false)) {
|
||||
overridePendingTransition(0, R.anim.confirm_credential_biometric_transition_exit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isToolbarEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void prepareEnterAnimation() {
|
||||
getFragment().prepareEnterAnimation();
|
||||
}
|
||||
|
||||
public void startEnterAnimation() {
|
||||
getFragment().startEnterAnimation();
|
||||
}
|
||||
|
||||
public ConfirmCredentialTheme getConfirmCredentialTheme() {
|
||||
return mConfirmCredentialTheme;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,523 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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
|
||||
*/
|
||||
|
||||
// TODO (b/35202196): move this class out of the root of the package.
|
||||
package com.android.settings.password;
|
||||
|
||||
import static android.app.Activity.RESULT_FIRST_USER;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LOCK_ATTEMPTS_FAILED;
|
||||
|
||||
import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.app.KeyguardManager;
|
||||
import android.app.RemoteLockscreenValidationSession;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.app.admin.ManagedSubscriptionsPolicy;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.hardware.biometrics.BiometricManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.service.remotelockscreenvalidation.RemoteLockscreenValidationClient;
|
||||
import android.telecom.TelecomManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.FeatureFlagUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.internal.widget.LockscreenCredential;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.Utils;
|
||||
import com.android.settings.core.InstrumentedFragment;
|
||||
|
||||
import com.google.android.setupdesign.GlifLayout;
|
||||
|
||||
/**
|
||||
* Base fragment to be shared for PIN/Pattern/Password confirmation fragments.
|
||||
*/
|
||||
public abstract class ConfirmDeviceCredentialBaseFragment extends InstrumentedFragment {
|
||||
public static final String TAG = ConfirmDeviceCredentialBaseFragment.class.getSimpleName();
|
||||
public static final String TITLE_TEXT = SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.title";
|
||||
public static final String HEADER_TEXT = SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.header";
|
||||
public static final String DETAILS_TEXT = SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.details";
|
||||
public static final String DARK_THEME = SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.darkTheme";
|
||||
public static final String SHOW_CANCEL_BUTTON =
|
||||
SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.showCancelButton";
|
||||
public static final String SHOW_WHEN_LOCKED =
|
||||
SETTINGS_PACKAGE_NAME + ".ConfirmCredentials.showWhenLocked";
|
||||
public static final String USE_FADE_ANIMATION =
|
||||
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_MANAGED_PROFILE = 2;
|
||||
protected static final int USER_TYPE_SECONDARY = 3;
|
||||
|
||||
/** Time we wait before clearing a wrong input attempt (e.g. pattern) and the error message. */
|
||||
protected static final long CLEAR_WRONG_ATTEMPT_TIMEOUT_MS = 3000;
|
||||
|
||||
protected static final String FRAGMENT_TAG_REMOTE_LOCKSCREEN_VALIDATION =
|
||||
"remote_lockscreen_validation";
|
||||
|
||||
protected boolean mReturnCredentials = false;
|
||||
protected boolean mReturnGatekeeperPassword = false;
|
||||
protected boolean mForceVerifyPath = false;
|
||||
protected GlifLayout mGlifLayout;
|
||||
protected CheckBox mCheckBox;
|
||||
protected Button mCancelButton;
|
||||
/** Button allowing managed profile password reset, null when is not shown. */
|
||||
@Nullable protected Button mForgotButton;
|
||||
protected int mEffectiveUserId;
|
||||
protected int mUserId;
|
||||
protected UserManager mUserManager;
|
||||
protected LockPatternUtils mLockPatternUtils;
|
||||
protected DevicePolicyManager mDevicePolicyManager;
|
||||
protected TextView mErrorTextView;
|
||||
protected final Handler mHandler = new Handler();
|
||||
protected boolean mFrp;
|
||||
protected boolean mRemoteValidation;
|
||||
protected boolean mRequestWriteRepairModePassword;
|
||||
protected boolean mRepairMode;
|
||||
protected CharSequence mAlternateButtonText;
|
||||
protected BiometricManager mBiometricManager;
|
||||
@Nullable protected RemoteLockscreenValidationSession mRemoteLockscreenValidationSession;
|
||||
/** Credential saved so the credential can be set for device if remote validation passes */
|
||||
@Nullable protected RemoteLockscreenValidationClient mRemoteLockscreenValidationClient;
|
||||
protected RemoteLockscreenValidationFragment mRemoteLockscreenValidationFragment;
|
||||
|
||||
private boolean isInternalActivity() {
|
||||
return (getActivity() instanceof ConfirmLockPassword.InternalActivity)
|
||||
|| (getActivity() instanceof ConfirmLockPattern.InternalActivity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
final Intent intent = getActivity().getIntent();
|
||||
mAlternateButtonText = intent.getCharSequenceExtra(
|
||||
KeyguardManager.EXTRA_ALTERNATE_BUTTON_LABEL);
|
||||
mReturnCredentials = intent.getBooleanExtra(
|
||||
ChooseLockSettingsHelper.EXTRA_KEY_RETURN_CREDENTIALS, false);
|
||||
|
||||
mReturnGatekeeperPassword = intent.getBooleanExtra(
|
||||
ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, false);
|
||||
mForceVerifyPath = intent.getBooleanExtra(
|
||||
ChooseLockSettingsHelper.EXTRA_KEY_FORCE_VERIFY, false);
|
||||
mRequestWriteRepairModePassword = intent.getBooleanExtra(
|
||||
ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_WRITE_REPAIR_MODE_PW, false);
|
||||
|
||||
if (intent.getBooleanExtra(IS_REMOTE_LOCKSCREEN_VALIDATION, false)) {
|
||||
if (FeatureFlagUtils.isEnabled(getContext(),
|
||||
FeatureFlagUtils.SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION)) {
|
||||
mRemoteValidation = true;
|
||||
} else {
|
||||
onRemoteLockscreenValidationFailure(
|
||||
"Remote lockscreen validation not enabled.");
|
||||
}
|
||||
}
|
||||
if (mRemoteValidation) {
|
||||
mRemoteLockscreenValidationSession = intent.getParcelableExtra(
|
||||
KeyguardManager.EXTRA_REMOTE_LOCKSCREEN_VALIDATION_SESSION,
|
||||
RemoteLockscreenValidationSession.class);
|
||||
if (mRemoteLockscreenValidationSession == null
|
||||
|| mRemoteLockscreenValidationSession.getRemainingAttempts() == 0) {
|
||||
onRemoteLockscreenValidationFailure("RemoteLockscreenValidationSession is null or "
|
||||
+ "no more attempts for remote lockscreen validation.");
|
||||
}
|
||||
|
||||
ComponentName remoteLockscreenValidationServiceComponent =
|
||||
intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME, ComponentName.class);
|
||||
if (remoteLockscreenValidationServiceComponent == null) {
|
||||
onRemoteLockscreenValidationFailure(
|
||||
"RemoteLockscreenValidationService ComponentName is null");
|
||||
}
|
||||
mRemoteLockscreenValidationClient = RemoteLockscreenValidationClient
|
||||
.create(getContext(), remoteLockscreenValidationServiceComponent);
|
||||
if (!mRemoteLockscreenValidationClient.isServiceAvailable()) {
|
||||
onRemoteLockscreenValidationFailure(String.format(
|
||||
"RemoteLockscreenValidationService at %s is not available",
|
||||
remoteLockscreenValidationServiceComponent.getClassName()));
|
||||
}
|
||||
|
||||
mRemoteLockscreenValidationFragment =
|
||||
(RemoteLockscreenValidationFragment) getFragmentManager()
|
||||
.findFragmentByTag(FRAGMENT_TAG_REMOTE_LOCKSCREEN_VALIDATION);
|
||||
if (mRemoteLockscreenValidationFragment == null) {
|
||||
mRemoteLockscreenValidationFragment = new RemoteLockscreenValidationFragment();
|
||||
getFragmentManager().beginTransaction().add(mRemoteLockscreenValidationFragment,
|
||||
FRAGMENT_TAG_REMOTE_LOCKSCREEN_VALIDATION).commit();
|
||||
}
|
||||
}
|
||||
|
||||
// Only take this argument into account if it belongs to the current profile.
|
||||
mUserId = Utils.getUserIdFromBundle(getActivity(), intent.getExtras(),
|
||||
isInternalActivity());
|
||||
mFrp = (mUserId == LockPatternUtils.USER_FRP);
|
||||
mRepairMode = (mUserId == LockPatternUtils.USER_REPAIR_MODE);
|
||||
mUserManager = UserManager.get(getActivity());
|
||||
mEffectiveUserId = mUserManager.getCredentialOwnerProfile(mUserId);
|
||||
mLockPatternUtils = new LockPatternUtils(getActivity());
|
||||
mDevicePolicyManager = (DevicePolicyManager) getActivity().getSystemService(
|
||||
Context.DEVICE_POLICY_SERVICE);
|
||||
mBiometricManager = getActivity().getSystemService(BiometricManager.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
mCancelButton = view.findViewById(R.id.cancelButton);
|
||||
boolean showCancelButton = mRemoteValidation || getActivity().getIntent().getBooleanExtra(
|
||||
SHOW_CANCEL_BUTTON, false);
|
||||
boolean hasAlternateButton = (mFrp || mRemoteValidation) && !TextUtils.isEmpty(
|
||||
mAlternateButtonText);
|
||||
mCancelButton.setVisibility(showCancelButton || hasAlternateButton
|
||||
? View.VISIBLE : View.GONE);
|
||||
if (hasAlternateButton) {
|
||||
mCancelButton.setText(mAlternateButtonText);
|
||||
}
|
||||
mCancelButton.setOnClickListener(v -> {
|
||||
if (hasAlternateButton) {
|
||||
getActivity().setResult(KeyguardManager.RESULT_ALTERNATE);
|
||||
getActivity().finish();
|
||||
} else if (mRemoteValidation) {
|
||||
onRemoteLockscreenValidationFailure("Forgot lockscreen credential button pressed.");
|
||||
}
|
||||
});
|
||||
setupForgotButtonIfManagedProfile(view);
|
||||
|
||||
mCheckBox = view.findViewById(R.id.checkbox);
|
||||
if (mCheckBox != null && mRemoteValidation) {
|
||||
mCheckBox.setVisibility(View.VISIBLE);
|
||||
}
|
||||
setupEmergencyCallButtonIfManagedSubscription(view);
|
||||
}
|
||||
|
||||
private void setupEmergencyCallButtonIfManagedSubscription(View view) {
|
||||
int policyType = getContext().getSystemService(
|
||||
DevicePolicyManager.class).getManagedSubscriptionsPolicy().getPolicyType();
|
||||
|
||||
if (policyType == ManagedSubscriptionsPolicy.TYPE_ALL_MANAGED_SUBSCRIPTIONS) {
|
||||
Button emergencyCallButton = view.findViewById(R.id.emergencyCallButton);
|
||||
if (emergencyCallButton == null) {
|
||||
Log.wtf(TAG,
|
||||
"Emergency call button not found in managed profile credential dialog");
|
||||
return;
|
||||
}
|
||||
emergencyCallButton.setVisibility(View.VISIBLE);
|
||||
emergencyCallButton.setOnClickListener(v -> {
|
||||
final Intent intent = getActivity()
|
||||
.getSystemService(TelecomManager.class)
|
||||
.createLaunchEmergencyDialerIntent(null)
|
||||
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
getActivity().startActivity(intent);
|
||||
getActivity().finish();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void setupForgotButtonIfManagedProfile(View view) {
|
||||
if (mUserManager.isManagedProfile(mUserId)
|
||||
&& mUserManager.isQuietModeEnabled(UserHandle.of(mUserId))
|
||||
&& mDevicePolicyManager.canProfileOwnerResetPasswordWhenLocked(mUserId)) {
|
||||
mForgotButton = view.findViewById(R.id.forgotButton);
|
||||
if (mForgotButton == null) {
|
||||
Log.wtf(TAG, "Forgot button not found in managed profile credential dialog");
|
||||
return;
|
||||
}
|
||||
mForgotButton.setVisibility(View.VISIBLE);
|
||||
mForgotButton.setOnClickListener(v -> {
|
||||
final Intent intent = new Intent();
|
||||
intent.setClassName(SETTINGS_PACKAGE_NAME, ForgotPasswordActivity.class.getName());
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.putExtra(Intent.EXTRA_USER_ID, mUserId);
|
||||
getActivity().startActivity(intent);
|
||||
getActivity().finish();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// User could be locked while Effective user is unlocked even though the effective owns the
|
||||
// credential. Otherwise, fingerprint can't unlock fbe/keystore through
|
||||
// verifyTiedProfileChallenge. In such case, we also wanna show the user message that
|
||||
// fingerprint is disabled due to device restart.
|
||||
protected boolean isStrongAuthRequired() {
|
||||
return mFrp || mRepairMode
|
||||
|| !mLockPatternUtils.isBiometricAllowedForUser(mEffectiveUserId)
|
||||
|| !mUserManager.isUserUnlocked(mUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
refreshLockScreen();
|
||||
}
|
||||
|
||||
protected void refreshLockScreen() {
|
||||
updateErrorMessage(mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId));
|
||||
}
|
||||
|
||||
protected void setAccessibilityTitle(CharSequence supplementalText) {
|
||||
Intent intent = getActivity().getIntent();
|
||||
if (intent != null) {
|
||||
CharSequence titleText = intent.getCharSequenceExtra(
|
||||
ConfirmDeviceCredentialBaseFragment.TITLE_TEXT);
|
||||
if (supplementalText == null) {
|
||||
return;
|
||||
}
|
||||
if (titleText == null) {
|
||||
getActivity().setTitle(supplementalText);
|
||||
} else {
|
||||
String accessibilityTitle =
|
||||
new StringBuilder(titleText).append(",").append(supplementalText).toString();
|
||||
getActivity().setTitle(Utils.createAccessibleSequence(titleText, accessibilityTitle));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
if (mRemoteLockscreenValidationClient != null) {
|
||||
mRemoteLockscreenValidationClient.disconnect();
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
protected abstract void authenticationSucceeded();
|
||||
|
||||
public void prepareEnterAnimation() {
|
||||
}
|
||||
|
||||
public void startEnterAnimation() {
|
||||
}
|
||||
|
||||
protected void reportFailedAttempt() {
|
||||
updateErrorMessage(
|
||||
mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId) + 1);
|
||||
mLockPatternUtils.reportFailedPasswordAttempt(mEffectiveUserId);
|
||||
}
|
||||
|
||||
protected void updateErrorMessage(int numAttempts) {
|
||||
final int maxAttempts =
|
||||
mLockPatternUtils.getMaximumFailedPasswordsForWipe(mEffectiveUserId);
|
||||
if (maxAttempts <= 0 || numAttempts <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the on-screen error string
|
||||
if (mErrorTextView != null) {
|
||||
final String message = getActivity().getString(
|
||||
R.string.lock_failed_attempts_before_wipe, numAttempts, maxAttempts);
|
||||
showError(message, 0);
|
||||
}
|
||||
|
||||
// Only show popup dialog before the last attempt and before wipe
|
||||
final int remainingAttempts = maxAttempts - numAttempts;
|
||||
if (remainingAttempts > 1) {
|
||||
return;
|
||||
}
|
||||
final FragmentManager fragmentManager = getChildFragmentManager();
|
||||
final int userType = getUserTypeForWipe();
|
||||
if (remainingAttempts == 1) {
|
||||
// Last try
|
||||
final String title = getActivity().getString(
|
||||
R.string.lock_last_attempt_before_wipe_warning_title);
|
||||
final String overrideMessageId = getLastTryOverrideErrorMessageId(userType);
|
||||
final int defaultMessageId = getLastTryDefaultErrorMessage(userType);
|
||||
final String message = mDevicePolicyManager.getResources().getString(
|
||||
overrideMessageId, () -> getString(defaultMessageId));
|
||||
LastTryDialog.show(fragmentManager, title, message,
|
||||
android.R.string.ok, false /* dismiss */);
|
||||
} else {
|
||||
// Device, profile, or secondary user is wiped
|
||||
final String message = getWipeMessage(userType);
|
||||
LastTryDialog.show(fragmentManager, null /* title */, message,
|
||||
com.android.settingslib.R.string.failed_attempts_now_wiping_dialog_dismiss,
|
||||
true /* dismiss */);
|
||||
}
|
||||
}
|
||||
|
||||
private int getUserTypeForWipe() {
|
||||
final UserInfo userToBeWiped = mUserManager.getUserInfo(
|
||||
mDevicePolicyManager.getProfileWithMinimumFailedPasswordsForWipe(mEffectiveUserId));
|
||||
if (userToBeWiped == null || userToBeWiped.isPrimary()) {
|
||||
return USER_TYPE_PRIMARY;
|
||||
} else if (userToBeWiped.isManagedProfile()) {
|
||||
return USER_TYPE_MANAGED_PROFILE;
|
||||
} else {
|
||||
return USER_TYPE_SECONDARY;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract String getLastTryOverrideErrorMessageId(int userType);
|
||||
protected abstract int getLastTryDefaultErrorMessage(int userType);
|
||||
|
||||
private String getWipeMessage(int userType) {
|
||||
switch (userType) {
|
||||
case USER_TYPE_PRIMARY:
|
||||
return getString(com.android.settingslib
|
||||
.R.string.failed_attempts_now_wiping_device);
|
||||
case USER_TYPE_MANAGED_PROFILE:
|
||||
return mDevicePolicyManager.getResources().getString(
|
||||
WORK_PROFILE_LOCK_ATTEMPTS_FAILED,
|
||||
() -> getString(
|
||||
com.android.settingslib.R.string.failed_attempts_now_wiping_profile));
|
||||
case USER_TYPE_SECONDARY:
|
||||
return getString(com.android.settingslib.R.string.failed_attempts_now_wiping_user);
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized user type:" + userType);
|
||||
}
|
||||
}
|
||||
|
||||
private final Runnable mResetErrorRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mErrorTextView.setText("");
|
||||
}
|
||||
};
|
||||
|
||||
protected void showError(CharSequence msg, long timeout) {
|
||||
mErrorTextView.setText(msg);
|
||||
onShowError();
|
||||
mHandler.removeCallbacks(mResetErrorRunnable);
|
||||
if (timeout != 0) {
|
||||
mHandler.postDelayed(mResetErrorRunnable, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
protected void clearResetErrorRunnable() {
|
||||
mHandler.removeCallbacks(mResetErrorRunnable);
|
||||
}
|
||||
|
||||
protected void validateGuess(LockscreenCredential credentialGuess) {
|
||||
mRemoteLockscreenValidationFragment.validateLockscreenGuess(
|
||||
mRemoteLockscreenValidationClient, credentialGuess,
|
||||
mRemoteLockscreenValidationSession.getSourcePublicKey(), mCheckBox.isChecked());
|
||||
}
|
||||
|
||||
protected void updateRemoteLockscreenValidationViews() {
|
||||
if (!mRemoteValidation || mRemoteLockscreenValidationFragment == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean enable = mRemoteLockscreenValidationFragment.isRemoteValidationInProgress();
|
||||
mGlifLayout.setProgressBarShown(enable);
|
||||
mCheckBox.setEnabled(!enable);
|
||||
mCancelButton.setEnabled(!enable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finishes the activity with result code {@link android.app.Activity#RESULT_FIRST_USER}
|
||||
* after logging the error message.
|
||||
* @param message Optional message to log.
|
||||
*/
|
||||
public void onRemoteLockscreenValidationFailure(String message) {
|
||||
if (!TextUtils.isEmpty(message)) {
|
||||
Log.w(TAG, message);
|
||||
}
|
||||
getActivity().setResult(RESULT_FIRST_USER);
|
||||
getActivity().finish();
|
||||
}
|
||||
|
||||
protected abstract void onShowError();
|
||||
|
||||
protected void showError(int msg, long timeout) {
|
||||
showError(getText(msg), timeout);
|
||||
}
|
||||
|
||||
public static class LastTryDialog extends DialogFragment {
|
||||
private static final String TAG = LastTryDialog.class.getSimpleName();
|
||||
|
||||
private static final String ARG_TITLE = "title";
|
||||
private static final String ARG_MESSAGE = "message";
|
||||
private static final String ARG_BUTTON = "button";
|
||||
private static final String ARG_DISMISS = "dismiss";
|
||||
|
||||
static boolean show(FragmentManager from, String title, String message, int button,
|
||||
boolean dismiss) {
|
||||
LastTryDialog existent = (LastTryDialog) from.findFragmentByTag(TAG);
|
||||
if (existent != null && !existent.isRemoving()) {
|
||||
return false;
|
||||
}
|
||||
Bundle args = new Bundle();
|
||||
args.putString(ARG_TITLE, title);
|
||||
args.putString(ARG_MESSAGE, message);
|
||||
args.putInt(ARG_BUTTON, button);
|
||||
args.putBoolean(ARG_DISMISS, dismiss);
|
||||
|
||||
DialogFragment dialog = new LastTryDialog();
|
||||
dialog.setArguments(args);
|
||||
dialog.show(from, TAG);
|
||||
from.executePendingTransactions();
|
||||
return true;
|
||||
}
|
||||
|
||||
static void hide(FragmentManager from) {
|
||||
LastTryDialog dialog = (LastTryDialog) from.findFragmentByTag(TAG);
|
||||
if (dialog != null) {
|
||||
dialog.dismissAllowingStateLoss();
|
||||
from.executePendingTransactions();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dialog setup.
|
||||
* <p>
|
||||
* To make it less likely that the dialog is dismissed accidentally, for example if the
|
||||
* device is malfunctioning or if the device is in a pocket, we set
|
||||
* {@code setCanceledOnTouchOutside(false)}.
|
||||
*/
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
Dialog dialog = new AlertDialog.Builder(getActivity())
|
||||
.setTitle(getArguments().getString(ARG_TITLE))
|
||||
.setMessage(getArguments().getString(ARG_MESSAGE))
|
||||
.setPositiveButton(getArguments().getInt(ARG_BUTTON), null)
|
||||
.create();
|
||||
dialog.setCanceledOnTouchOutside(false);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(final DialogInterface dialog) {
|
||||
super.onDismiss(dialog);
|
||||
if (getActivity() != null && getArguments().getBoolean(ARG_DISMISS)) {
|
||||
getActivity().finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityOptions;
|
||||
import android.app.IActivityManager;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.content.pm.UserInfo;
|
||||
import android.content.pm.UserProperties;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserManager;
|
||||
import android.view.View;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowInsetsController;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
|
||||
/** Class containing methods shared between CDCA and CDCBA */
|
||||
public class ConfirmDeviceCredentialUtils {
|
||||
|
||||
public static void checkForPendingIntent(Activity activity) {
|
||||
// See Change-Id I52c203735fa9b53fd2f7df971824747eeb930f36 for context
|
||||
int taskId = activity.getIntent().getIntExtra(Intent.EXTRA_TASK_ID, -1);
|
||||
if (taskId != -1) {
|
||||
try {
|
||||
IActivityManager activityManager = ActivityManager.getService();
|
||||
final ActivityOptions options = ActivityOptions.makeBasic();
|
||||
activityManager.startActivityFromRecents(taskId, options.toBundle());
|
||||
return;
|
||||
} catch (RemoteException e) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
IntentSender intentSender = activity.getIntent().getParcelableExtra(Intent.EXTRA_INTENT);
|
||||
if (intentSender != null) {
|
||||
try {
|
||||
ActivityOptions activityOptions =
|
||||
ActivityOptions.makeBasic()
|
||||
.setPendingIntentBackgroundActivityStartMode(
|
||||
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
|
||||
activity.startIntentSenderForResult(intentSender, -1, null, 0, 0, 0,
|
||||
activityOptions.toBundle());
|
||||
} catch (IntentSender.SendIntentException e) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void reportSuccessfulAttempt(LockPatternUtils utils, UserManager userManager,
|
||||
DevicePolicyManager dpm, int userId, boolean isStrongAuth) {
|
||||
if (isStrongAuth) {
|
||||
utils.reportSuccessfulPasswordAttempt(userId);
|
||||
if (isBiometricUnlockEnabledForPrivateSpace()) {
|
||||
final UserInfo userInfo = userManager.getUserInfo(userId);
|
||||
if (userInfo != null) {
|
||||
if (isProfileThatAlwaysRequiresAuthToDisableQuietMode(userManager, userInfo)
|
||||
|| userInfo.isManagedProfile()) {
|
||||
// Keyguard is responsible to disable StrongAuth for primary user. Disable
|
||||
// StrongAuth for profile challenges only here.
|
||||
utils.userPresent(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dpm.reportSuccessfulBiometricAttempt(userId);
|
||||
}
|
||||
if (!isBiometricUnlockEnabledForPrivateSpace()) {
|
||||
if (userManager.isManagedProfile(userId)) {
|
||||
// Disable StrongAuth for work challenge only here.
|
||||
utils.userPresent(userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the userInfo passed as the parameter corresponds to a profile that always
|
||||
* requires auth to disable quiet mode and false otherwise
|
||||
*/
|
||||
private static boolean isProfileThatAlwaysRequiresAuthToDisableQuietMode(
|
||||
UserManager userManager, @NonNull UserInfo userInfo) {
|
||||
final UserProperties userProperties =
|
||||
userManager.getUserProperties(userInfo.getUserHandle());
|
||||
return userProperties.isAuthAlwaysRequiredToDisableQuietMode() && userInfo.isProfile();
|
||||
}
|
||||
|
||||
private static boolean isBiometricUnlockEnabledForPrivateSpace() {
|
||||
return android.os.Flags.allowPrivateProfile()
|
||||
&& android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace();
|
||||
}
|
||||
|
||||
/**
|
||||
* Request hiding soft-keyboard before animating away credential UI, in case IME
|
||||
* insets animation get delayed by dismissing animation.
|
||||
* @param view used to get root {@link WindowInsets} and {@link WindowInsetsController}.
|
||||
*/
|
||||
public static void hideImeImmediately(@NonNull View view) {
|
||||
if (view.isAttachedToWindow()
|
||||
&& view.getRootWindowInsets().isVisible(WindowInsets.Type.ime())) {
|
||||
view.getWindowInsetsController().hide(WindowInsets.Type.ime());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,731 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PASSWORD_HEADER;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PIN_HEADER;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE;
|
||||
import static android.app.admin.DevicePolicyResources.UNDEFINED;
|
||||
|
||||
import static com.android.settings.biometrics.GatekeeperPasswordProvider.containsGatekeeperPasswordHandle;
|
||||
import static com.android.settings.biometrics.GatekeeperPasswordProvider.getGatekeeperPasswordHandle;
|
||||
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE;
|
||||
|
||||
import android.app.KeyguardManager;
|
||||
import android.app.RemoteLockscreenValidationResult;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Typeface;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.CountDownTimer;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserManager;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.ImeAwareEditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.TextView.OnEditorActionListener;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.android.internal.widget.LockPatternChecker;
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.internal.widget.LockscreenCredential;
|
||||
import com.android.internal.widget.TextViewInputDisabler;
|
||||
import com.android.settings.R;
|
||||
import com.android.settingslib.animation.AppearAnimationUtils;
|
||||
import com.android.settingslib.animation.DisappearAnimationUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class ConfirmLockPassword extends ConfirmDeviceCredentialBaseActivity {
|
||||
|
||||
// The index of the array is isStrongAuth << 1 + isAlpha.
|
||||
private static final int[] DETAIL_TEXTS = new int[] {
|
||||
R.string.lockpassword_confirm_your_pin_generic,
|
||||
R.string.lockpassword_confirm_your_password_generic,
|
||||
R.string.lockpassword_strong_auth_required_device_pin,
|
||||
R.string.lockpassword_strong_auth_required_device_password,
|
||||
};
|
||||
|
||||
public static class InternalActivity extends ConfirmLockPassword {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent getIntent() {
|
||||
Intent modIntent = new Intent(super.getIntent());
|
||||
modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ConfirmLockPasswordFragment.class.getName());
|
||||
return modIntent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isValidFragment(String fragmentName) {
|
||||
if (ConfirmLockPasswordFragment.class.getName().equals(fragmentName)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.main_content);
|
||||
if (fragment != null && fragment instanceof ConfirmLockPasswordFragment) {
|
||||
((ConfirmLockPasswordFragment) fragment).onWindowFocusChanged(hasFocus);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ConfirmLockPasswordFragment extends ConfirmDeviceCredentialBaseFragment
|
||||
implements OnClickListener, OnEditorActionListener,
|
||||
CredentialCheckResultTracker.Listener, SaveAndFinishWorker.Listener,
|
||||
RemoteLockscreenValidationFragment.Listener {
|
||||
private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result";
|
||||
private ImeAwareEditText mPasswordEntry;
|
||||
private TextViewInputDisabler mPasswordEntryInputDisabler;
|
||||
private AsyncTask<?, ?, ?> mPendingLockCheck;
|
||||
private CredentialCheckResultTracker mCredentialCheckResultTracker;
|
||||
private boolean mDisappearing = false;
|
||||
private CountDownTimer mCountdownTimer;
|
||||
private boolean mIsAlpha;
|
||||
private InputMethodManager mImm;
|
||||
private AppearAnimationUtils mAppearAnimationUtils;
|
||||
private DisappearAnimationUtils mDisappearAnimationUtils;
|
||||
private boolean mIsManagedProfile;
|
||||
private CharSequence mCheckBoxLabel;
|
||||
|
||||
// required constructor for fragments
|
||||
public ConfirmLockPasswordFragment() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
final int storedQuality = mLockPatternUtils.getKeyguardStoredPasswordQuality(
|
||||
mEffectiveUserId);
|
||||
|
||||
ConfirmLockPassword activity = (ConfirmLockPassword) getActivity();
|
||||
View view = inflater.inflate(
|
||||
activity.getConfirmCredentialTheme() == ConfirmCredentialTheme.NORMAL
|
||||
? R.layout.confirm_lock_password_normal
|
||||
: R.layout.confirm_lock_password,
|
||||
container,
|
||||
false);
|
||||
mGlifLayout = view.findViewById(R.id.setup_wizard_layout);
|
||||
mPasswordEntry = (ImeAwareEditText) view.findViewById(R.id.password_entry);
|
||||
mPasswordEntry.setOnEditorActionListener(this);
|
||||
// EditText inside ScrollView doesn't automatically get focus.
|
||||
mPasswordEntry.requestFocus();
|
||||
mPasswordEntryInputDisabler = new TextViewInputDisabler(mPasswordEntry);
|
||||
mErrorTextView = (TextView) view.findViewById(R.id.errorText);
|
||||
|
||||
if (mRemoteValidation) {
|
||||
mIsAlpha = mRemoteLockscreenValidationSession.getLockType()
|
||||
== 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
|
||||
|| DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC == storedQuality
|
||||
|| DevicePolicyManager.PASSWORD_QUALITY_COMPLEX == storedQuality
|
||||
|| DevicePolicyManager.PASSWORD_QUALITY_MANAGED == storedQuality;
|
||||
}
|
||||
mImm = (InputMethodManager) getActivity().getSystemService(
|
||||
Context.INPUT_METHOD_SERVICE);
|
||||
|
||||
mIsManagedProfile = UserManager.get(getActivity()).isManagedProfile(mEffectiveUserId);
|
||||
|
||||
Intent intent = getActivity().getIntent();
|
||||
if (intent != null) {
|
||||
CharSequence headerMessage = intent.getCharSequenceExtra(
|
||||
ConfirmDeviceCredentialBaseFragment.HEADER_TEXT);
|
||||
CharSequence detailsMessage = intent.getCharSequenceExtra(
|
||||
ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT);
|
||||
if (TextUtils.isEmpty(headerMessage) && mIsManagedProfile) {
|
||||
headerMessage = mDevicePolicyManager.getOrganizationNameForUser(mUserId);
|
||||
}
|
||||
if (TextUtils.isEmpty(headerMessage)) {
|
||||
headerMessage = getDefaultHeader();
|
||||
}
|
||||
if (TextUtils.isEmpty(detailsMessage)) {
|
||||
detailsMessage = getDefaultDetails();
|
||||
}
|
||||
mGlifLayout.setHeaderText(headerMessage);
|
||||
|
||||
if (mIsManagedProfile) {
|
||||
mGlifLayout.getDescriptionTextView().setVisibility(View.GONE);
|
||||
} else {
|
||||
mGlifLayout.setDescriptionText(detailsMessage);
|
||||
}
|
||||
mCheckBoxLabel = intent.getCharSequenceExtra(KeyguardManager.EXTRA_CHECKBOX_LABEL);
|
||||
}
|
||||
int currentType = mPasswordEntry.getInputType();
|
||||
if (mIsAlpha) {
|
||||
mPasswordEntry.setInputType(currentType);
|
||||
mPasswordEntry.setContentDescription(
|
||||
getContext().getString(R.string.unlock_set_unlock_password_title));
|
||||
} else {
|
||||
mPasswordEntry.setInputType(
|
||||
InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD);
|
||||
mPasswordEntry.setContentDescription(
|
||||
getContext().getString(R.string.unlock_set_unlock_pin_title));
|
||||
}
|
||||
// Can't set via XML since setInputType resets the fontFamily to null
|
||||
mPasswordEntry.setTypeface(Typeface.create(
|
||||
getContext().getString(com.android.internal.R.string.config_headlineFontFamily),
|
||||
Typeface.NORMAL));
|
||||
mAppearAnimationUtils = new AppearAnimationUtils(getContext(),
|
||||
220, 2f /* translationScale */, 1f /* delayScale*/,
|
||||
AnimationUtils.loadInterpolator(getContext(),
|
||||
android.R.interpolator.linear_out_slow_in));
|
||||
mDisappearAnimationUtils = new DisappearAnimationUtils(getContext(),
|
||||
110, 1f /* translationScale */,
|
||||
0.5f /* delayScale */, AnimationUtils.loadInterpolator(
|
||||
getContext(), android.R.interpolator.fast_out_linear_in));
|
||||
setAccessibilityTitle(mGlifLayout.getHeaderText());
|
||||
|
||||
mCredentialCheckResultTracker = (CredentialCheckResultTracker) getFragmentManager()
|
||||
.findFragmentByTag(FRAGMENT_TAG_CHECK_LOCK_RESULT);
|
||||
if (mCredentialCheckResultTracker == null) {
|
||||
mCredentialCheckResultTracker = new CredentialCheckResultTracker();
|
||||
getFragmentManager().beginTransaction().add(mCredentialCheckResultTracker,
|
||||
FRAGMENT_TAG_CHECK_LOCK_RESULT).commit();
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, @Nullable Bundle 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);
|
||||
}
|
||||
updateRemoteLockscreenValidationViews();
|
||||
}
|
||||
|
||||
if (mForgotButton != null) {
|
||||
mForgotButton.setText(mIsAlpha
|
||||
? R.string.lockpassword_forgot_password
|
||||
: R.string.lockpassword_forgot_pin);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (mPasswordEntry != null) {
|
||||
mPasswordEntry.setText(null);
|
||||
}
|
||||
// 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
|
||||
// the instance to become gc-able.
|
||||
new Handler(Looper.myLooper()).postDelayed(() -> {
|
||||
System.gc();
|
||||
System.runFinalization();
|
||||
System.gc();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
private String getDefaultHeader() {
|
||||
if (mFrp) {
|
||||
return mIsAlpha ? getString(R.string.lockpassword_confirm_your_password_header_frp)
|
||||
: getString(R.string.lockpassword_confirm_your_pin_header_frp);
|
||||
}
|
||||
if (mRepairMode) {
|
||||
return mIsAlpha
|
||||
? getString(R.string.lockpassword_confirm_repair_mode_password_header)
|
||||
: getString(R.string.lockpassword_confirm_repair_mode_pin_header);
|
||||
}
|
||||
if (mRemoteValidation) {
|
||||
return getString(R.string.lockpassword_remote_validation_header);
|
||||
}
|
||||
if (mIsManagedProfile) {
|
||||
if (mIsAlpha) {
|
||||
return mDevicePolicyManager.getResources().getString(
|
||||
CONFIRM_WORK_PROFILE_PASSWORD_HEADER,
|
||||
() -> getString(
|
||||
R.string.lockpassword_confirm_your_work_password_header));
|
||||
}
|
||||
return mDevicePolicyManager.getResources().getString(
|
||||
CONFIRM_WORK_PROFILE_PIN_HEADER,
|
||||
() -> getString(R.string.lockpassword_confirm_your_work_pin_header));
|
||||
}
|
||||
return mIsAlpha ? getString(R.string.lockpassword_confirm_your_password_header)
|
||||
: getString(R.string.lockpassword_confirm_your_pin_header);
|
||||
}
|
||||
|
||||
private String getDefaultDetails() {
|
||||
if (mFrp) {
|
||||
return mIsAlpha ? getString(R.string.lockpassword_confirm_your_password_details_frp)
|
||||
: getString(R.string.lockpassword_confirm_your_pin_details_frp);
|
||||
}
|
||||
if (mRepairMode) {
|
||||
return mIsAlpha
|
||||
? getString(R.string.lockpassword_confirm_repair_mode_password_details)
|
||||
: getString(R.string.lockpassword_confirm_repair_mode_pin_details);
|
||||
}
|
||||
if (mRemoteValidation) {
|
||||
return getContext().getString(mIsAlpha
|
||||
? R.string.lockpassword_remote_validation_password_details
|
||||
: R.string.lockpassword_remote_validation_pin_details);
|
||||
}
|
||||
boolean isStrongAuthRequired = isStrongAuthRequired();
|
||||
// Map boolean flags to an index by isStrongAuth << 1 + isAlpha.
|
||||
int index = ((isStrongAuthRequired ? 1 : 0) << 1) + (mIsAlpha ? 1 : 0);
|
||||
return 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() {
|
||||
return mIsAlpha ? R.string.lockpassword_invalid_password
|
||||
: R.string.lockpassword_invalid_pin;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLastTryOverrideErrorMessageId(int userType) {
|
||||
if (userType == USER_TYPE_MANAGED_PROFILE) {
|
||||
return mIsAlpha ? WORK_PROFILE_LAST_PASSWORD_ATTEMPT_BEFORE_WIPE
|
||||
: WORK_PROFILE_LAST_PIN_ATTEMPT_BEFORE_WIPE;
|
||||
}
|
||||
|
||||
return UNDEFINED;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLastTryDefaultErrorMessage(int userType) {
|
||||
switch (userType) {
|
||||
case USER_TYPE_PRIMARY:
|
||||
return mIsAlpha ? R.string.lock_last_password_attempt_before_wipe_device
|
||||
: R.string.lock_last_pin_attempt_before_wipe_device;
|
||||
case USER_TYPE_MANAGED_PROFILE:
|
||||
return mIsAlpha ? R.string.lock_last_password_attempt_before_wipe_profile
|
||||
: R.string.lock_last_pin_attempt_before_wipe_profile;
|
||||
case USER_TYPE_SECONDARY:
|
||||
return mIsAlpha ? R.string.lock_last_password_attempt_before_wipe_user
|
||||
: R.string.lock_last_pin_attempt_before_wipe_user;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized user type:" + userType);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareEnterAnimation() {
|
||||
super.prepareEnterAnimation();
|
||||
mGlifLayout.getHeaderTextView().setAlpha(0f);
|
||||
mGlifLayout.getDescriptionTextView().setAlpha(0f);
|
||||
mCancelButton.setAlpha(0f);
|
||||
if (mForgotButton != null) {
|
||||
mForgotButton.setAlpha(0f);
|
||||
}
|
||||
mPasswordEntry.setAlpha(0f);
|
||||
mErrorTextView.setAlpha(0f);
|
||||
}
|
||||
|
||||
private View[] getActiveViews() {
|
||||
ArrayList<View> result = new ArrayList<>();
|
||||
result.add(mGlifLayout.getHeaderTextView());
|
||||
result.add(mGlifLayout.getDescriptionTextView());
|
||||
if (mCancelButton.getVisibility() == View.VISIBLE) {
|
||||
result.add(mCancelButton);
|
||||
}
|
||||
if (mForgotButton != null) {
|
||||
result.add(mForgotButton);
|
||||
}
|
||||
result.add(mPasswordEntry);
|
||||
result.add(mErrorTextView);
|
||||
return result.toArray(new View[] {});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startEnterAnimation() {
|
||||
super.startEnterAnimation();
|
||||
mAppearAnimationUtils.startAnimation(getActiveViews(), this::updatePasswordEntry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (mCountdownTimer != null) {
|
||||
mCountdownTimer.cancel();
|
||||
mCountdownTimer = null;
|
||||
}
|
||||
mCredentialCheckResultTracker.setListener(null);
|
||||
if (mRemoteLockscreenValidationFragment != null) {
|
||||
mRemoteLockscreenValidationFragment.setListener(null, /* handler= */ null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.CONFIRM_LOCK_PASSWORD;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
long deadline = mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId);
|
||||
if (deadline != 0) {
|
||||
mCredentialCheckResultTracker.clearResult();
|
||||
handleAttemptLockout(deadline);
|
||||
} else {
|
||||
updatePasswordEntry();
|
||||
mErrorTextView.setText("");
|
||||
updateErrorMessage(
|
||||
mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId));
|
||||
}
|
||||
mCredentialCheckResultTracker.setListener(this);
|
||||
if (mRemoteLockscreenValidationFragment != null) {
|
||||
mRemoteLockscreenValidationFragment.setListener(this, mHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void authenticationSucceeded() {
|
||||
mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId);
|
||||
}
|
||||
|
||||
private void updatePasswordEntry() {
|
||||
final boolean isLockedOut =
|
||||
mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId) != 0;
|
||||
final boolean isRemoteLockscreenValidationInProgress =
|
||||
mRemoteLockscreenValidationFragment != null
|
||||
&& mRemoteLockscreenValidationFragment.isRemoteValidationInProgress();
|
||||
boolean shouldEnableInput = !isLockedOut && !isRemoteLockscreenValidationInProgress;
|
||||
mPasswordEntry.setEnabled(shouldEnableInput);
|
||||
mPasswordEntryInputDisabler.setInputEnabled(shouldEnableInput);
|
||||
if (shouldEnableInput) {
|
||||
mPasswordEntry.scheduleShowSoftInput();
|
||||
mPasswordEntry.requestFocus();
|
||||
} else {
|
||||
mImm.hideSoftInputFromWindow(mPasswordEntry.getWindowToken(), /* flags= */0);
|
||||
}
|
||||
}
|
||||
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
if (!hasFocus) {
|
||||
return;
|
||||
}
|
||||
// Post to let window focus logic to finish to allow soft input show/hide properly.
|
||||
mPasswordEntry.post(this::updatePasswordEntry);
|
||||
}
|
||||
|
||||
private void handleNext() {
|
||||
if (mPendingLockCheck != null || mDisappearing) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(b/120484642): This is a point of entry for passwords from the UI
|
||||
final Editable passwordText = mPasswordEntry.getText();
|
||||
if (TextUtils.isEmpty(passwordText)) {
|
||||
return;
|
||||
}
|
||||
final LockscreenCredential credential = mIsAlpha
|
||||
? LockscreenCredential.createPassword(passwordText)
|
||||
: LockscreenCredential.createPin(passwordText);
|
||||
|
||||
mPasswordEntryInputDisabler.setInputEnabled(false);
|
||||
|
||||
if (mRemoteValidation) {
|
||||
validateGuess(credential);
|
||||
updateRemoteLockscreenValidationViews();
|
||||
updatePasswordEntry();
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = new Intent();
|
||||
// TODO(b/161956762): Sanitize this
|
||||
if (mReturnGatekeeperPassword) {
|
||||
if (isInternalActivity()) {
|
||||
startVerifyPassword(credential, intent,
|
||||
LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE);
|
||||
return;
|
||||
}
|
||||
} else if (mForceVerifyPath) {
|
||||
if (isInternalActivity()) {
|
||||
final int flags = mRequestWriteRepairModePassword
|
||||
? LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW : 0;
|
||||
startVerifyPassword(credential, intent, flags);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
startCheckPassword(credential, intent);
|
||||
return;
|
||||
}
|
||||
|
||||
mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId);
|
||||
}
|
||||
|
||||
private boolean isInternalActivity() {
|
||||
return getActivity() instanceof ConfirmLockPassword.InternalActivity;
|
||||
}
|
||||
|
||||
private void startVerifyPassword(LockscreenCredential credential, final Intent intent,
|
||||
@LockPatternUtils.VerifyFlag int flags) {
|
||||
final int localEffectiveUserId = mEffectiveUserId;
|
||||
final int localUserId = mUserId;
|
||||
final LockPatternChecker.OnVerifyCallback onVerifyCallback = (response, timeoutMs) -> {
|
||||
mPendingLockCheck = null;
|
||||
final boolean matched = response.isMatched();
|
||||
if (matched && mReturnCredentials) {
|
||||
if ((flags & LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE) != 0) {
|
||||
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE,
|
||||
response.getGatekeeperPasswordHandle());
|
||||
} else {
|
||||
intent.putExtra(
|
||||
ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
|
||||
response.getGatekeeperHAT());
|
||||
}
|
||||
}
|
||||
mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
|
||||
localEffectiveUserId);
|
||||
};
|
||||
mPendingLockCheck = (localEffectiveUserId == localUserId)
|
||||
? LockPatternChecker.verifyCredential(mLockPatternUtils, credential,
|
||||
localUserId, flags, onVerifyCallback)
|
||||
: LockPatternChecker.verifyTiedProfileChallenge(mLockPatternUtils, credential,
|
||||
localUserId, flags, onVerifyCallback);
|
||||
}
|
||||
|
||||
private void startCheckPassword(final LockscreenCredential credential,
|
||||
final Intent intent) {
|
||||
final int localEffectiveUserId = mEffectiveUserId;
|
||||
mPendingLockCheck = LockPatternChecker.checkCredential(
|
||||
mLockPatternUtils,
|
||||
credential,
|
||||
localEffectiveUserId,
|
||||
new LockPatternChecker.OnCheckCallback() {
|
||||
@Override
|
||||
public void onChecked(boolean matched, int timeoutMs) {
|
||||
mPendingLockCheck = null;
|
||||
if (matched && isInternalActivity() && mReturnCredentials) {
|
||||
intent.putExtra(
|
||||
ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, credential);
|
||||
}
|
||||
mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
|
||||
localEffectiveUserId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void startDisappearAnimation(final Intent intent) {
|
||||
ConfirmDeviceCredentialUtils.hideImeImmediately(
|
||||
getActivity().getWindow().getDecorView());
|
||||
|
||||
if (mDisappearing) {
|
||||
return;
|
||||
}
|
||||
mDisappearing = true;
|
||||
|
||||
final ConfirmLockPassword activity = (ConfirmLockPassword) getActivity();
|
||||
// Bail if there is no active activity.
|
||||
if (activity == null || activity.isFinishing()) {
|
||||
return;
|
||||
}
|
||||
if (activity.getConfirmCredentialTheme() == ConfirmCredentialTheme.DARK) {
|
||||
mDisappearAnimationUtils.startAnimation(getActiveViews(), () -> {
|
||||
activity.setResult(RESULT_OK, intent);
|
||||
activity.finish();
|
||||
activity.overridePendingTransition(
|
||||
R.anim.confirm_credential_close_enter,
|
||||
R.anim.confirm_credential_close_exit);
|
||||
});
|
||||
} else {
|
||||
activity.setResult(RESULT_OK, intent);
|
||||
activity.finish();
|
||||
}
|
||||
}
|
||||
|
||||
private void onPasswordChecked(boolean matched, Intent intent, int timeoutMs,
|
||||
int effectiveUserId, boolean newResult) {
|
||||
mPasswordEntryInputDisabler.setInputEnabled(true);
|
||||
if (matched) {
|
||||
if (newResult) {
|
||||
ConfirmDeviceCredentialUtils.reportSuccessfulAttempt(mLockPatternUtils,
|
||||
mUserManager, mDevicePolicyManager, mEffectiveUserId,
|
||||
/* isStrongAuth */ true);
|
||||
}
|
||||
startDisappearAnimation(intent);
|
||||
ConfirmDeviceCredentialUtils.checkForPendingIntent(getActivity());
|
||||
} else {
|
||||
if (timeoutMs > 0) {
|
||||
refreshLockScreen();
|
||||
long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
|
||||
effectiveUserId, timeoutMs);
|
||||
handleAttemptLockout(deadline);
|
||||
} else {
|
||||
showError(getErrorMessage(), CLEAR_WRONG_ATTEMPT_TIMEOUT_MS);
|
||||
}
|
||||
if (newResult) {
|
||||
reportFailedAttempt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoteLockscreenValidationResult(
|
||||
RemoteLockscreenValidationResult result) {
|
||||
switch (result.getResultCode()) {
|
||||
case RemoteLockscreenValidationResult.RESULT_GUESS_VALID:
|
||||
if (mCheckBox.isChecked() && mRemoteLockscreenValidationFragment
|
||||
.getLockscreenCredential() != null) {
|
||||
Log.i(TAG, "Setting device screen lock to the other device's screen lock.");
|
||||
SaveAndFinishWorker saveAndFinishWorker = new SaveAndFinishWorker();
|
||||
getFragmentManager().beginTransaction().add(saveAndFinishWorker, null)
|
||||
.commit();
|
||||
getFragmentManager().executePendingTransactions();
|
||||
saveAndFinishWorker
|
||||
.setListener(this)
|
||||
.setRequestGatekeeperPasswordHandle(true);
|
||||
saveAndFinishWorker.start(
|
||||
mLockPatternUtils,
|
||||
mRemoteLockscreenValidationFragment.getLockscreenCredential(),
|
||||
/* currentCredential= */ null,
|
||||
mEffectiveUserId);
|
||||
} else {
|
||||
mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(),
|
||||
/* timeoutMs= */ 0, mEffectiveUserId);
|
||||
}
|
||||
return;
|
||||
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:
|
||||
case RemoteLockscreenValidationResult.RESULT_SESSION_EXPIRED:
|
||||
onRemoteLockscreenValidationFailure(String.format(
|
||||
"Cannot continue remote lockscreen validation. ResultCode=%d",
|
||||
result.getResultCode()));
|
||||
break;
|
||||
}
|
||||
updateRemoteLockscreenValidationViews();
|
||||
updatePasswordEntry();
|
||||
mRemoteLockscreenValidationFragment.clearLockscreenCredential();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs,
|
||||
int effectiveUserId, boolean newResult) {
|
||||
onPasswordChecked(matched, intent, timeoutMs, effectiveUserId, newResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onShowError() {
|
||||
mPasswordEntry.setText(null);
|
||||
}
|
||||
|
||||
private void handleAttemptLockout(long elapsedRealtimeDeadline) {
|
||||
clearResetErrorRunnable();
|
||||
mCountdownTimer = new CountDownTimer(
|
||||
elapsedRealtimeDeadline - SystemClock.elapsedRealtime(),
|
||||
LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
|
||||
|
||||
@Override
|
||||
public void onTick(long millisUntilFinished) {
|
||||
final int secondsCountdown = (int) (millisUntilFinished / 1000);
|
||||
showError(getString(
|
||||
R.string.lockpattern_too_many_failed_confirmation_attempts,
|
||||
secondsCountdown), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
updatePasswordEntry();
|
||||
mErrorTextView.setText("");
|
||||
updateErrorMessage(
|
||||
mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId));
|
||||
}
|
||||
}.start();
|
||||
updatePasswordEntry();
|
||||
}
|
||||
|
||||
public void onClick(View v) {
|
||||
if (v.getId() == R.id.next_button) {
|
||||
handleNext();
|
||||
} else if (v.getId() == R.id.cancel_button) {
|
||||
getActivity().setResult(RESULT_CANCELED);
|
||||
getActivity().finish();
|
||||
}
|
||||
}
|
||||
|
||||
// {@link OnEditorActionListener} methods.
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
// Check if this was the result of hitting the enter or "done" key
|
||||
if (actionId == EditorInfo.IME_NULL
|
||||
|| actionId == EditorInfo.IME_ACTION_DONE
|
||||
|| actionId == EditorInfo.IME_ACTION_NEXT) {
|
||||
handleNext();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for when the current device's lockscreen was set to the guess used for
|
||||
* remote lockscreen validation.
|
||||
*/
|
||||
@Override
|
||||
public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
|
||||
Log.i(TAG, "Device lockscreen has been set to remote device's lockscreen.");
|
||||
mRemoteLockscreenValidationFragment.clearLockscreenCredential();
|
||||
|
||||
Intent result = new Intent();
|
||||
if (mRemoteValidation && containsGatekeeperPasswordHandle(resultData)) {
|
||||
result.putExtra(EXTRA_KEY_GK_PW_HANDLE, getGatekeeperPasswordHandle(resultData));
|
||||
}
|
||||
mCredentialCheckResultTracker.setResult(/* matched= */ true, result,
|
||||
/* timeoutMs= */ 0, mEffectiveUserId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,759 @@
|
||||
/*
|
||||
* Copyright (C) 2008 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.DevicePolicyResources.Strings.Settings.CONFIRM_WORK_PROFILE_PATTERN_HEADER;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE;
|
||||
import static android.app.admin.DevicePolicyResources.UNDEFINED;
|
||||
|
||||
import static com.android.settings.biometrics.GatekeeperPasswordProvider.containsGatekeeperPasswordHandle;
|
||||
import static com.android.settings.biometrics.GatekeeperPasswordProvider.getGatekeeperPasswordHandle;
|
||||
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.KeyguardManager;
|
||||
import android.app.RemoteLockscreenValidationResult;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.CountDownTimer;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.widget.LinearLayoutWithDefaultTouchRecepient;
|
||||
import com.android.internal.widget.LockPatternChecker;
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.internal.widget.LockPatternView;
|
||||
import com.android.internal.widget.LockPatternView.Cell;
|
||||
import com.android.internal.widget.LockscreenCredential;
|
||||
import com.android.settings.R;
|
||||
import com.android.settingslib.animation.AppearAnimationCreator;
|
||||
import com.android.settingslib.animation.AppearAnimationUtils;
|
||||
import com.android.settingslib.animation.DisappearAnimationUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Launch this when you want the user to confirm their lock pattern.
|
||||
*
|
||||
* Sets an activity result of {@link Activity#RESULT_OK} when the user
|
||||
* successfully confirmed their pattern.
|
||||
*/
|
||||
public class ConfirmLockPattern extends ConfirmDeviceCredentialBaseActivity {
|
||||
|
||||
public static class InternalActivity extends ConfirmLockPattern {
|
||||
}
|
||||
|
||||
private enum Stage {
|
||||
NeedToUnlock,
|
||||
NeedToUnlockWrong,
|
||||
LockedOut
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent getIntent() {
|
||||
Intent modIntent = new Intent(super.getIntent());
|
||||
modIntent.putExtra(EXTRA_SHOW_FRAGMENT, ConfirmLockPatternFragment.class.getName());
|
||||
return modIntent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isValidFragment(String fragmentName) {
|
||||
if (ConfirmLockPatternFragment.class.getName().equals(fragmentName)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static class ConfirmLockPatternFragment extends ConfirmDeviceCredentialBaseFragment
|
||||
implements AppearAnimationCreator<Object>, CredentialCheckResultTracker.Listener,
|
||||
SaveAndFinishWorker.Listener, RemoteLockscreenValidationFragment.Listener {
|
||||
|
||||
private static final String FRAGMENT_TAG_CHECK_LOCK_RESULT = "check_lock_result";
|
||||
|
||||
private LockPatternView mLockPatternView;
|
||||
private AsyncTask<?, ?, ?> mPendingLockCheck;
|
||||
private CredentialCheckResultTracker mCredentialCheckResultTracker;
|
||||
private boolean mDisappearing = false;
|
||||
private CountDownTimer mCountdownTimer;
|
||||
|
||||
private View mSudContent;
|
||||
|
||||
// caller-supplied text for various prompts
|
||||
private CharSequence mHeaderText;
|
||||
private CharSequence mDetailsText;
|
||||
private CharSequence mCheckBoxLabel;
|
||||
|
||||
private AppearAnimationUtils mAppearAnimationUtils;
|
||||
private DisappearAnimationUtils mDisappearAnimationUtils;
|
||||
|
||||
private boolean mIsManagedProfile;
|
||||
|
||||
// required constructor for fragments
|
||||
public ConfirmLockPatternFragment() {
|
||||
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
ConfirmLockPattern activity = (ConfirmLockPattern) getActivity();
|
||||
View view = inflater.inflate(
|
||||
activity.getConfirmCredentialTheme() == ConfirmCredentialTheme.NORMAL
|
||||
? R.layout.confirm_lock_pattern_normal
|
||||
: R.layout.confirm_lock_pattern,
|
||||
container,
|
||||
false);
|
||||
mGlifLayout = view.findViewById(R.id.setup_wizard_layout);
|
||||
mLockPatternView = (LockPatternView) view.findViewById(R.id.lockPattern);
|
||||
mErrorTextView = (TextView) view.findViewById(R.id.errorText);
|
||||
// TODO(b/243008023) Workaround for Glif layout on 2 panel choose lock settings.
|
||||
mSudContent = mGlifLayout.findViewById(
|
||||
com.google.android.setupdesign.R.id.sud_layout_content);
|
||||
mSudContent.setPadding(mSudContent.getPaddingLeft(), 0, mSudContent.getPaddingRight(),
|
||||
0);
|
||||
mIsManagedProfile = UserManager.get(getActivity()).isManagedProfile(mEffectiveUserId);
|
||||
|
||||
// make it so unhandled touch events within the unlock screen go to the
|
||||
// lock pattern view.
|
||||
final LinearLayoutWithDefaultTouchRecepient topLayout
|
||||
= (LinearLayoutWithDefaultTouchRecepient) view.findViewById(R.id.topLayout);
|
||||
topLayout.setDefaultTouchRecepient(mLockPatternView);
|
||||
|
||||
Intent intent = getActivity().getIntent();
|
||||
if (intent != null) {
|
||||
mHeaderText = intent.getCharSequenceExtra(
|
||||
ConfirmDeviceCredentialBaseFragment.HEADER_TEXT);
|
||||
mDetailsText = intent.getCharSequenceExtra(
|
||||
ConfirmDeviceCredentialBaseFragment.DETAILS_TEXT);
|
||||
mCheckBoxLabel = intent.getCharSequenceExtra(KeyguardManager.EXTRA_CHECKBOX_LABEL);
|
||||
}
|
||||
if (TextUtils.isEmpty(mHeaderText) && mIsManagedProfile) {
|
||||
mHeaderText = mDevicePolicyManager.getOrganizationNameForUser(mUserId);
|
||||
}
|
||||
|
||||
mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
|
||||
mEffectiveUserId));
|
||||
mLockPatternView.setOnPatternListener(mConfirmExistingLockPatternListener);
|
||||
mLockPatternView.setOnTouchListener((v, event) -> {
|
||||
if (event.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
v.getParent().requestDisallowInterceptTouchEvent(true);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
updateStage(Stage.NeedToUnlock);
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
// on first launch, if no lock pattern is set, then finish with
|
||||
// success (don't want user to get stuck confirming something that
|
||||
// doesn't exist).
|
||||
// Don't do this check for FRP though, because the pattern is not stored
|
||||
// in a way that isLockPatternEnabled is aware of for that case.
|
||||
// TODO(roosa): This block should no longer be needed since we removed the
|
||||
// ability to disable the pattern in L. Remove this block after
|
||||
// ensuring it's safe to do so. (Note that ConfirmLockPassword
|
||||
// doesn't have this).
|
||||
if (!mFrp && !mRemoteValidation && !mRepairMode
|
||||
&& !mLockPatternUtils.isLockPatternEnabled(mEffectiveUserId)) {
|
||||
getActivity().setResult(Activity.RESULT_OK);
|
||||
getActivity().finish();
|
||||
}
|
||||
}
|
||||
mAppearAnimationUtils = new AppearAnimationUtils(getContext(),
|
||||
AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 2f /* translationScale */,
|
||||
1.3f /* delayScale */, AnimationUtils.loadInterpolator(
|
||||
getContext(), android.R.interpolator.linear_out_slow_in));
|
||||
mDisappearAnimationUtils = new DisappearAnimationUtils(getContext(),
|
||||
125, 4f /* translationScale */,
|
||||
0.3f /* delayScale */, AnimationUtils.loadInterpolator(
|
||||
getContext(), android.R.interpolator.fast_out_linear_in),
|
||||
new AppearAnimationUtils.RowTranslationScaler() {
|
||||
@Override
|
||||
public float getRowTranslationScale(int row, int numRows) {
|
||||
return (float)(numRows - row) / numRows;
|
||||
}
|
||||
});
|
||||
setAccessibilityTitle(mGlifLayout.getHeaderText());
|
||||
|
||||
mCredentialCheckResultTracker = (CredentialCheckResultTracker) getFragmentManager()
|
||||
.findFragmentByTag(FRAGMENT_TAG_CHECK_LOCK_RESULT);
|
||||
if (mCredentialCheckResultTracker == null) {
|
||||
mCredentialCheckResultTracker = new CredentialCheckResultTracker();
|
||||
getFragmentManager().beginTransaction().add(mCredentialCheckResultTracker,
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, @Nullable Bundle 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);
|
||||
}
|
||||
updateRemoteLockscreenValidationViews();
|
||||
}
|
||||
|
||||
if (mForgotButton != null) {
|
||||
mForgotButton.setText(R.string.lockpassword_forgot_pattern);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
// deliberately not calling super since we are managing this in full
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
|
||||
if (mCountdownTimer != null) {
|
||||
mCountdownTimer.cancel();
|
||||
}
|
||||
mCredentialCheckResultTracker.setListener(null);
|
||||
if (mRemoteLockscreenValidationFragment != null) {
|
||||
mRemoteLockscreenValidationFragment.setListener(null, /* handler= */ null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.CONFIRM_LOCK_PATTERN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
// if the user is currently locked out, enforce it.
|
||||
long deadline = mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId);
|
||||
if (deadline != 0) {
|
||||
mCredentialCheckResultTracker.clearResult();
|
||||
handleAttemptLockout(deadline);
|
||||
} else if (!mLockPatternView.isEnabled()) {
|
||||
// The deadline has passed, but the timer was cancelled. Or the pending lock
|
||||
// check was cancelled. Need to clean up.
|
||||
updateStage(Stage.NeedToUnlock);
|
||||
}
|
||||
mCredentialCheckResultTracker.setListener(this);
|
||||
if (mRemoteLockscreenValidationFragment != null) {
|
||||
mRemoteLockscreenValidationFragment.setListener(this, mHandler);
|
||||
if (mRemoteLockscreenValidationFragment.isRemoteValidationInProgress()) {
|
||||
mLockPatternView.setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onShowError() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void prepareEnterAnimation() {
|
||||
super.prepareEnterAnimation();
|
||||
mGlifLayout.getHeaderTextView().setAlpha(0f);
|
||||
mCancelButton.setAlpha(0f);
|
||||
if (mForgotButton != null) {
|
||||
mForgotButton.setAlpha(0f);
|
||||
}
|
||||
mLockPatternView.setAlpha(0f);
|
||||
mGlifLayout.getDescriptionTextView().setAlpha(0f);
|
||||
}
|
||||
|
||||
private String getDefaultDetails() {
|
||||
if (mFrp) {
|
||||
return getString(R.string.lockpassword_confirm_your_pattern_details_frp);
|
||||
}
|
||||
if (mRepairMode) {
|
||||
return getString(R.string.lockpassword_confirm_repair_mode_pattern_details);
|
||||
}
|
||||
if (mRemoteValidation) {
|
||||
return getString(
|
||||
R.string.lockpassword_remote_validation_pattern_details);
|
||||
}
|
||||
final boolean isStrongAuthRequired = isStrongAuthRequired();
|
||||
return isStrongAuthRequired
|
||||
? getString(R.string.lockpassword_strong_auth_required_device_pattern)
|
||||
: getString(R.string.lockpassword_confirm_your_pattern_generic);
|
||||
}
|
||||
|
||||
private Object[][] getActiveViews() {
|
||||
ArrayList<ArrayList<Object>> result = new ArrayList<>();
|
||||
result.add(new ArrayList<>(Collections.singletonList(mGlifLayout.getHeaderTextView())));
|
||||
result.add(new ArrayList<>(
|
||||
Collections.singletonList(mGlifLayout.getDescriptionTextView())));
|
||||
if (mCancelButton.getVisibility() == View.VISIBLE) {
|
||||
result.add(new ArrayList<>(Collections.singletonList(mCancelButton)));
|
||||
}
|
||||
if (mForgotButton != null) {
|
||||
result.add(new ArrayList<>(Collections.singletonList(mForgotButton)));
|
||||
}
|
||||
LockPatternView.CellState[][] cellStates = mLockPatternView.getCellStates();
|
||||
for (int i = 0; i < cellStates.length; i++) {
|
||||
ArrayList<Object> row = new ArrayList<>();
|
||||
for (int j = 0; j < cellStates[i].length; j++) {
|
||||
row.add(cellStates[i][j]);
|
||||
}
|
||||
result.add(row);
|
||||
}
|
||||
Object[][] resultArr = new Object[result.size()][cellStates[0].length];
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
ArrayList<Object> row = result.get(i);
|
||||
for (int j = 0; j < row.size(); j++) {
|
||||
resultArr[i][j] = row.get(j);
|
||||
}
|
||||
}
|
||||
return resultArr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startEnterAnimation() {
|
||||
super.startEnterAnimation();
|
||||
mLockPatternView.setAlpha(1f);
|
||||
mAppearAnimationUtils.startAnimation2d(getActiveViews(), null, this);
|
||||
}
|
||||
|
||||
private void updateStage(Stage stage) {
|
||||
switch (stage) {
|
||||
case NeedToUnlock:
|
||||
if (mHeaderText != null) {
|
||||
mGlifLayout.setHeaderText(mHeaderText);
|
||||
} else {
|
||||
mGlifLayout.setHeaderText(getDefaultHeader());
|
||||
}
|
||||
|
||||
CharSequence detailsText =
|
||||
mDetailsText == null ? getDefaultDetails() : mDetailsText;
|
||||
|
||||
if (mIsManagedProfile) {
|
||||
mGlifLayout.getDescriptionTextView().setVisibility(View.GONE);
|
||||
} else {
|
||||
mGlifLayout.setDescriptionText(detailsText);
|
||||
}
|
||||
|
||||
mErrorTextView.setText("");
|
||||
updateErrorMessage(
|
||||
mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId));
|
||||
|
||||
mLockPatternView.setEnabled(true);
|
||||
mLockPatternView.enableInput();
|
||||
mLockPatternView.clearPattern();
|
||||
break;
|
||||
case NeedToUnlockWrong:
|
||||
showError(R.string.lockpattern_need_to_unlock_wrong,
|
||||
CLEAR_WRONG_ATTEMPT_TIMEOUT_MS);
|
||||
|
||||
mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
|
||||
mLockPatternView.setEnabled(true);
|
||||
mLockPatternView.enableInput();
|
||||
break;
|
||||
case LockedOut:
|
||||
mLockPatternView.clearPattern();
|
||||
// enabled = false means: disable input, and have the
|
||||
// appearance of being disabled.
|
||||
mLockPatternView.setEnabled(false); // appearance of being disabled
|
||||
break;
|
||||
}
|
||||
|
||||
// Always announce the header for accessibility. This is a no-op
|
||||
// when accessibility is disabled.
|
||||
mGlifLayout.getHeaderTextView().announceForAccessibility(mGlifLayout.getHeaderText());
|
||||
}
|
||||
|
||||
private String getDefaultHeader() {
|
||||
if (mFrp) {
|
||||
return getString(R.string.lockpassword_confirm_your_pattern_header_frp);
|
||||
}
|
||||
if (mRepairMode) {
|
||||
return getString(R.string.lockpassword_confirm_repair_mode_pattern_header);
|
||||
}
|
||||
if (mRemoteValidation) {
|
||||
return getString(R.string.lockpassword_remote_validation_header);
|
||||
}
|
||||
if (mIsManagedProfile) {
|
||||
return mDevicePolicyManager.getResources().getString(
|
||||
CONFIRM_WORK_PROFILE_PATTERN_HEADER,
|
||||
() -> getString(R.string.lockpassword_confirm_your_work_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() {
|
||||
public void run() {
|
||||
mLockPatternView.clearPattern();
|
||||
}
|
||||
};
|
||||
|
||||
// clear the wrong pattern unless they have started a new one
|
||||
// already
|
||||
private void postClearPatternRunnable() {
|
||||
mLockPatternView.removeCallbacks(mClearPatternRunnable);
|
||||
mLockPatternView.postDelayed(mClearPatternRunnable, CLEAR_WRONG_ATTEMPT_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void authenticationSucceeded() {
|
||||
mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId);
|
||||
}
|
||||
|
||||
private void startDisappearAnimation(final Intent intent) {
|
||||
if (mDisappearing) {
|
||||
return;
|
||||
}
|
||||
mDisappearing = true;
|
||||
|
||||
final ConfirmLockPattern activity = (ConfirmLockPattern) getActivity();
|
||||
// Bail if there is no active activity.
|
||||
if (activity == null || activity.isFinishing()) {
|
||||
return;
|
||||
}
|
||||
if (activity.getConfirmCredentialTheme() == ConfirmCredentialTheme.DARK) {
|
||||
mLockPatternView.clearPattern();
|
||||
mDisappearAnimationUtils.startAnimation2d(getActiveViews(),
|
||||
() -> {
|
||||
activity.setResult(RESULT_OK, intent);
|
||||
activity.finish();
|
||||
activity.overridePendingTransition(
|
||||
R.anim.confirm_credential_close_enter,
|
||||
R.anim.confirm_credential_close_exit);
|
||||
}, this);
|
||||
} else {
|
||||
activity.setResult(RESULT_OK, intent);
|
||||
activity.finish();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The pattern listener that responds according to a user confirming
|
||||
* an existing lock pattern.
|
||||
*/
|
||||
private LockPatternView.OnPatternListener mConfirmExistingLockPatternListener
|
||||
= new LockPatternView.OnPatternListener() {
|
||||
|
||||
public void onPatternStart() {
|
||||
mLockPatternView.removeCallbacks(mClearPatternRunnable);
|
||||
}
|
||||
|
||||
public void onPatternCleared() {
|
||||
mLockPatternView.removeCallbacks(mClearPatternRunnable);
|
||||
}
|
||||
|
||||
public void onPatternCellAdded(List<Cell> pattern) {
|
||||
|
||||
}
|
||||
|
||||
public void onPatternDetected(List<LockPatternView.Cell> pattern) {
|
||||
if (mPendingLockCheck != null || mDisappearing) {
|
||||
return;
|
||||
}
|
||||
|
||||
mLockPatternView.setEnabled(false);
|
||||
|
||||
final LockscreenCredential credential = LockscreenCredential.createPattern(pattern);
|
||||
|
||||
if (mRemoteValidation) {
|
||||
validateGuess(credential);
|
||||
updateRemoteLockscreenValidationViews();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(b/161956762): Sanitize this
|
||||
Intent intent = new Intent();
|
||||
if (mReturnGatekeeperPassword) {
|
||||
if (isInternalActivity()) {
|
||||
startVerifyPattern(credential, intent,
|
||||
LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE);
|
||||
return;
|
||||
}
|
||||
} else if (mForceVerifyPath) {
|
||||
if (isInternalActivity()) {
|
||||
final int flags = mRequestWriteRepairModePassword
|
||||
? LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW : 0;
|
||||
startVerifyPattern(credential, intent, flags);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
startCheckPattern(credential, intent);
|
||||
return;
|
||||
}
|
||||
|
||||
mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId);
|
||||
}
|
||||
|
||||
private boolean isInternalActivity() {
|
||||
return getActivity() instanceof ConfirmLockPattern.InternalActivity;
|
||||
}
|
||||
|
||||
private void startVerifyPattern(final LockscreenCredential pattern,
|
||||
final Intent intent, @LockPatternUtils.VerifyFlag int flags) {
|
||||
final int localEffectiveUserId = mEffectiveUserId;
|
||||
final int localUserId = mUserId;
|
||||
final LockPatternChecker.OnVerifyCallback onVerifyCallback =
|
||||
(response, timeoutMs) -> {
|
||||
mPendingLockCheck = null;
|
||||
final boolean matched = response.isMatched();
|
||||
if (matched && mReturnCredentials) {
|
||||
if ((flags & LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE) != 0) {
|
||||
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE,
|
||||
response.getGatekeeperPasswordHandle());
|
||||
} else {
|
||||
intent.putExtra(
|
||||
ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
|
||||
response.getGatekeeperHAT());
|
||||
}
|
||||
}
|
||||
mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
|
||||
localEffectiveUserId);
|
||||
};
|
||||
mPendingLockCheck = (localEffectiveUserId == localUserId)
|
||||
? LockPatternChecker.verifyCredential(
|
||||
mLockPatternUtils, pattern, localUserId, flags,
|
||||
onVerifyCallback)
|
||||
: LockPatternChecker.verifyTiedProfileChallenge(
|
||||
mLockPatternUtils, pattern, localUserId, flags,
|
||||
onVerifyCallback);
|
||||
}
|
||||
|
||||
private void startCheckPattern(final LockscreenCredential pattern,
|
||||
final Intent intent) {
|
||||
if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
|
||||
// Pattern size is less than the minimum, do not count it as an fail attempt.
|
||||
onPatternChecked(false, intent, 0, mEffectiveUserId, false /* newResult */);
|
||||
return;
|
||||
}
|
||||
|
||||
final int localEffectiveUserId = mEffectiveUserId;
|
||||
mPendingLockCheck = LockPatternChecker.checkCredential(
|
||||
mLockPatternUtils,
|
||||
pattern,
|
||||
localEffectiveUserId,
|
||||
new LockPatternChecker.OnCheckCallback() {
|
||||
@Override
|
||||
public void onChecked(boolean matched, int timeoutMs) {
|
||||
mPendingLockCheck = null;
|
||||
if (matched && isInternalActivity() && mReturnCredentials) {
|
||||
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD,
|
||||
pattern);
|
||||
}
|
||||
mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
|
||||
localEffectiveUserId);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private void onPatternChecked(boolean matched, Intent intent, int timeoutMs,
|
||||
int effectiveUserId, boolean newResult) {
|
||||
mLockPatternView.setEnabled(true);
|
||||
if (matched) {
|
||||
if (newResult) {
|
||||
ConfirmDeviceCredentialUtils.reportSuccessfulAttempt(mLockPatternUtils,
|
||||
mUserManager, mDevicePolicyManager, mEffectiveUserId,
|
||||
/* isStrongAuth */ true);
|
||||
}
|
||||
startDisappearAnimation(intent);
|
||||
ConfirmDeviceCredentialUtils.checkForPendingIntent(getActivity());
|
||||
} else {
|
||||
if (timeoutMs > 0) {
|
||||
refreshLockScreen();
|
||||
long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
|
||||
effectiveUserId, timeoutMs);
|
||||
handleAttemptLockout(deadline);
|
||||
} else {
|
||||
updateStage(Stage.NeedToUnlockWrong);
|
||||
postClearPatternRunnable();
|
||||
}
|
||||
if (newResult) {
|
||||
reportFailedAttempt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRemoteLockscreenValidationResult(
|
||||
RemoteLockscreenValidationResult result) {
|
||||
switch (result.getResultCode()) {
|
||||
case RemoteLockscreenValidationResult.RESULT_GUESS_VALID:
|
||||
if (mCheckBox.isChecked() && mRemoteLockscreenValidationFragment
|
||||
.getLockscreenCredential() != null) {
|
||||
Log.i(TAG, "Setting device screen lock to the other device's screen lock.");
|
||||
SaveAndFinishWorker saveAndFinishWorker = new SaveAndFinishWorker();
|
||||
getFragmentManager().beginTransaction().add(saveAndFinishWorker, null)
|
||||
.commit();
|
||||
getFragmentManager().executePendingTransactions();
|
||||
saveAndFinishWorker
|
||||
.setListener(this)
|
||||
.setRequestGatekeeperPasswordHandle(true);
|
||||
saveAndFinishWorker.start(
|
||||
mLockPatternUtils,
|
||||
mRemoteLockscreenValidationFragment.getLockscreenCredential(),
|
||||
/* currentCredential= */ null,
|
||||
mEffectiveUserId);
|
||||
} else {
|
||||
mCredentialCheckResultTracker.setResult(/* matched= */ true, new Intent(),
|
||||
/* timeoutMs= */ 0, mEffectiveUserId);
|
||||
}
|
||||
return;
|
||||
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:
|
||||
case RemoteLockscreenValidationResult.RESULT_SESSION_EXPIRED:
|
||||
onRemoteLockscreenValidationFailure(String.format(
|
||||
"Cannot continue remote lockscreen validation. ResultCode=%d",
|
||||
result.getResultCode()));
|
||||
break;
|
||||
}
|
||||
updateRemoteLockscreenValidationViews();
|
||||
mRemoteLockscreenValidationFragment.clearLockscreenCredential();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs,
|
||||
int effectiveUserId, boolean newResult) {
|
||||
onPatternChecked(matched, intent, timeoutMs, effectiveUserId, newResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getLastTryOverrideErrorMessageId(int userType) {
|
||||
if (userType == USER_TYPE_MANAGED_PROFILE) {
|
||||
return WORK_PROFILE_LAST_PATTERN_ATTEMPT_BEFORE_WIPE;
|
||||
}
|
||||
|
||||
return UNDEFINED;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getLastTryDefaultErrorMessage(int userType) {
|
||||
switch (userType) {
|
||||
case USER_TYPE_PRIMARY:
|
||||
return R.string.lock_last_pattern_attempt_before_wipe_device;
|
||||
case USER_TYPE_MANAGED_PROFILE:
|
||||
return R.string.lock_last_pattern_attempt_before_wipe_profile;
|
||||
case USER_TYPE_SECONDARY:
|
||||
return R.string.lock_last_pattern_attempt_before_wipe_user;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized user type:" + userType);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleAttemptLockout(long elapsedRealtimeDeadline) {
|
||||
clearResetErrorRunnable();
|
||||
updateStage(Stage.LockedOut);
|
||||
long elapsedRealtime = SystemClock.elapsedRealtime();
|
||||
mCountdownTimer = new CountDownTimer(
|
||||
elapsedRealtimeDeadline - elapsedRealtime,
|
||||
LockPatternUtils.FAILED_ATTEMPT_COUNTDOWN_INTERVAL_MS) {
|
||||
|
||||
@Override
|
||||
public void onTick(long millisUntilFinished) {
|
||||
final int secondsCountdown = (int) (millisUntilFinished / 1000);
|
||||
mErrorTextView.setText(getString(
|
||||
R.string.lockpattern_too_many_failed_confirmation_attempts,
|
||||
secondsCountdown));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
updateStage(Stage.NeedToUnlock);
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void createAnimation(Object obj, long delay,
|
||||
long duration, float translationY, final boolean appearing,
|
||||
Interpolator interpolator,
|
||||
final Runnable finishListener) {
|
||||
if (obj instanceof LockPatternView.CellState) {
|
||||
final LockPatternView.CellState animatedCell = (LockPatternView.CellState) obj;
|
||||
mLockPatternView.startCellStateAnimation(animatedCell,
|
||||
1f, appearing ? 1f : 0f, /* alpha */
|
||||
appearing ? translationY : 0f, /* startTranslation */
|
||||
appearing ? 0f : translationY, /* endTranslation */
|
||||
appearing ? 0f : 1f, 1f /* scale */,
|
||||
delay, duration, interpolator, finishListener);
|
||||
} else {
|
||||
mAppearAnimationUtils.createAnimation((View) obj, delay, duration, translationY,
|
||||
appearing, interpolator, finishListener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for when the current device's lockscreen to the guess used for
|
||||
* remote lockscreen validation.
|
||||
*/
|
||||
@Override
|
||||
public void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData) {
|
||||
Log.i(TAG, "Device lockscreen has been set to remote device's lockscreen.");
|
||||
mRemoteLockscreenValidationFragment.clearLockscreenCredential();
|
||||
|
||||
Intent result = new Intent();
|
||||
if (mRemoteValidation && containsGatekeeperPasswordHandle(resultData)) {
|
||||
result.putExtra(EXTRA_KEY_GK_PW_HANDLE, getGatekeeperPasswordHandle(resultData));
|
||||
}
|
||||
mCredentialCheckResultTracker.setResult(/* matched= */ true, result,
|
||||
/* timeoutMs= */ 0, mEffectiveUserId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
/**
|
||||
* An invisible retained fragment to track lock check result.
|
||||
*/
|
||||
public class CredentialCheckResultTracker extends Fragment {
|
||||
|
||||
private Listener mListener;
|
||||
private boolean mHasResult = false;
|
||||
|
||||
private boolean mResultMatched;
|
||||
private Intent mResultData;
|
||||
private int mResultTimeoutMs;
|
||||
private int mResultEffectiveUserId;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setRetainInstance(true);
|
||||
}
|
||||
|
||||
public void setListener(Listener listener) {
|
||||
if (mListener == listener) {
|
||||
return;
|
||||
}
|
||||
|
||||
mListener = listener;
|
||||
if (mListener != null && mHasResult) {
|
||||
mListener.onCredentialChecked(mResultMatched, mResultData, mResultTimeoutMs,
|
||||
mResultEffectiveUserId, false /* newResult */);
|
||||
}
|
||||
}
|
||||
|
||||
public void setResult(boolean matched, Intent intent, int timeoutMs, int effectiveUserId) {
|
||||
mResultMatched = matched;
|
||||
mResultData = intent;
|
||||
mResultTimeoutMs = timeoutMs;
|
||||
mResultEffectiveUserId = effectiveUserId;
|
||||
|
||||
mHasResult = true;
|
||||
if (mListener != null) {
|
||||
mListener.onCredentialChecked(mResultMatched, mResultData, mResultTimeoutMs,
|
||||
mResultEffectiveUserId, true /* newResult */);
|
||||
mHasResult = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void clearResult() {
|
||||
mHasResult = false;
|
||||
mResultMatched = false;
|
||||
mResultData = null;
|
||||
mResultTimeoutMs = 0;
|
||||
mResultEffectiveUserId = 0;
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
public void onCredentialChecked(boolean matched, Intent intent, int timeoutMs,
|
||||
int effectiveUserId, boolean newResult);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.DevicePolicyResources.Strings.Settings.FORGOT_PASSWORD_TEXT;
|
||||
import static android.app.admin.DevicePolicyResources.Strings.Settings.FORGOT_PASSWORD_TITLE;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.util.Log;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.settings.R;
|
||||
|
||||
import com.google.android.setupcompat.template.FooterBarMixin;
|
||||
import com.google.android.setupcompat.template.FooterButton;
|
||||
import com.google.android.setupdesign.GlifLayout;
|
||||
import com.google.android.setupdesign.util.ContentStyler;
|
||||
import com.google.android.setupdesign.util.ThemeHelper;
|
||||
|
||||
/**
|
||||
* An activity that asks the user to contact their admin to get assistance with forgotten password.
|
||||
*/
|
||||
public class ForgotPasswordActivity extends Activity {
|
||||
public static final String TAG = ForgotPasswordActivity.class.getSimpleName();
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
int userId = getIntent().getIntExtra(Intent.EXTRA_USER_ID, -1);
|
||||
if (userId < 0) {
|
||||
Log.e(TAG, "No valid userId supplied, exiting");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
ThemeHelper.trySetDynamicColor(this);
|
||||
setContentView(R.layout.forgot_password_activity);
|
||||
|
||||
DevicePolicyManager devicePolicyManager = getSystemService(DevicePolicyManager.class);
|
||||
TextView forgotPasswordText = (TextView) findViewById(R.id.forgot_password_text);
|
||||
forgotPasswordText.setText(devicePolicyManager.getResources().getString(
|
||||
FORGOT_PASSWORD_TEXT, () -> getString(R.string.forgot_password_text)));
|
||||
|
||||
final GlifLayout layout = findViewById(R.id.setup_wizard_layout);
|
||||
layout.getMixin(FooterBarMixin.class).setPrimaryButton(
|
||||
new FooterButton.Builder(this)
|
||||
.setText(android.R.string.ok)
|
||||
.setListener(v -> finish())
|
||||
.setButtonType(FooterButton.ButtonType.DONE)
|
||||
.setTheme(com.google.android.setupdesign.R.style.SudGlifButton_Primary)
|
||||
.build()
|
||||
);
|
||||
|
||||
if (ThemeHelper.shouldApplyMaterialYouStyle(this)) {
|
||||
ContentStyler.applyBodyPartnerCustomizationStyle(
|
||||
layout.findViewById(R.id.forgot_password_text));
|
||||
}
|
||||
|
||||
layout.setHeaderText(devicePolicyManager.getResources().getString(
|
||||
FORGOT_PASSWORD_TITLE, () -> getString(R.string.forgot_password_title)));
|
||||
|
||||
UserManager.get(this).requestQuietModeEnabled(
|
||||
false, UserHandle.of(userId), UserManager.QUIET_MODE_DISABLE_DONT_ASK_CREDENTIAL);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.android.internal.widget.LockscreenCredential;
|
||||
|
||||
/**
|
||||
* Helper for handling managed passwords in security settings UI.
|
||||
* It provides resources that should be shown in settings UI when lock password quality is set to
|
||||
* {@link android.app.admin.DevicePolicyManager#PASSWORD_QUALITY_MANAGED} and hooks for implementing
|
||||
* an option for setting the password quality to
|
||||
* {@link android.app.admin.DevicePolicyManager#PASSWORD_QUALITY_MANAGED}.
|
||||
*/
|
||||
public class ManagedLockPasswordProvider {
|
||||
/** Factory method to make it easier to inject extended ManagedLockPasswordProviders. */
|
||||
public static ManagedLockPasswordProvider get(Context context, int userId) {
|
||||
return new ManagedLockPasswordProvider();
|
||||
}
|
||||
|
||||
protected ManagedLockPasswordProvider() {}
|
||||
|
||||
/**
|
||||
* Whether choosing/setting a managed lock password is supported for the user.
|
||||
* Please update {@link #getPickerOptionTitle(boolean)} if overridden to return true.
|
||||
*/
|
||||
boolean isSettingManagedPasswordSupported() { return false; }
|
||||
|
||||
/**
|
||||
* Whether the user should be able to choose managed lock password.
|
||||
*/
|
||||
boolean isManagedPasswordChoosable() { return false; }
|
||||
|
||||
/**
|
||||
* Returns title for managed password preference in security (lock) setting picker.
|
||||
* Should be overridden if {@link #isManagedPasswordSupported()} returns true.
|
||||
* @param forFingerprint Whether fingerprint unlock is enabled.
|
||||
*/
|
||||
CharSequence getPickerOptionTitle(boolean forFingerprint) { return ""; }
|
||||
|
||||
/**
|
||||
* Creates intent that should be launched when user chooses managed password in the lock
|
||||
* settings picker.
|
||||
* @param requirePasswordToDecrypt Whether a password is needed to decrypt the user.
|
||||
* @param password Current lock password.
|
||||
* @return Intent that should update lock password to a managed password.
|
||||
*/
|
||||
Intent createIntent(boolean requirePasswordToDecrypt, LockscreenCredential password) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
16
Settings/src/com/android/settings/password/OWNERS
Normal file
16
Settings/src/com/android/settings/password/OWNERS
Normal file
@@ -0,0 +1,16 @@
|
||||
# Default reviewers for this and subdirectories.
|
||||
curtislb@google.com
|
||||
graciecheng@google.com
|
||||
ilyamaty@google.com
|
||||
jaggies@google.com
|
||||
jbolinger@google.com
|
||||
joshmccloskey@google.com
|
||||
kchyn@google.com
|
||||
paulcrowley@google.com
|
||||
rubinxu@google.com
|
||||
diyab@google.com
|
||||
austindelgado@google.com
|
||||
spdonghao@google.com
|
||||
wenhuiy@google.com
|
||||
|
||||
# Emergency approvers in case the above are not available
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.password.PasswordRequirementAdapter.PasswordRequirementViewHolder;
|
||||
|
||||
/**
|
||||
* Used in {@link ChooseLockPassword} to show password requirements.
|
||||
*/
|
||||
public class PasswordRequirementAdapter extends
|
||||
RecyclerView.Adapter<PasswordRequirementViewHolder> {
|
||||
|
||||
private String[] mRequirements;
|
||||
private Context mContext;
|
||||
|
||||
public PasswordRequirementAdapter(Context context) {
|
||||
mContext = context;
|
||||
setHasStableIds(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PasswordRequirementViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.password_requirement_item, parent, false);
|
||||
return new PasswordRequirementViewHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mRequirements.length;
|
||||
}
|
||||
|
||||
public void setRequirements(String[] requirements) {
|
||||
mRequirements = requirements;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return mRequirements[position].hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewAttachedToWindow(@NonNull PasswordRequirementViewHolder holder) {
|
||||
holder.mDescriptionText.announceForAccessibility(holder.mDescriptionText.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PasswordRequirementViewHolder holder, int position) {
|
||||
final int fontSize = mContext.getResources().getDimensionPixelSize(
|
||||
R.dimen.password_requirement_font_size);
|
||||
holder.mDescriptionText.setText(mRequirements[position]);
|
||||
holder.mDescriptionText.setTextAppearance(R.style.ScreenLockPasswordHintTextFontStyle);
|
||||
holder.mDescriptionText.setTextSize(fontSize / mContext.getResources()
|
||||
.getDisplayMetrics().scaledDensity);
|
||||
}
|
||||
|
||||
public static class PasswordRequirementViewHolder extends RecyclerView.ViewHolder {
|
||||
private TextView mDescriptionText;
|
||||
|
||||
public PasswordRequirementViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
mDescriptionText = (TextView) itemView;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
128
Settings/src/com/android/settings/password/PasswordUtils.java
Normal file
128
Settings/src/com/android/settings/password/PasswordUtils.java
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.settings.password;
|
||||
|
||||
import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.IActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.Utils;
|
||||
|
||||
public final class PasswordUtils extends com.android.settingslib.Utils {
|
||||
|
||||
private static final String TAG = "Settings";
|
||||
|
||||
/**
|
||||
* Returns whether the uid which the activity with {@code activityToken} is launched from has
|
||||
* been granted the {@code permission}.
|
||||
*/
|
||||
public static boolean isCallingAppPermitted(Context context, IBinder activityToken,
|
||||
String permission) {
|
||||
try {
|
||||
return context.checkPermission(permission, /* pid= */ -1,
|
||||
ActivityManager.getService().getLaunchedFromUid(activityToken))
|
||||
== PackageManager.PERMISSION_GRANTED;
|
||||
} catch (RemoteException e) {
|
||||
Log.v(TAG, "Could not talk to activity manager.", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the label of the package which the activity with {@code activityToken} is launched
|
||||
* from or {@code null} if it is launched from the settings app itself.
|
||||
*/
|
||||
@Nullable
|
||||
public static CharSequence getCallingAppLabel(Context context, IBinder activityToken) {
|
||||
String pkg = getCallingAppPackageName(activityToken);
|
||||
if (pkg == null || pkg.equals(SETTINGS_PACKAGE_NAME)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Utils.getApplicationLabel(context, pkg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the package name which the activity with {@code activityToken} is launched from.
|
||||
*/
|
||||
@Nullable
|
||||
public static String getCallingAppPackageName(IBinder activityToken) {
|
||||
String pkg = null;
|
||||
try {
|
||||
pkg = ActivityManager.getService().getLaunchedFromPackage(activityToken);
|
||||
} catch (RemoteException e) {
|
||||
Log.v(TAG, "Could not talk to activity manager.", e);
|
||||
}
|
||||
return pkg;
|
||||
}
|
||||
|
||||
/** Crashes the calling application and provides it with {@code message}. */
|
||||
public static void crashCallingApplication(IBinder activityToken, String message,
|
||||
int exceptionTypeId) {
|
||||
IActivityManager am = ActivityManager.getService();
|
||||
try {
|
||||
int uid = am.getLaunchedFromUid(activityToken);
|
||||
int userId = UserHandle.getUserId(uid);
|
||||
am.crashApplicationWithType(
|
||||
uid,
|
||||
/* initialPid= */ -1,
|
||||
getCallingAppPackageName(activityToken),
|
||||
userId,
|
||||
message,
|
||||
false,
|
||||
exceptionTypeId);
|
||||
} catch (RemoteException e) {
|
||||
Log.v(TAG, "Could not talk to activity manager.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Setup screen lock options button under the Glif Header. */
|
||||
public static void setupScreenLockOptionsButton(Context context, View view, Button optButton) {
|
||||
final LinearLayout headerLayout = view.findViewById(
|
||||
com.google.android.setupdesign.R.id.sud_layout_header);
|
||||
final TextView sucTitleView = headerLayout.findViewById(R.id.suc_layout_title);
|
||||
if (headerLayout != null && sucTitleView != null) {
|
||||
final ViewGroup.MarginLayoutParams layoutTitleParams =
|
||||
(ViewGroup.MarginLayoutParams) sucTitleView.getLayoutParams();
|
||||
final ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
lp.leftMargin = layoutTitleParams.leftMargin;
|
||||
lp.topMargin = (int) context.getResources().getDimensionPixelSize(
|
||||
R.dimen.screen_lock_options_button_margin_top);
|
||||
optButton.setPadding(0, 0, 0, 0);
|
||||
optButton.setLayoutParams(lp);
|
||||
optButton.setText(context.getString(R.string.setup_lock_settings_options_button_label));
|
||||
headerLayout.addView(optButton);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* 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.RemoteLockscreenValidationResult;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.service.remotelockscreenvalidation.IRemoteLockscreenValidationCallback;
|
||||
import android.service.remotelockscreenvalidation.RemoteLockscreenValidationClient;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.internal.widget.LockscreenCredential;
|
||||
import com.android.security.SecureBox;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
|
||||
/**
|
||||
* A fragment used to hold state for remote lockscreen validation.
|
||||
* If the original listener is ever re-created, the new listener must be set again using
|
||||
* {@link #setListener} so that the validation result does not get handled by the old listener.
|
||||
*/
|
||||
public class RemoteLockscreenValidationFragment extends Fragment {
|
||||
|
||||
private static final String TAG = RemoteLockscreenValidationFragment.class.getSimpleName();
|
||||
|
||||
private Listener mListener;
|
||||
private Handler mHandler;
|
||||
private boolean mIsInProgress;
|
||||
private RemoteLockscreenValidationResult mResult;
|
||||
private String mErrorMessage;
|
||||
private LockscreenCredential mLockscreenCredential;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setRetainInstance(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
clearLockscreenCredential();
|
||||
if (mResult != null && mErrorMessage != null) {
|
||||
Log.w(TAG, "Unprocessed remote lockscreen validation result");
|
||||
}
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} if remote lockscreen guess validation has started or
|
||||
* the validation result has not yet been handled.
|
||||
*/
|
||||
public boolean isRemoteValidationInProgress() {
|
||||
return mIsInProgress;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the listener and handler that will handle the result of remote lockscreen validation.
|
||||
* Unprocessed results or failures will be handled after the listener is set.
|
||||
*/
|
||||
public void setListener(Listener listener, Handler handler) {
|
||||
if (mListener == listener) {
|
||||
return;
|
||||
}
|
||||
|
||||
mListener = listener;
|
||||
mHandler = handler;
|
||||
|
||||
if (mResult != null) {
|
||||
handleResult();
|
||||
} else if (mErrorMessage != null) {
|
||||
handleFailure();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@link LockscreenCredential} if it was cached in {@link #validateLockscreenGuess}.
|
||||
*/
|
||||
public LockscreenCredential getLockscreenCredential() {
|
||||
return mLockscreenCredential;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the {@link LockscreenCredential} if it was cached in {@link #validateLockscreenGuess}.
|
||||
*/
|
||||
public void clearLockscreenCredential() {
|
||||
if (mLockscreenCredential != null) {
|
||||
mLockscreenCredential.zeroize();
|
||||
mLockscreenCredential = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the lockscreen guess on the remote device.
|
||||
* @param remoteLockscreenValidationClient the client that should be used to send the guess to
|
||||
* for validation
|
||||
* @param guess the {@link LockscreenCredential} guess that the user entered
|
||||
* @param encryptionKey the key that should be used to encrypt the guess before validation
|
||||
* @param shouldCacheGuess whether to cache to guess so it can be used to set the current
|
||||
* device's lockscreen after validation succeeds.
|
||||
*/
|
||||
public void validateLockscreenGuess(
|
||||
RemoteLockscreenValidationClient remoteLockscreenValidationClient,
|
||||
LockscreenCredential guess, byte[] encryptionKey, boolean shouldCacheGuess) {
|
||||
if (shouldCacheGuess) {
|
||||
mLockscreenCredential = guess;
|
||||
}
|
||||
|
||||
remoteLockscreenValidationClient.validateLockscreenGuess(
|
||||
encryptDeviceCredentialGuess(guess.getCredential(), encryptionKey),
|
||||
new IRemoteLockscreenValidationCallback.Stub() {
|
||||
@Override
|
||||
public void onSuccess(RemoteLockscreenValidationResult result) {
|
||||
mResult = result;
|
||||
handleResult();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(String message) {
|
||||
mErrorMessage = message;
|
||||
handleFailure();
|
||||
}
|
||||
});
|
||||
mIsInProgress = true;
|
||||
}
|
||||
|
||||
private byte[] encryptDeviceCredentialGuess(byte[] guess, byte[] encryptionKey) {
|
||||
try {
|
||||
PublicKey publicKey = SecureBox.decodePublicKey(encryptionKey);
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
private void handleResult() {
|
||||
if (mHandler != null) {
|
||||
mHandler.post(()-> {
|
||||
if (mListener == null || mResult == null) {
|
||||
return;
|
||||
}
|
||||
mIsInProgress = false;
|
||||
mListener.onRemoteLockscreenValidationResult(mResult);
|
||||
mResult = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void handleFailure() {
|
||||
if (mHandler != null) {
|
||||
mHandler.post(()-> {
|
||||
if (mListener == null || mErrorMessage == null) {
|
||||
return;
|
||||
}
|
||||
mIsInProgress = false;
|
||||
mListener.onRemoteLockscreenValidationFailure(
|
||||
String.format("Remote lockscreen validation failed: %s", mErrorMessage));
|
||||
mErrorMessage = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
void onRemoteLockscreenValidationResult(RemoteLockscreenValidationResult result);
|
||||
void onRemoteLockscreenValidationFailure(String message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.content.Intent;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.internal.widget.LockscreenCredential;
|
||||
import com.android.internal.widget.VerifyCredentialResponse;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.safetycenter.LockScreenSafetySource;
|
||||
|
||||
/**
|
||||
* An invisible retained worker fragment to track the AsyncWork that saves (and optionally
|
||||
* verifies if a challenge is given) the chosen lock credential (pattern/pin/password).
|
||||
*/
|
||||
public class SaveAndFinishWorker extends Fragment {
|
||||
private static final String TAG = "SaveAndFinishWorker";
|
||||
|
||||
private Listener mListener;
|
||||
private boolean mFinished;
|
||||
private Intent mResultData;
|
||||
|
||||
private LockPatternUtils mUtils;
|
||||
private boolean mRequestGatekeeperPassword;
|
||||
private boolean mRequestWriteRepairModePassword;
|
||||
private boolean mWasSecureBefore;
|
||||
private int mUserId;
|
||||
private int mUnificationProfileId = UserHandle.USER_NULL;
|
||||
private LockscreenCredential mUnificationProfileCredential;
|
||||
private LockscreenCredential mChosenCredential;
|
||||
private LockscreenCredential mCurrentCredential;
|
||||
|
||||
private boolean mBlocking;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setRetainInstance(true);
|
||||
}
|
||||
|
||||
public SaveAndFinishWorker setListener(Listener listener) {
|
||||
if (mListener == listener) {
|
||||
return this;
|
||||
}
|
||||
|
||||
mListener = listener;
|
||||
if (mFinished && mListener != null) {
|
||||
mListener.onChosenLockSaveFinished(mWasSecureBefore, mResultData);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void prepare(LockPatternUtils utils, LockscreenCredential chosenCredential,
|
||||
LockscreenCredential currentCredential, int userId) {
|
||||
mUtils = utils;
|
||||
mUserId = userId;
|
||||
// This will be a no-op for non managed profiles.
|
||||
mWasSecureBefore = mUtils.isSecure(mUserId);
|
||||
mFinished = false;
|
||||
mResultData = null;
|
||||
|
||||
mChosenCredential = chosenCredential;
|
||||
mCurrentCredential = currentCredential != null ? currentCredential
|
||||
: LockscreenCredential.createNone();
|
||||
}
|
||||
|
||||
public void start(LockPatternUtils utils, LockscreenCredential chosenCredential,
|
||||
LockscreenCredential currentCredential, int userId) {
|
||||
prepare(utils, chosenCredential, currentCredential, userId);
|
||||
if (mBlocking) {
|
||||
finish(saveAndVerifyInBackground().second);
|
||||
} else {
|
||||
new Task().execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the save and verify work in background.
|
||||
* @return pair where the first is a boolean confirming whether the change was successful or not
|
||||
* and second is the Intent which has the challenge token or is null.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
Pair<Boolean, Intent> saveAndVerifyInBackground() {
|
||||
final int userId = mUserId;
|
||||
try {
|
||||
if (!mUtils.setLockCredential(mChosenCredential, mCurrentCredential, userId)) {
|
||||
return Pair.create(false, null);
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG, "Failed to set lockscreen credential", e);
|
||||
return Pair.create(false, null);
|
||||
}
|
||||
|
||||
unifyProfileCredentialIfRequested();
|
||||
|
||||
@LockPatternUtils.VerifyFlag int flags = 0;
|
||||
if (mRequestGatekeeperPassword) {
|
||||
// If a Gatekeeper Password was requested, invoke the LockSettingsService code
|
||||
// path to return a Gatekeeper Password based on the credential that the user
|
||||
// chose. This should only be run if the credential was successfully set.
|
||||
flags |= LockPatternUtils.VERIFY_FLAG_REQUEST_GK_PW_HANDLE;
|
||||
}
|
||||
if (mRequestWriteRepairModePassword) {
|
||||
flags |= LockPatternUtils.VERIFY_FLAG_WRITE_REPAIR_MODE_PW;
|
||||
}
|
||||
if (flags == 0) {
|
||||
return Pair.create(true, null);
|
||||
}
|
||||
|
||||
Intent result = new Intent();
|
||||
final VerifyCredentialResponse response = mUtils.verifyCredential(mChosenCredential,
|
||||
userId, flags);
|
||||
if (response.isMatched()) {
|
||||
if (mRequestGatekeeperPassword && response.containsGatekeeperPasswordHandle()) {
|
||||
result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE,
|
||||
response.getGatekeeperPasswordHandle());
|
||||
} else if (mRequestGatekeeperPassword) {
|
||||
Log.e(TAG, "critical: missing GK PW handle for known good credential: " + response);
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "critical: bad response for known good credential: " + response);
|
||||
}
|
||||
if (mRequestWriteRepairModePassword) {
|
||||
// Notify the caller if repair mode credential is saved successfully
|
||||
result.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_WROTE_REPAIR_MODE_CREDENTIAL,
|
||||
response.isMatched());
|
||||
}
|
||||
|
||||
return Pair.create(true, result);
|
||||
}
|
||||
|
||||
private void finish(Intent resultData) {
|
||||
mFinished = true;
|
||||
mResultData = resultData;
|
||||
if (mListener != null) {
|
||||
mListener.onChosenLockSaveFinished(mWasSecureBefore, mResultData);
|
||||
}
|
||||
if (mUnificationProfileCredential != null) {
|
||||
mUnificationProfileCredential.zeroize();
|
||||
}
|
||||
LockScreenSafetySource.onLockScreenChange(getContext());
|
||||
}
|
||||
|
||||
public SaveAndFinishWorker setRequestGatekeeperPasswordHandle(boolean value) {
|
||||
mRequestGatekeeperPassword = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SaveAndFinishWorker setRequestWriteRepairModePassword(boolean value) {
|
||||
mRequestWriteRepairModePassword = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SaveAndFinishWorker setBlocking(boolean blocking) {
|
||||
mBlocking = blocking;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SaveAndFinishWorker setProfileToUnify(
|
||||
int profileId, LockscreenCredential credential) {
|
||||
mUnificationProfileId = profileId;
|
||||
mUnificationProfileCredential = credential.duplicate();
|
||||
return this;
|
||||
}
|
||||
|
||||
private void unifyProfileCredentialIfRequested() {
|
||||
if (mUnificationProfileId != UserHandle.USER_NULL) {
|
||||
mUtils.setSeparateProfileChallengeEnabled(mUnificationProfileId, false,
|
||||
mUnificationProfileCredential);
|
||||
}
|
||||
}
|
||||
|
||||
private class Task extends AsyncTask<Void, Void, Pair<Boolean, Intent>> {
|
||||
|
||||
@Override
|
||||
protected Pair<Boolean, Intent> doInBackground(Void... params){
|
||||
return saveAndVerifyInBackground();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Pair<Boolean, Intent> resultData) {
|
||||
if (!resultData.first) {
|
||||
Toast.makeText(getContext(), R.string.lockpassword_credential_changed,
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
finish(resultData.second);
|
||||
}
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
void onChosenLockSaveFinished(boolean wasSecureBefore, Intent resultData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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 android.app.KeyguardManager;
|
||||
import android.content.Context;
|
||||
|
||||
public class ScreenLockSuggestionActivity extends ChooseLockGeneric {
|
||||
|
||||
public static boolean isSuggestionComplete(Context context) {
|
||||
KeyguardManager km = context.getSystemService(KeyguardManager.class);
|
||||
return km.isKeyguardSecure();
|
||||
}
|
||||
}
|
||||
107
Settings/src/com/android/settings/password/ScreenLockType.java
Normal file
107
Settings/src/com/android/settings/password/ScreenLockType.java
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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 android.app.admin.DevicePolicyManager;
|
||||
|
||||
/**
|
||||
* List of screen lock type options that are available in ChooseLockGeneric. Provides the key and
|
||||
* the associated quality, and also some helper functions to translate between them.
|
||||
*/
|
||||
public enum ScreenLockType {
|
||||
|
||||
NONE(
|
||||
DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
|
||||
"unlock_set_off"),
|
||||
SWIPE(
|
||||
DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
|
||||
"unlock_set_none"),
|
||||
PATTERN(
|
||||
DevicePolicyManager.PASSWORD_QUALITY_SOMETHING,
|
||||
"unlock_set_pattern"),
|
||||
PIN(
|
||||
DevicePolicyManager.PASSWORD_QUALITY_NUMERIC,
|
||||
DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX,
|
||||
"unlock_set_pin"),
|
||||
PASSWORD(
|
||||
DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC,
|
||||
DevicePolicyManager.PASSWORD_QUALITY_COMPLEX,
|
||||
"unlock_set_password"),
|
||||
MANAGED(
|
||||
DevicePolicyManager.PASSWORD_QUALITY_MANAGED,
|
||||
"unlock_set_managed");
|
||||
|
||||
/**
|
||||
* The default quality of the type of lock used. For example, in the case of PIN, the default
|
||||
* quality if PASSWORD_QUALITY_NUMERIC, while the highest quality is
|
||||
* PASSWORD_QUALITY_NUMERIC_COMPLEX.
|
||||
*/
|
||||
public final int defaultQuality;
|
||||
|
||||
/**
|
||||
* The highest quality for the given type of lock. For example, in the case of password, the
|
||||
* default quality is PASSWORD_QUALITY_ALPHABETIC, but the highest possible quality is
|
||||
* PASSWORD_QUALITY_COMPLEX.
|
||||
*/
|
||||
public final int maxQuality;
|
||||
|
||||
public final String preferenceKey;
|
||||
|
||||
ScreenLockType(int quality, String preferenceKey) {
|
||||
this(quality, quality, preferenceKey);
|
||||
}
|
||||
|
||||
ScreenLockType(int defaultQuality, int maxQuality, String preferenceKey) {
|
||||
this.defaultQuality = defaultQuality;
|
||||
this.maxQuality = maxQuality;
|
||||
this.preferenceKey = preferenceKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the screen lock type for the given quality. Note that this method assumes that a screen
|
||||
* lock is enabled, which means if the quality is
|
||||
* {@link DevicePolicyManager#PASSWORD_QUALITY_UNSPECIFIED}, the returned type will be
|
||||
* {@link #SWIPE} and not {@link #NONE}.
|
||||
*/
|
||||
public static ScreenLockType fromQuality(int quality) {
|
||||
switch (quality) {
|
||||
case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING:
|
||||
return ScreenLockType.PATTERN;
|
||||
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC:
|
||||
case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX:
|
||||
return ScreenLockType.PIN;
|
||||
case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC:
|
||||
case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC:
|
||||
case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX:
|
||||
return ScreenLockType.PASSWORD;
|
||||
case DevicePolicyManager.PASSWORD_QUALITY_MANAGED:
|
||||
return ScreenLockType.MANAGED;
|
||||
case DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED:
|
||||
return ScreenLockType.SWIPE;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ScreenLockType fromKey(String key) {
|
||||
for (ScreenLockType lock : ScreenLockType.values()) {
|
||||
if (lock.preferenceKey.equals(key)) {
|
||||
return lock;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY;
|
||||
import static android.app.admin.DevicePolicyManager.ACTION_SET_NEW_PARENT_PROFILE_PASSWORD;
|
||||
import static android.app.admin.DevicePolicyManager.ACTION_SET_NEW_PASSWORD;
|
||||
import static android.app.admin.DevicePolicyManager.EXTRA_DEVICE_PASSWORD_REQUIREMENT_ONLY;
|
||||
import static android.app.admin.DevicePolicyManager.EXTRA_PASSWORD_COMPLEXITY;
|
||||
import static android.app.admin.DevicePolicyManager.PASSWORD_COMPLEXITY_NONE;
|
||||
|
||||
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CALLER_APP_NAME;
|
||||
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CHOOSE_LOCK_SCREEN_DESCRIPTION;
|
||||
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_CHOOSE_LOCK_SCREEN_TITLE;
|
||||
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_DEVICE_PASSWORD_REQUIREMENT_ONLY;
|
||||
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_IS_CALLING_APP_ADMIN;
|
||||
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.RemoteServiceException.MissingRequestPasswordComplexityPermissionException;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.app.admin.DevicePolicyManager.PasswordComplexity;
|
||||
import android.app.admin.PasswordMetrics;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.settings.overlay.FeatureFactory;
|
||||
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
|
||||
|
||||
import com.google.android.setupcompat.util.WizardManagerHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Trampolines {@link DevicePolicyManager#ACTION_SET_NEW_PASSWORD} and
|
||||
* {@link DevicePolicyManager#ACTION_SET_NEW_PARENT_PROFILE_PASSWORD} intent to the appropriate UI
|
||||
* activity for handling set new password.
|
||||
*/
|
||||
public class SetNewPasswordActivity extends Activity implements SetNewPasswordController.Ui {
|
||||
private static final String TAG = "SetNewPasswordActivity";
|
||||
private String mNewPasswordAction;
|
||||
private SetNewPasswordController mSetNewPasswordController;
|
||||
|
||||
/**
|
||||
* From intent extra {@link DevicePolicyManager#EXTRA_PASSWORD_COMPLEXITY}.
|
||||
*
|
||||
* <p>This is used only if caller has the required permission and activity is launched by
|
||||
* {@link DevicePolicyManager#ACTION_SET_NEW_PASSWORD}.
|
||||
*/
|
||||
private @PasswordComplexity int mRequestedMinComplexity = PASSWORD_COMPLEXITY_NONE;
|
||||
|
||||
private boolean mDevicePasswordRequirementOnly = false;
|
||||
|
||||
/**
|
||||
* Label of the app which launches this activity.
|
||||
*
|
||||
* <p>Value would be {@code null} if launched from settings app.
|
||||
*/
|
||||
private String mCallerAppName = null;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedState) {
|
||||
super.onCreate(savedState);
|
||||
final Intent intent = getIntent();
|
||||
|
||||
mNewPasswordAction = intent.getAction();
|
||||
if (!ACTION_SET_NEW_PASSWORD.equals(mNewPasswordAction)
|
||||
&& !ACTION_SET_NEW_PARENT_PROFILE_PASSWORD.equals(mNewPasswordAction)) {
|
||||
Log.e(TAG, "Unexpected action to launch this activity");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
logSetNewPasswordIntent();
|
||||
|
||||
final IBinder activityToken = getActivityToken();
|
||||
mCallerAppName = (String) PasswordUtils.getCallingAppLabel(this, activityToken);
|
||||
if (ACTION_SET_NEW_PASSWORD.equals(mNewPasswordAction)
|
||||
&& intent.hasExtra(EXTRA_PASSWORD_COMPLEXITY)) {
|
||||
final boolean hasPermission = PasswordUtils.isCallingAppPermitted(
|
||||
this, activityToken, REQUEST_PASSWORD_COMPLEXITY);
|
||||
if (hasPermission) {
|
||||
mRequestedMinComplexity =
|
||||
PasswordMetrics.sanitizeComplexityLevel(intent.getIntExtra(
|
||||
EXTRA_PASSWORD_COMPLEXITY, PASSWORD_COMPLEXITY_NONE));
|
||||
} else {
|
||||
PasswordUtils.crashCallingApplication(activityToken,
|
||||
"Must have permission "
|
||||
+ REQUEST_PASSWORD_COMPLEXITY + " to use extra "
|
||||
+ EXTRA_PASSWORD_COMPLEXITY,
|
||||
MissingRequestPasswordComplexityPermissionException.TYPE_ID);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (ACTION_SET_NEW_PARENT_PROFILE_PASSWORD.equals(mNewPasswordAction)) {
|
||||
mDevicePasswordRequirementOnly = intent.getBooleanExtra(
|
||||
EXTRA_DEVICE_PASSWORD_REQUIREMENT_ONLY, false);
|
||||
Log.i(TAG, String.format("DEVICE_PASSWORD_REQUIREMENT_ONLY: %b",
|
||||
mDevicePasswordRequirementOnly));
|
||||
}
|
||||
mSetNewPasswordController = SetNewPasswordController.create(
|
||||
this, this, intent, activityToken);
|
||||
mSetNewPasswordController.dispatchSetNewPasswordIntent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launchChooseLock(Bundle chooseLockFingerprintExtras) {
|
||||
final boolean isInSetupWizard = WizardManagerHelper.isAnySetupWizard(getIntent());
|
||||
Intent intent = isInSetupWizard ? new Intent(this, SetupChooseLockGeneric.class)
|
||||
: new Intent(this, ChooseLockGeneric.class);
|
||||
intent.setAction(mNewPasswordAction);
|
||||
intent.putExtras(chooseLockFingerprintExtras);
|
||||
intent.putExtra(EXTRA_KEY_CHOOSE_LOCK_SCREEN_TITLE,
|
||||
getIntent().getIntExtra(EXTRA_KEY_CHOOSE_LOCK_SCREEN_TITLE, -1));
|
||||
intent.putExtra(EXTRA_KEY_CHOOSE_LOCK_SCREEN_DESCRIPTION,
|
||||
getIntent().getIntExtra(EXTRA_KEY_CHOOSE_LOCK_SCREEN_DESCRIPTION, -1));
|
||||
if (mCallerAppName != null) {
|
||||
intent.putExtra(EXTRA_KEY_CALLER_APP_NAME, mCallerAppName);
|
||||
}
|
||||
if (mRequestedMinComplexity != PASSWORD_COMPLEXITY_NONE) {
|
||||
intent.putExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY, mRequestedMinComplexity);
|
||||
}
|
||||
if (isCallingAppAdmin()) {
|
||||
intent.putExtra(EXTRA_KEY_IS_CALLING_APP_ADMIN, true);
|
||||
}
|
||||
intent.putExtra(EXTRA_KEY_DEVICE_PASSWORD_REQUIREMENT_ONLY, mDevicePasswordRequirementOnly);
|
||||
// Copy the setup wizard intent extra to the intent.
|
||||
WizardManagerHelper.copyWizardManagerExtras(getIntent(), intent);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
|
||||
private boolean isCallingAppAdmin() {
|
||||
DevicePolicyManager devicePolicyManager = getSystemService(DevicePolicyManager.class);
|
||||
String callingAppPackageName = PasswordUtils.getCallingAppPackageName(getActivityToken());
|
||||
List<ComponentName> admins = devicePolicyManager.getActiveAdmins();
|
||||
if (admins == null) {
|
||||
return false;
|
||||
}
|
||||
for (ComponentName componentName : admins) {
|
||||
if (componentName.getPackageName().equals(callingAppPackageName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void logSetNewPasswordIntent() {
|
||||
final String callingAppPackageName =
|
||||
PasswordUtils.getCallingAppPackageName(getActivityToken());
|
||||
|
||||
// use int min value to denote absence of EXTRA_PASSWORD_COMPLEXITY
|
||||
final int extraPasswordComplexity = getIntent().hasExtra(EXTRA_PASSWORD_COMPLEXITY)
|
||||
? getIntent().getIntExtra(EXTRA_PASSWORD_COMPLEXITY, PASSWORD_COMPLEXITY_NONE)
|
||||
: Integer.MIN_VALUE;
|
||||
|
||||
final boolean extraDevicePasswordRequirementOnly = getIntent().getBooleanExtra(
|
||||
EXTRA_DEVICE_PASSWORD_REQUIREMENT_ONLY, false);
|
||||
|
||||
// Use 30th bit to encode extraDevicePasswordRequirementOnly, since the top bit (31th bit)
|
||||
// encodes whether EXTRA_PASSWORD_COMPLEXITY has been absent.
|
||||
final int logValue = extraPasswordComplexity
|
||||
| (extraDevicePasswordRequirementOnly ? 1 << 30 : 0);
|
||||
// this activity is launched by either ACTION_SET_NEW_PASSWORD or
|
||||
// ACTION_SET_NEW_PARENT_PROFILE_PASSWORD
|
||||
final int action = ACTION_SET_NEW_PASSWORD.equals(mNewPasswordAction)
|
||||
? SettingsEnums.ACTION_SET_NEW_PASSWORD
|
||||
: SettingsEnums.ACTION_SET_NEW_PARENT_PROFILE_PASSWORD;
|
||||
|
||||
final MetricsFeatureProvider metricsProvider =
|
||||
FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
|
||||
metricsProvider.action(
|
||||
metricsProvider.getAttribution(this),
|
||||
action,
|
||||
SettingsEnums.SET_NEW_PASSWORD_ACTIVITY,
|
||||
callingAppPackageName,
|
||||
logValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.ACTION_SET_NEW_PASSWORD;
|
||||
import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FACE;
|
||||
import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT;
|
||||
|
||||
import static com.android.internal.util.Preconditions.checkNotNull;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.admin.DevicePolicyManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.hardware.face.FaceManager;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.UserManager;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.settings.Utils;
|
||||
|
||||
/**
|
||||
* Business logic for {@link SetNewPasswordActivity}.
|
||||
*
|
||||
* <p>On devices that supports fingerprint, this controller directs the user to configure
|
||||
* fingerprint + a backup password if the device admin allows fingerprint for keyguard and
|
||||
* the user has never configured a fingerprint before.
|
||||
*/
|
||||
final class SetNewPasswordController {
|
||||
|
||||
interface Ui {
|
||||
/** Starts the {@link ChooseLockGeneric} activity with the given extras. */
|
||||
void launchChooseLock(Bundle chooseLockFingerprintExtras);
|
||||
}
|
||||
|
||||
/**
|
||||
* Which user is setting new password.
|
||||
*/
|
||||
private final int mTargetUserId;
|
||||
private final PackageManager mPackageManager;
|
||||
@Nullable
|
||||
private final FingerprintManager mFingerprintManager;
|
||||
@Nullable
|
||||
private final FaceManager mFaceManager;
|
||||
private final DevicePolicyManager mDevicePolicyManager;
|
||||
private final Ui mUi;
|
||||
|
||||
public static SetNewPasswordController create(Context context, Ui ui, Intent intent,
|
||||
IBinder activityToken) {
|
||||
// Trying to figure out which user is setting new password. If it is
|
||||
// ACTION_SET_NEW_PARENT_PROFILE_PASSWORD, it is the current user to set
|
||||
// new password. Otherwise, it is the user who starts this activity
|
||||
// setting new password.
|
||||
final int userId;
|
||||
if (ACTION_SET_NEW_PASSWORD.equals(intent.getAction())) {
|
||||
userId = Utils.getSecureTargetUser(activityToken,
|
||||
UserManager.get(context), null, intent.getExtras()).getIdentifier();
|
||||
} else {
|
||||
userId = ActivityManager.getCurrentUser();
|
||||
}
|
||||
// Create a wrapper of FingerprintManager for testing, see IFingerPrintManager for details.
|
||||
final FingerprintManager fingerprintManager = Utils.getFingerprintManagerOrNull(context);
|
||||
final FaceManager faceManager = Utils.getFaceManagerOrNull(context);
|
||||
return new SetNewPasswordController(userId,
|
||||
context.getPackageManager(),
|
||||
fingerprintManager, faceManager,
|
||||
(DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE), ui);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
SetNewPasswordController(
|
||||
int targetUserId,
|
||||
PackageManager packageManager,
|
||||
FingerprintManager fingerprintManager,
|
||||
FaceManager faceManager,
|
||||
DevicePolicyManager devicePolicyManager,
|
||||
Ui ui) {
|
||||
mTargetUserId = targetUserId;
|
||||
mPackageManager = checkNotNull(packageManager);
|
||||
mFingerprintManager = fingerprintManager;
|
||||
mFaceManager = faceManager;
|
||||
mDevicePolicyManager = checkNotNull(devicePolicyManager);
|
||||
mUi = checkNotNull(ui);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches the set new password intent to the correct activity that handles it.
|
||||
*/
|
||||
public void dispatchSetNewPasswordIntent() {
|
||||
final Bundle extras;
|
||||
|
||||
final boolean hasFeatureFingerprint = mPackageManager
|
||||
.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
|
||||
final boolean hasFeatureFace = mPackageManager
|
||||
.hasSystemFeature(PackageManager.FEATURE_FACE);
|
||||
|
||||
final boolean shouldShowFingerprintEnroll = mFingerprintManager != null
|
||||
&& mFingerprintManager.isHardwareDetected()
|
||||
&& !mFingerprintManager.hasEnrolledFingerprints(mTargetUserId)
|
||||
&& !isFingerprintDisabledByAdmin();
|
||||
final boolean shouldShowFaceEnroll = mFaceManager != null
|
||||
&& mFaceManager.isHardwareDetected()
|
||||
&& !mFaceManager.hasEnrolledTemplates(mTargetUserId)
|
||||
&& !isFaceDisabledByAdmin();
|
||||
|
||||
if (hasFeatureFace && shouldShowFaceEnroll
|
||||
&& hasFeatureFingerprint && shouldShowFingerprintEnroll) {
|
||||
extras = getBiometricChooseLockExtras();
|
||||
} else if (hasFeatureFace && shouldShowFaceEnroll) {
|
||||
extras = getFaceChooseLockExtras();
|
||||
} else if (hasFeatureFingerprint && shouldShowFingerprintEnroll) {
|
||||
extras = getFingerprintChooseLockExtras();
|
||||
} else {
|
||||
extras = new Bundle();
|
||||
}
|
||||
|
||||
// No matter we show fingerprint options or not, we should tell the next activity which
|
||||
// user is setting new password.
|
||||
extras.putInt(Intent.EXTRA_USER_ID, mTargetUserId);
|
||||
mUi.launchChooseLock(extras);
|
||||
}
|
||||
|
||||
private Bundle getBiometricChooseLockExtras() {
|
||||
Bundle chooseLockExtras = new Bundle();
|
||||
chooseLockExtras.putBoolean(
|
||||
ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, true);
|
||||
chooseLockExtras.putBoolean(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true);
|
||||
chooseLockExtras.putBoolean(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, true);
|
||||
return chooseLockExtras;
|
||||
}
|
||||
|
||||
private Bundle getFingerprintChooseLockExtras() {
|
||||
Bundle chooseLockExtras = new Bundle();
|
||||
chooseLockExtras.putBoolean(
|
||||
ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, true);
|
||||
chooseLockExtras.putBoolean(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true);
|
||||
chooseLockExtras.putBoolean(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, true);
|
||||
return chooseLockExtras;
|
||||
}
|
||||
|
||||
private Bundle getFaceChooseLockExtras() {
|
||||
Bundle chooseLockExtras = new Bundle();
|
||||
chooseLockExtras.putBoolean(
|
||||
ChooseLockGeneric.ChooseLockGenericFragment.HIDE_INSECURE_OPTIONS, true);
|
||||
chooseLockExtras.putBoolean(ChooseLockSettingsHelper.EXTRA_KEY_REQUEST_GK_PW_HANDLE, true);
|
||||
chooseLockExtras.putBoolean(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, true);
|
||||
return chooseLockExtras;
|
||||
}
|
||||
|
||||
private boolean isFingerprintDisabledByAdmin() {
|
||||
int disabledFeatures =
|
||||
mDevicePolicyManager.getKeyguardDisabledFeatures(null, mTargetUserId);
|
||||
return (disabledFeatures & KEYGUARD_DISABLE_FINGERPRINT) != 0;
|
||||
}
|
||||
|
||||
private boolean isFaceDisabledByAdmin() {
|
||||
int disabledFeatures =
|
||||
mDevicePolicyManager.getKeyguardDisabledFeatures(null, mTargetUserId);
|
||||
return (disabledFeatures & KEYGUARD_DISABLE_FACE) != 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY;
|
||||
import static android.app.admin.DevicePolicyManager.EXTRA_PASSWORD_COMPLEXITY;
|
||||
|
||||
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
|
||||
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_REQUESTED_MIN_COMPLEXITY;
|
||||
|
||||
import android.app.RemoteServiceException.MissingRequestPasswordComplexityPermissionException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.UserHandle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SetupWizardUtils;
|
||||
import com.android.settings.utils.SettingsDividerItemDecoration;
|
||||
|
||||
import com.google.android.setupcompat.util.WizardManagerHelper;
|
||||
import com.google.android.setupdesign.GlifPreferenceLayout;
|
||||
import com.google.android.setupdesign.util.ThemeHelper;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Setup Wizard's version of ChooseLockGeneric screen. It inherits the logic and basic structure
|
||||
* from ChooseLockGeneric class, and should remain similar to that behaviorally. This class should
|
||||
* only overload base methods for minor theme and behavior differences specific to Setup Wizard.
|
||||
* Other changes should be done to ChooseLockGeneric class instead and let this class inherit
|
||||
* those changes.
|
||||
*/
|
||||
// TODO(b/123225425): Restrict SetupChooseLockGeneric to be accessible by SUW only
|
||||
public class SetupChooseLockGeneric extends ChooseLockGeneric {
|
||||
|
||||
private static final String KEY_UNLOCK_SET_DO_LATER = "unlock_set_do_later";
|
||||
|
||||
@Override
|
||||
protected boolean isValidFragment(String fragmentName) {
|
||||
return SetupChooseLockGenericFragment.class.getName().equals(fragmentName);
|
||||
}
|
||||
|
||||
@Override
|
||||
/* package */ Class<? extends PreferenceFragmentCompat> getFragmentClass() {
|
||||
return SetupChooseLockGenericFragment.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstance) {
|
||||
setTheme(SetupWizardUtils.getTheme(this, getIntent()));
|
||||
ThemeHelper.trySetDynamicColor(this);
|
||||
super.onCreate(savedInstance);
|
||||
|
||||
if(getIntent().hasExtra(EXTRA_KEY_REQUESTED_MIN_COMPLEXITY)) {
|
||||
IBinder activityToken = getActivityToken();
|
||||
boolean hasPermission = PasswordUtils.isCallingAppPermitted(
|
||||
this, activityToken, REQUEST_PASSWORD_COMPLEXITY);
|
||||
if (!hasPermission) {
|
||||
PasswordUtils.crashCallingApplication(activityToken,
|
||||
"Must have permission " + REQUEST_PASSWORD_COMPLEXITY
|
||||
+ " to use extra " + EXTRA_PASSWORD_COMPLEXITY,
|
||||
MissingRequestPasswordComplexityPermissionException.TYPE_ID);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
findViewById(R.id.content_parent).setFitsSystemWindows(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isToolbarEnabled() {
|
||||
// Hide the action bar from this page.
|
||||
return false;
|
||||
}
|
||||
|
||||
public static class SetupChooseLockGenericFragment extends ChooseLockGenericFragment {
|
||||
|
||||
public static final String EXTRA_PASSWORD_QUALITY = ":settings:password_quality";
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
GlifPreferenceLayout layout = (GlifPreferenceLayout) view;
|
||||
layout.setDescriptionText(loadDescriptionText());
|
||||
layout.setDividerItemDecoration(new SettingsDividerItemDecoration(getContext()));
|
||||
layout.setDividerInset(getContext().getResources().getDimensionPixelSize(
|
||||
com.google.android.setupdesign.R.dimen.sud_items_glif_text_divider_inset));
|
||||
|
||||
layout.setIcon(getContext().getDrawable(R.drawable.ic_lock));
|
||||
|
||||
int titleResource = isForBiometric() ?
|
||||
R.string.lock_settings_picker_title : R.string.setup_lock_settings_picker_title;
|
||||
if (getActivity() != null) {
|
||||
getActivity().setTitle(titleResource);
|
||||
}
|
||||
|
||||
layout.setHeaderText(titleResource);
|
||||
// Use the dividers in SetupWizardRecyclerLayout. Suppress the dividers in
|
||||
// PreferenceFragment.
|
||||
setDivider(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addHeaderView() {
|
||||
// The original logic has been moved to onViewCreated and
|
||||
// uses GlifLayout#setDescriptionText instead,
|
||||
// keep empty body here since we won't call super method.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
if (data == null) {
|
||||
data = new Intent();
|
||||
}
|
||||
// Add the password quality extra to the intent data that will be sent back for
|
||||
// Setup Wizard.
|
||||
LockPatternUtils lockPatternUtils = new LockPatternUtils(getActivity());
|
||||
data.putExtra(EXTRA_PASSWORD_QUALITY,
|
||||
lockPatternUtils.getKeyguardStoredPasswordQuality(UserHandle.myUserId()));
|
||||
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
|
||||
Bundle savedInstanceState) {
|
||||
GlifPreferenceLayout layout = (GlifPreferenceLayout) parent;
|
||||
return layout.onCreateRecyclerView(inflater, parent, savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean canRunBeforeDeviceProvisioned() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<? extends ChooseLockGeneric.InternalActivity> getInternalActivityClass() {
|
||||
return SetupChooseLockGeneric.InternalActivity.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean alwaysHideInsecureScreenLockTypes() {
|
||||
// At this part of the flow, the user has already indicated they want to add a pin,
|
||||
// pattern or password, so don't show "None" or "Slide". We disable them here.
|
||||
// This only happens for setup wizard.
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addPreferences() {
|
||||
if (isForBiometric()) {
|
||||
super.addPreferences();
|
||||
} else {
|
||||
addPreferencesFromResource(R.xml.setup_security_settings_picker);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceTreeClick(Preference preference) {
|
||||
final String key = preference.getKey();
|
||||
if (KEY_UNLOCK_SET_DO_LATER.equals(key)) {
|
||||
// show warning.
|
||||
final Intent intent = getActivity().getIntent();
|
||||
SetupSkipDialog dialog = SetupSkipDialog.newInstance(
|
||||
CREDENTIAL_TYPE_NONE,
|
||||
intent.getBooleanExtra(SetupSkipDialog.EXTRA_FRP_SUPPORTED, false),
|
||||
/* forFingerprint= */ false,
|
||||
/* forFace= */ false,
|
||||
/* forBiometrics= */ false,
|
||||
WizardManagerHelper.isAnySetupWizard(intent)
|
||||
);
|
||||
dialog.show(getFragmentManager());
|
||||
return true;
|
||||
}
|
||||
return super.onPreferenceTreeClick(preference);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Intent getLockPasswordIntent(int quality) {
|
||||
final Intent intent = SetupChooseLockPassword.modifyIntentForSetup(
|
||||
getContext(), super.getLockPasswordIntent(quality));
|
||||
SetupWizardUtils.copySetupExtras(getActivity().getIntent(), intent);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Intent getLockPatternIntent() {
|
||||
final Intent intent = SetupChooseLockPattern.modifyIntentForSetup(
|
||||
getContext(), super.getLockPatternIntent());
|
||||
SetupWizardUtils.copySetupExtras(getActivity().getIntent(), intent);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Intent getBiometricEnrollIntent(Context context) {
|
||||
final Intent intent = super.getBiometricEnrollIntent(context);
|
||||
SetupWizardUtils.copySetupExtras(getActivity().getIntent(), intent);
|
||||
return intent;
|
||||
}
|
||||
|
||||
private boolean isForBiometric() {
|
||||
return mForFingerprint || mForFace || mForBiometrics;
|
||||
}
|
||||
|
||||
String loadDescriptionText() {
|
||||
return getString(isForBiometric()
|
||||
? R.string.lock_settings_picker_biometrics_added_security_message
|
||||
: R.string.setup_lock_settings_picker_message);
|
||||
}
|
||||
}
|
||||
|
||||
public static class InternalActivity extends ChooseLockGeneric.InternalActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedState) {
|
||||
setTheme(SetupWizardUtils.getTheme(this, getIntent()));
|
||||
ThemeHelper.trySetDynamicColor(this);
|
||||
super.onCreate(savedState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isValidFragment(String fragmentName) {
|
||||
return InternalSetupChooseLockGenericFragment.class.getName().equals(fragmentName);
|
||||
}
|
||||
|
||||
@Override
|
||||
/* package */ Class<? extends Fragment> getFragmentClass() {
|
||||
return InternalSetupChooseLockGenericFragment.class;
|
||||
}
|
||||
|
||||
public static class InternalSetupChooseLockGenericFragment
|
||||
extends ChooseLockGenericFragment {
|
||||
@Override
|
||||
protected boolean canRunBeforeDeviceProvisioned() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NotNull View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
GlifPreferenceLayout layout = (GlifPreferenceLayout) view;
|
||||
int titleResource = R.string.lock_settings_picker_new_lock_title;
|
||||
|
||||
layout.setHeaderText(titleResource);
|
||||
setDivider(new ColorDrawable(Color.TRANSPARENT));
|
||||
setDividerHeight(0);
|
||||
getHeaderView().setVisible(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Intent getLockPasswordIntent(int quality) {
|
||||
final Intent intent = SetupChooseLockPassword.modifyIntentForSetup(
|
||||
getContext(), super.getLockPasswordIntent(quality));
|
||||
SetupWizardUtils.copySetupExtras(getIntent(), intent);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Intent getLockPatternIntent() {
|
||||
final Intent intent = SetupChooseLockPattern.modifyIntentForSetup(
|
||||
getContext(), super.getLockPatternIntent());
|
||||
SetupWizardUtils.copySetupExtras(getIntent(), intent);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Intent getBiometricEnrollIntent(Context context) {
|
||||
final Intent intent = super.getBiometricEnrollIntent(context);
|
||||
SetupWizardUtils.copySetupExtras(getIntent(), intent);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
|
||||
Bundle savedInstanceState) {
|
||||
GlifPreferenceLayout layout = (GlifPreferenceLayout) parent;
|
||||
return layout.onCreateRecyclerView(inflater, parent, savedInstanceState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
|
||||
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SetupRedactionInterstitial;
|
||||
import com.android.settings.password.ChooseLockTypeDialogFragment.OnLockTypeSelectedListener;
|
||||
|
||||
import com.google.android.setupcompat.util.WizardManagerHelper;
|
||||
|
||||
/**
|
||||
* Setup Wizard's version of ChooseLockPassword screen. It inherits the logic and basic structure
|
||||
* from ChooseLockPassword class, and should remain similar to that behaviorally. This class should
|
||||
* only overload base methods for minor theme and behavior differences specific to Setup Wizard.
|
||||
* Other changes should be done to ChooseLockPassword class instead and let this class inherit
|
||||
* those changes.
|
||||
*/
|
||||
public class SetupChooseLockPassword extends ChooseLockPassword {
|
||||
|
||||
private static final String TAG = "SetupChooseLockPassword";
|
||||
|
||||
public static Intent modifyIntentForSetup(
|
||||
Context context,
|
||||
Intent chooseLockPasswordIntent) {
|
||||
chooseLockPasswordIntent.setClass(context, SetupChooseLockPassword.class);
|
||||
chooseLockPasswordIntent.putExtra(EXTRA_PREFS_SHOW_BUTTON_BAR, false);
|
||||
return chooseLockPasswordIntent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isValidFragment(String fragmentName) {
|
||||
return SetupChooseLockPasswordFragment.class.getName().equals(fragmentName);
|
||||
}
|
||||
|
||||
@Override
|
||||
/* package */ Class<? extends Fragment> getFragmentClass() {
|
||||
return SetupChooseLockPasswordFragment.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstance) {
|
||||
super.onCreate(savedInstance);
|
||||
findViewById(R.id.content_parent).setFitsSystemWindows(false);
|
||||
}
|
||||
|
||||
public static class SetupChooseLockPasswordFragment extends ChooseLockPasswordFragment
|
||||
implements OnLockTypeSelectedListener {
|
||||
|
||||
private static final String TAG_SKIP_SCREEN_LOCK_DIALOG = "skip_screen_lock_dialog";
|
||||
|
||||
@Nullable
|
||||
private Button mOptionsButton;
|
||||
private boolean mLeftButtonIsSkip;
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
final Activity activity = getActivity();
|
||||
ChooseLockGenericController chooseLockGenericController =
|
||||
new ChooseLockGenericController.Builder(activity, mUserId)
|
||||
.setHideInsecureScreenLockTypes(true)
|
||||
.build();
|
||||
boolean anyOptionsShown = chooseLockGenericController
|
||||
.getVisibleAndEnabledScreenLockTypes().size() > 0;
|
||||
boolean showOptionsButton = activity.getIntent().getBooleanExtra(
|
||||
ChooseLockGeneric.ChooseLockGenericFragment.EXTRA_SHOW_OPTIONS_BUTTON, false);
|
||||
if (!anyOptionsShown) {
|
||||
Log.w(TAG, "Visible screen lock types is empty!");
|
||||
}
|
||||
|
||||
if (showOptionsButton && anyOptionsShown) {
|
||||
mOptionsButton = new Button(new ContextThemeWrapper(getActivity(),
|
||||
com.google.android.setupdesign.R.style.SudGlifButton_Tertiary));
|
||||
mOptionsButton.setId(R.id.screen_lock_options);
|
||||
PasswordUtils.setupScreenLockOptionsButton(getActivity(), view, mOptionsButton);
|
||||
mOptionsButton.setVisibility(View.VISIBLE);
|
||||
mOptionsButton.setOnClickListener((btn) ->
|
||||
ChooseLockTypeDialogFragment.newInstance(mUserId)
|
||||
.show(getChildFragmentManager(), TAG_SKIP_SCREEN_LOCK_DIALOG));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSkipOrClearButtonClick(View view) {
|
||||
if (mLeftButtonIsSkip) {
|
||||
final Intent intent = getActivity().getIntent();
|
||||
final boolean frpSupported = intent
|
||||
.getBooleanExtra(SetupSkipDialog.EXTRA_FRP_SUPPORTED, false);
|
||||
final boolean forFingerprint = intent
|
||||
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
|
||||
final boolean forFace = intent
|
||||
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
|
||||
final boolean forBiometrics = intent
|
||||
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, false);
|
||||
final SetupSkipDialog dialog = SetupSkipDialog.newInstance(
|
||||
mIsAlphaMode ? CREDENTIAL_TYPE_PASSWORD : CREDENTIAL_TYPE_PIN,
|
||||
frpSupported,
|
||||
forFingerprint,
|
||||
forFace,
|
||||
forBiometrics,
|
||||
WizardManagerHelper.isAnySetupWizard(intent));
|
||||
|
||||
ConfirmDeviceCredentialUtils.hideImeImmediately(
|
||||
getActivity().getWindow().getDecorView());
|
||||
|
||||
dialog.show(getFragmentManager());
|
||||
return;
|
||||
}
|
||||
super.onSkipOrClearButtonClick(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Intent getRedactionInterstitialIntent(Context context) {
|
||||
// Setup wizard's redaction interstitial is deferred to optional step. Enable that
|
||||
// optional step if the lock screen was set up.
|
||||
SetupRedactionInterstitial.setEnabled(context, true);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLockTypeSelected(ScreenLockType lock) {
|
||||
ScreenLockType currentLockType = mIsAlphaMode ?
|
||||
ScreenLockType.PASSWORD : ScreenLockType.PIN;
|
||||
if (lock == currentLockType) {
|
||||
return;
|
||||
}
|
||||
startChooseLockActivity(lock, getActivity());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getStageType() {
|
||||
// Return TYPE_NONE to make generic lock screen launch in Setup wizard flow before
|
||||
// fingerprint and face setup.
|
||||
return Stage.TYPE_NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateUi() {
|
||||
super.updateUi();
|
||||
// Show the skip button during SUW but not during Settings > Biometric Enrollment
|
||||
if (mUiStage == Stage.Introduction) {
|
||||
mSkipOrClearButton.setText(getActivity(), R.string.skip_label);
|
||||
mLeftButtonIsSkip = true;
|
||||
} else {
|
||||
mSkipOrClearButton.setText(getActivity(), R.string.lockpassword_clear_label);
|
||||
mLeftButtonIsSkip = false;
|
||||
}
|
||||
|
||||
if (mOptionsButton != null) {
|
||||
mOptionsButton.setVisibility(
|
||||
mUiStage == Stage.Introduction ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
// Visibility of auto pin confirm opt-in/out option should always be invisible.
|
||||
if (mAutoPinConfirmOption != null) {
|
||||
mAutoPinConfirmOption.setVisibility(View.GONE);
|
||||
mAutoConfirmSecurityMessage.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.content.res.Configuration.ORIENTATION_LANDSCAPE;
|
||||
|
||||
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.SetupRedactionInterstitial;
|
||||
|
||||
import com.google.android.setupcompat.util.WizardManagerHelper;
|
||||
|
||||
/**
|
||||
* Setup Wizard's version of ChooseLockPattern screen. It inherits the logic and basic structure
|
||||
* from ChooseLockPattern class, and should remain similar to that behaviorally. This class should
|
||||
* only overload base methods for minor theme and behavior differences specific to Setup Wizard.
|
||||
* Other changes should be done to ChooseLockPattern class instead and let this class inherit
|
||||
* those changes.
|
||||
*/
|
||||
public class SetupChooseLockPattern extends ChooseLockPattern {
|
||||
|
||||
public static Intent modifyIntentForSetup(Context context, Intent chooseLockPatternIntent) {
|
||||
chooseLockPatternIntent.setClass(context, SetupChooseLockPattern.class);
|
||||
return chooseLockPatternIntent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isValidFragment(String fragmentName) {
|
||||
return SetupChooseLockPatternFragment.class.getName().equals(fragmentName);
|
||||
}
|
||||
|
||||
@Override
|
||||
/* package */ Class<? extends Fragment> getFragmentClass() {
|
||||
return SetupChooseLockPatternFragment.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// Show generic pattern title when pattern lock screen launch in Setup wizard flow before
|
||||
// fingerprint and face setup.
|
||||
setTitle(R.string.lockpassword_choose_your_pattern_header);
|
||||
}
|
||||
|
||||
public static class SetupChooseLockPatternFragment extends ChooseLockPatternFragment
|
||||
implements ChooseLockTypeDialogFragment.OnLockTypeSelectedListener {
|
||||
|
||||
private static final String TAG_SKIP_SCREEN_LOCK_DIALOG = "skip_screen_lock_dialog";
|
||||
|
||||
@Nullable
|
||||
private Button mOptionsButton;
|
||||
private boolean mLeftButtonIsSkip;
|
||||
|
||||
@Override
|
||||
public View onCreateView(
|
||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = super.onCreateView(inflater, container, savedInstanceState);
|
||||
if (!getResources().getBoolean(R.bool.config_lock_pattern_minimal_ui)) {
|
||||
mOptionsButton = new Button(new ContextThemeWrapper(getActivity(),
|
||||
com.google.android.setupdesign.R.style.SudGlifButton_Tertiary));
|
||||
mOptionsButton.setId(R.id.screen_lock_options);
|
||||
PasswordUtils.setupScreenLockOptionsButton(getActivity(), view, mOptionsButton);
|
||||
mOptionsButton.setOnClickListener((btn) ->
|
||||
ChooseLockTypeDialogFragment.newInstance(mUserId)
|
||||
.show(getChildFragmentManager(), TAG_SKIP_SCREEN_LOCK_DIALOG));
|
||||
}
|
||||
// Show the skip button during SUW but not during Settings > Biometric Enrollment
|
||||
mSkipOrClearButton.setOnClickListener(this::onSkipOrClearButtonClick);
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSkipOrClearButtonClick(View view) {
|
||||
if (mLeftButtonIsSkip) {
|
||||
final Intent intent = getActivity().getIntent();
|
||||
final boolean frpSupported = intent
|
||||
.getBooleanExtra(SetupSkipDialog.EXTRA_FRP_SUPPORTED, false);
|
||||
final boolean forFingerprint = intent
|
||||
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT, false);
|
||||
final boolean forFace = intent
|
||||
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE, false);
|
||||
final boolean forBiometrics = intent
|
||||
.getBooleanExtra(ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS, false);
|
||||
final SetupSkipDialog dialog = SetupSkipDialog.newInstance(
|
||||
CREDENTIAL_TYPE_PATTERN,
|
||||
frpSupported,
|
||||
forFingerprint,
|
||||
forFace,
|
||||
forBiometrics,
|
||||
WizardManagerHelper.isAnySetupWizard(intent));
|
||||
dialog.show(getFragmentManager());
|
||||
return;
|
||||
}
|
||||
super.onSkipOrClearButtonClick(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLockTypeSelected(ScreenLockType lock) {
|
||||
if (ScreenLockType.PATTERN == lock) {
|
||||
return;
|
||||
}
|
||||
startChooseLockActivity(lock, getActivity());
|
||||
}
|
||||
|
||||
private boolean showMinimalUi() {
|
||||
return getResources().getBoolean(R.bool.config_lock_pattern_minimal_ui);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateStage(Stage stage) {
|
||||
super.updateStage(stage);
|
||||
if (!showMinimalUi() && mOptionsButton != null) {
|
||||
// In landscape, keep view stub to avoid pattern view shifting, but in portrait the
|
||||
// header title and description could become multiple lines in confirm stage,
|
||||
// gone the button view to reserve more room for growth height of header.
|
||||
@View.Visibility
|
||||
final int hideOrGone =
|
||||
getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE
|
||||
? View.INVISIBLE : View.GONE;
|
||||
mOptionsButton.setVisibility(
|
||||
(stage == Stage.Introduction || stage == Stage.HelpScreen ||
|
||||
stage == Stage.ChoiceTooShort || stage == Stage.FirstChoiceValid)
|
||||
? View.VISIBLE : hideOrGone);
|
||||
}
|
||||
|
||||
if (stage.leftMode == LeftButtonMode.Gone && stage == Stage.Introduction) {
|
||||
mSkipOrClearButton.setVisibility(View.VISIBLE);
|
||||
mSkipOrClearButton.setText(getActivity(), R.string.skip_label);
|
||||
mLeftButtonIsSkip = true;
|
||||
} else {
|
||||
mLeftButtonIsSkip = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldShowGenericTitle() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Intent getRedactionInterstitialIntent(Context context) {
|
||||
// Setup wizard's redaction interstitial is deferred to optional step. Enable that
|
||||
// optional step if the lock screen was set up.
|
||||
SetupRedactionInterstitial.setEnabled(context, true);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
207
Settings/src/com/android/settings/password/SetupSkipDialog.java
Normal file
207
Settings/src/com/android/settings/password/SetupSkipDialog.java
Normal file
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Copyright (C) 2016 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.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
|
||||
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PATTERN;
|
||||
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PIN;
|
||||
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_FOR_BIOMETRICS;
|
||||
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_FOR_FACE;
|
||||
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_FOR_FINGERPRINT;
|
||||
import static com.android.settings.password.ChooseLockSettingsHelper.EXTRA_KEY_IS_SUW;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.app.settings.SettingsEnums;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import com.android.internal.widget.LockPatternUtils;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.Utils;
|
||||
import com.android.settings.biometrics.BiometricUtils;
|
||||
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
|
||||
|
||||
public class SetupSkipDialog extends InstrumentedDialogFragment
|
||||
implements DialogInterface.OnClickListener {
|
||||
|
||||
public static final String EXTRA_FRP_SUPPORTED = ":settings:frp_supported";
|
||||
|
||||
private static final String ARG_FRP_SUPPORTED = "frp_supported";
|
||||
// The key indicates type of screen lock credential types(PIN/Pattern/Password)
|
||||
private static final String ARG_LOCK_CREDENTIAL_TYPE = "lock_credential_type";
|
||||
// The key indicates type of lock screen setup is alphanumeric for password setup.
|
||||
private static final String TAG_SKIP_DIALOG = "skip_dialog";
|
||||
public static final int RESULT_SKIP = Activity.RESULT_FIRST_USER + 10;
|
||||
|
||||
public static SetupSkipDialog newInstance(@LockPatternUtils.CredentialType int credentialType,
|
||||
boolean isFrpSupported, boolean forFingerprint, boolean forFace,
|
||||
boolean forBiometrics, boolean isSuw) {
|
||||
SetupSkipDialog dialog = new SetupSkipDialog();
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(ARG_LOCK_CREDENTIAL_TYPE, credentialType);
|
||||
args.putBoolean(ARG_FRP_SUPPORTED, isFrpSupported);
|
||||
args.putBoolean(EXTRA_KEY_FOR_FINGERPRINT, forFingerprint);
|
||||
args.putBoolean(EXTRA_KEY_FOR_FACE, forFace);
|
||||
args.putBoolean(EXTRA_KEY_FOR_BIOMETRICS, forBiometrics);
|
||||
args.putBoolean(EXTRA_KEY_IS_SUW, isSuw);
|
||||
dialog.setArguments(args);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMetricsCategory() {
|
||||
return SettingsEnums.DIALOG_FINGERPRINT_SKIP_SETUP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
return onCreateDialogBuilder().create();
|
||||
}
|
||||
|
||||
private AlertDialog.Builder getBiometricsBuilder(
|
||||
@LockPatternUtils.CredentialType int credentialType, boolean isSuw, boolean hasFace,
|
||||
boolean hasFingerprint) {
|
||||
final boolean isFaceSupported = hasFace && (!isSuw || BiometricUtils.isFaceSupportedInSuw(
|
||||
getContext()));
|
||||
final int msgResId;
|
||||
final int screenLockResId;
|
||||
switch (credentialType) {
|
||||
case CREDENTIAL_TYPE_PATTERN:
|
||||
screenLockResId = R.string.unlock_set_unlock_pattern_title;
|
||||
msgResId = getPatternSkipMessageRes(hasFace && isFaceSupported, hasFingerprint);
|
||||
break;
|
||||
case CREDENTIAL_TYPE_PASSWORD:
|
||||
screenLockResId = R.string.unlock_set_unlock_password_title;
|
||||
msgResId = getPasswordSkipMessageRes(hasFace && isFaceSupported, hasFingerprint);
|
||||
break;
|
||||
case CREDENTIAL_TYPE_PIN:
|
||||
default:
|
||||
screenLockResId = R.string.unlock_set_unlock_pin_title;
|
||||
msgResId = getPinSkipMessageRes(hasFace && isFaceSupported, hasFingerprint);
|
||||
break;
|
||||
}
|
||||
return new AlertDialog.Builder(getContext())
|
||||
.setPositiveButton(R.string.skip_lock_screen_dialog_button_label, this)
|
||||
.setNegativeButton(R.string.cancel_lock_screen_dialog_button_label, this)
|
||||
.setTitle(getSkipSetupTitle(screenLockResId, hasFingerprint,
|
||||
hasFace && isFaceSupported))
|
||||
.setMessage(msgResId);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public AlertDialog.Builder onCreateDialogBuilder() {
|
||||
Bundle args = getArguments();
|
||||
final boolean isSuw = args.getBoolean(EXTRA_KEY_IS_SUW);
|
||||
final boolean forBiometrics = args.getBoolean(EXTRA_KEY_FOR_BIOMETRICS);
|
||||
final boolean forFace = args.getBoolean(EXTRA_KEY_FOR_FACE);
|
||||
final boolean forFingerprint = args.getBoolean(EXTRA_KEY_FOR_FINGERPRINT);
|
||||
@LockPatternUtils.CredentialType
|
||||
final int credentialType = args.getInt(ARG_LOCK_CREDENTIAL_TYPE);
|
||||
|
||||
if (forFace || forFingerprint || forBiometrics) {
|
||||
final boolean hasFace = Utils.hasFaceHardware(getContext());
|
||||
final boolean hasFingerprint = Utils.hasFingerprintHardware(getContext());
|
||||
return getBiometricsBuilder(credentialType, isSuw, hasFace, hasFingerprint);
|
||||
}
|
||||
|
||||
return new AlertDialog.Builder(getContext())
|
||||
.setPositiveButton(R.string.skip_anyway_button_label, this)
|
||||
.setNegativeButton(R.string.go_back_button_label, this)
|
||||
.setTitle(R.string.lock_screen_intro_skip_title)
|
||||
.setMessage(args.getBoolean(ARG_FRP_SUPPORTED) ?
|
||||
R.string.lock_screen_intro_skip_dialog_text_frp :
|
||||
R.string.lock_screen_intro_skip_dialog_text);
|
||||
}
|
||||
|
||||
@StringRes
|
||||
private int getPatternSkipMessageRes(boolean hasFace, boolean hasFingerprint) {
|
||||
if (hasFace && hasFingerprint) {
|
||||
return R.string.lock_screen_pattern_skip_biometrics_message;
|
||||
} else if (hasFace) {
|
||||
return R.string.lock_screen_pattern_skip_face_message;
|
||||
} else if (hasFingerprint) {
|
||||
return R.string.lock_screen_pattern_skip_fingerprint_message;
|
||||
} else {
|
||||
return R.string.lock_screen_pattern_skip_message;
|
||||
}
|
||||
}
|
||||
|
||||
@StringRes
|
||||
private int getPasswordSkipMessageRes(boolean hasFace, boolean hasFingerprint) {
|
||||
if (hasFace && hasFingerprint) {
|
||||
return R.string.lock_screen_password_skip_biometrics_message;
|
||||
} else if (hasFace) {
|
||||
return R.string.lock_screen_password_skip_face_message;
|
||||
} else if (hasFingerprint) {
|
||||
return R.string.lock_screen_password_skip_fingerprint_message;
|
||||
} else {
|
||||
return R.string.lock_screen_password_skip_message;
|
||||
}
|
||||
}
|
||||
|
||||
@StringRes
|
||||
private int getPinSkipMessageRes(boolean hasFace, boolean hasFingerprint) {
|
||||
if (hasFace && hasFingerprint) {
|
||||
return R.string.lock_screen_pin_skip_biometrics_message;
|
||||
} else if (hasFace) {
|
||||
return R.string.lock_screen_pin_skip_face_message;
|
||||
} else if (hasFingerprint) {
|
||||
return R.string.lock_screen_pin_skip_fingerprint_message;
|
||||
} else {
|
||||
return R.string.lock_screen_pin_skip_message;
|
||||
}
|
||||
}
|
||||
|
||||
private String getSkipSetupTitle(int screenTypeResId, boolean hasFingerprint,
|
||||
boolean hasFace) {
|
||||
return getString(R.string.lock_screen_skip_setup_title,
|
||||
BiometricUtils.getCombinedScreenLockOptions(getContext(),
|
||||
getString(screenTypeResId), hasFingerprint, hasFace));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int button) {
|
||||
Activity activity = getActivity();
|
||||
switch (button) {
|
||||
case DialogInterface.BUTTON_POSITIVE:
|
||||
activity.setResult(RESULT_SKIP);
|
||||
activity.finish();
|
||||
break;
|
||||
case DialogInterface.BUTTON_NEGATIVE:
|
||||
View view = activity.getCurrentFocus();
|
||||
if(view != null) {
|
||||
view.requestFocus();
|
||||
InputMethodManager imm = (InputMethodManager) activity
|
||||
.getSystemService(Activity.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void show(FragmentManager manager) {
|
||||
show(manager, TAG_SKIP_DIALOG);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user