fix: 引入Settings的Module

This commit is contained in:
2024-12-10 14:57:24 +08:00
parent ad8fc8731d
commit df105485bd
6934 changed files with 896168 additions and 2 deletions

View File

@@ -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;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -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();
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View 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

View File

@@ -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;
}
}
}

View 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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View 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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}
}
}

View 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);
}
}