Both View focus (which is triggered by View.requestFocus()) and IME focus (which is internally handled inside InputMethodManager), are implemented as delayed tasks on the UI thread. The goal here is to make sure that InputMethodManager.showSoftInput() always gets called only after the target EditText gained IME focus. This requires some tricks, but is basically a solved problem with ImeAwareEditText introduced by I182b05d3ff59fb3b4732d60d0d5a464f0e0e0235. Here we can just reuse it. Note that ConfirmLockPassword & ChooseLockPassword are the only ones using ScrollToParentEditText. Latter doesn't call IMM.showSoftInput(). Fixes: 62542157 Test: Verified keyboard still shows-up on the ConfirmLock screen. Change-Id: I892d639f3cc5d43db553b682d5278b8ce2fe72da
538 lines
22 KiB
Java
538 lines
22 KiB
Java
/*
|
|
* 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 android.app.Fragment;
|
|
import android.app.admin.DevicePolicyManager;
|
|
import android.content.Context;
|
|
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.os.storage.StorageManager;
|
|
import android.text.InputType;
|
|
import android.text.TextUtils;
|
|
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.TextView;
|
|
import android.widget.TextView.OnEditorActionListener;
|
|
|
|
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
|
|
import com.android.internal.widget.LockPatternChecker;
|
|
import com.android.internal.widget.LockPatternUtils;
|
|
import com.android.internal.widget.TextViewInputDisabler;
|
|
import com.android.settings.R;
|
|
import com.android.settings.widget.ImeAwareEditText;
|
|
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 << 2 + isProfile << 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_confirm_your_pin_generic_profile,
|
|
R.string.lockpassword_confirm_your_password_generic_profile,
|
|
R.string.lockpassword_strong_auth_required_reason_restart_device_pin,
|
|
R.string.lockpassword_strong_auth_required_reason_restart_device_password,
|
|
R.string.lockpassword_strong_auth_required_reason_restart_work_pin,
|
|
R.string.lockpassword_strong_auth_required_reason_restart_work_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 = getFragmentManager().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 {
|
|
private static final long ERROR_MESSAGE_TIMEOUT = 3000;
|
|
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 TextView mHeaderTextView;
|
|
private TextView mDetailsTextView;
|
|
private CountDownTimer mCountdownTimer;
|
|
private boolean mIsAlpha;
|
|
private InputMethodManager mImm;
|
|
private boolean mUsingFingerprint = false;
|
|
private AppearAnimationUtils mAppearAnimationUtils;
|
|
private DisappearAnimationUtils mDisappearAnimationUtils;
|
|
private boolean mBlockImm;
|
|
|
|
// required constructor for fragments
|
|
public ConfirmLockPasswordFragment() {
|
|
|
|
}
|
|
|
|
@Override
|
|
public void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
}
|
|
|
|
@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.INTERNAL
|
|
? R.layout.confirm_lock_password_internal
|
|
: R.layout.confirm_lock_password,
|
|
container,
|
|
false);
|
|
|
|
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);
|
|
|
|
mHeaderTextView = (TextView) view.findViewById(R.id.headerText);
|
|
if (mHeaderTextView == null) {
|
|
mHeaderTextView = view.findViewById(R.id.suw_layout_title);
|
|
}
|
|
mDetailsTextView = (TextView) view.findViewById(R.id.detailsText);
|
|
mErrorTextView = (TextView) view.findViewById(R.id.errorText);
|
|
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);
|
|
|
|
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)) {
|
|
headerMessage = getString(getDefaultHeader());
|
|
}
|
|
if (TextUtils.isEmpty(detailsMessage)) {
|
|
detailsMessage = getString(getDefaultDetails());
|
|
}
|
|
mHeaderTextView.setText(headerMessage);
|
|
mDetailsTextView.setText(detailsMessage);
|
|
}
|
|
int currentType = mPasswordEntry.getInputType();
|
|
mPasswordEntry.setInputType(mIsAlpha ? currentType
|
|
: (InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_VARIATION_PASSWORD));
|
|
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(mHeaderTextView.getText());
|
|
|
|
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;
|
|
}
|
|
|
|
private int getDefaultHeader() {
|
|
return mIsAlpha ? R.string.lockpassword_confirm_your_password_header
|
|
: R.string.lockpassword_confirm_your_pin_header;
|
|
}
|
|
|
|
private int getDefaultDetails() {
|
|
boolean isStrongAuthRequired = isStrongAuthRequired();
|
|
boolean isProfile = UserManager.get(getActivity()).isManagedProfile(mEffectiveUserId);
|
|
// Map boolean flags to an index by isStrongAuth << 2 + isProfile << 1 + isAlpha.
|
|
int index = ((isStrongAuthRequired ? 1 : 0) << 2) + ((isProfile ? 1 : 0) << 1)
|
|
+ (mIsAlpha ? 1 : 0);
|
|
return DETAIL_TEXTS[index];
|
|
}
|
|
|
|
private int getErrorMessage() {
|
|
return mIsAlpha ? R.string.lockpassword_invalid_password
|
|
: R.string.lockpassword_invalid_pin;
|
|
}
|
|
|
|
@Override
|
|
protected int getLastTryErrorMessage(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();
|
|
mHeaderTextView.setAlpha(0f);
|
|
mDetailsTextView.setAlpha(0f);
|
|
mCancelButton.setAlpha(0f);
|
|
mPasswordEntry.setAlpha(0f);
|
|
mFingerprintIcon.setAlpha(0f);
|
|
mBlockImm = true;
|
|
}
|
|
|
|
private View[] getActiveViews() {
|
|
ArrayList<View> result = new ArrayList<>();
|
|
result.add(mHeaderTextView);
|
|
result.add(mDetailsTextView);
|
|
if (mCancelButton.getVisibility() == View.VISIBLE) {
|
|
result.add(mCancelButton);
|
|
}
|
|
result.add(mPasswordEntry);
|
|
if (mFingerprintIcon.getVisibility() == View.VISIBLE) {
|
|
result.add(mFingerprintIcon);
|
|
}
|
|
return result.toArray(new View[] {});
|
|
}
|
|
|
|
@Override
|
|
public void startEnterAnimation() {
|
|
super.startEnterAnimation();
|
|
mAppearAnimationUtils.startAnimation(getActiveViews(), new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
mBlockImm = false;
|
|
resetState();
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onPause() {
|
|
super.onPause();
|
|
if (mCountdownTimer != null) {
|
|
mCountdownTimer.cancel();
|
|
mCountdownTimer = null;
|
|
}
|
|
mCredentialCheckResultTracker.setListener(null);
|
|
}
|
|
|
|
@Override
|
|
public int getMetricsCategory() {
|
|
return MetricsEvent.CONFIRM_LOCK_PASSWORD;
|
|
}
|
|
|
|
@Override
|
|
public void onResume() {
|
|
super.onResume();
|
|
long deadline = mLockPatternUtils.getLockoutAttemptDeadline(mEffectiveUserId);
|
|
if (deadline != 0) {
|
|
mCredentialCheckResultTracker.clearResult();
|
|
handleAttemptLockout(deadline);
|
|
} else {
|
|
resetState();
|
|
mErrorTextView.setText("");
|
|
updateErrorMessage(
|
|
mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId));
|
|
}
|
|
mCredentialCheckResultTracker.setListener(this);
|
|
}
|
|
|
|
@Override
|
|
protected void authenticationSucceeded() {
|
|
mCredentialCheckResultTracker.setResult(true, new Intent(), 0, mEffectiveUserId);
|
|
}
|
|
|
|
@Override
|
|
public void onFingerprintIconVisibilityChanged(boolean visible) {
|
|
mUsingFingerprint = visible;
|
|
}
|
|
|
|
private void resetState() {
|
|
if (mBlockImm) return;
|
|
mPasswordEntry.setEnabled(true);
|
|
mPasswordEntryInputDisabler.setInputEnabled(true);
|
|
if (shouldAutoShowSoftKeyboard()) {
|
|
mPasswordEntry.scheduleShowSoftInput();
|
|
}
|
|
}
|
|
|
|
private boolean shouldAutoShowSoftKeyboard() {
|
|
return mPasswordEntry.isEnabled() && !mUsingFingerprint;
|
|
}
|
|
|
|
public void onWindowFocusChanged(boolean hasFocus) {
|
|
if (!hasFocus || mBlockImm) {
|
|
return;
|
|
}
|
|
// Post to let window focus logic to finish to allow soft input show/hide properly.
|
|
mPasswordEntry.post(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
if (shouldAutoShowSoftKeyboard()) {
|
|
resetState();
|
|
return;
|
|
}
|
|
|
|
mImm.hideSoftInputFromWindow(mPasswordEntry.getWindowToken(),
|
|
InputMethodManager.HIDE_IMPLICIT_ONLY);
|
|
}
|
|
});
|
|
}
|
|
|
|
private void handleNext() {
|
|
if (mPendingLockCheck != null || mDisappearing) {
|
|
return;
|
|
}
|
|
|
|
final String pin = mPasswordEntry.getText().toString();
|
|
if (TextUtils.isEmpty(pin)) {
|
|
return;
|
|
}
|
|
|
|
mPasswordEntryInputDisabler.setInputEnabled(false);
|
|
final boolean verifyChallenge = getActivity().getIntent().getBooleanExtra(
|
|
ChooseLockSettingsHelper.EXTRA_KEY_HAS_CHALLENGE, false);
|
|
|
|
Intent intent = new Intent();
|
|
if (verifyChallenge) {
|
|
if (isInternalActivity()) {
|
|
startVerifyPassword(pin, intent);
|
|
return;
|
|
}
|
|
} else {
|
|
startCheckPassword(pin, intent);
|
|
return;
|
|
}
|
|
|
|
mCredentialCheckResultTracker.setResult(false, intent, 0, mEffectiveUserId);
|
|
}
|
|
|
|
private boolean isInternalActivity() {
|
|
return getActivity() instanceof ConfirmLockPassword.InternalActivity;
|
|
}
|
|
|
|
private void startVerifyPassword(final String pin, final Intent intent) {
|
|
long challenge = getActivity().getIntent().getLongExtra(
|
|
ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE, 0);
|
|
final int localEffectiveUserId = mEffectiveUserId;
|
|
final int localUserId = mUserId;
|
|
final LockPatternChecker.OnVerifyCallback onVerifyCallback =
|
|
new LockPatternChecker.OnVerifyCallback() {
|
|
@Override
|
|
public void onVerified(byte[] token, int timeoutMs) {
|
|
mPendingLockCheck = null;
|
|
boolean matched = false;
|
|
if (token != null) {
|
|
matched = true;
|
|
if (mReturnCredentials) {
|
|
intent.putExtra(
|
|
ChooseLockSettingsHelper.EXTRA_KEY_CHALLENGE_TOKEN,
|
|
token);
|
|
}
|
|
}
|
|
mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
|
|
localUserId);
|
|
}
|
|
};
|
|
mPendingLockCheck = (localEffectiveUserId == localUserId)
|
|
? LockPatternChecker.verifyPassword(
|
|
mLockPatternUtils, pin, challenge, localUserId, onVerifyCallback)
|
|
: LockPatternChecker.verifyTiedProfileChallenge(
|
|
mLockPatternUtils, pin, false, challenge, localUserId,
|
|
onVerifyCallback);
|
|
}
|
|
|
|
private void startCheckPassword(final String pin, final Intent intent) {
|
|
final int localEffectiveUserId = mEffectiveUserId;
|
|
mPendingLockCheck = LockPatternChecker.checkPassword(
|
|
mLockPatternUtils,
|
|
pin,
|
|
localEffectiveUserId,
|
|
new LockPatternChecker.OnCheckCallback() {
|
|
@Override
|
|
public void onChecked(boolean matched, int timeoutMs) {
|
|
mPendingLockCheck = null;
|
|
if (matched && isInternalActivity() && mReturnCredentials) {
|
|
intent.putExtra(ChooseLockSettingsHelper.EXTRA_KEY_TYPE,
|
|
mIsAlpha ? StorageManager.CRYPT_TYPE_PASSWORD
|
|
: StorageManager.CRYPT_TYPE_PIN);
|
|
intent.putExtra(
|
|
ChooseLockSettingsHelper.EXTRA_KEY_PASSWORD, pin);
|
|
}
|
|
mCredentialCheckResultTracker.setResult(matched, intent, timeoutMs,
|
|
localEffectiveUserId);
|
|
}
|
|
});
|
|
}
|
|
|
|
private void startDisappearAnimation(final Intent intent) {
|
|
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) {
|
|
reportSuccessfulAttempt();
|
|
}
|
|
startDisappearAnimation(intent);
|
|
checkForPendingIntent();
|
|
} else {
|
|
if (timeoutMs > 0) {
|
|
refreshLockScreen();
|
|
long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
|
|
effectiveUserId, timeoutMs);
|
|
handleAttemptLockout(deadline);
|
|
} else {
|
|
showError(getErrorMessage(), ERROR_MESSAGE_TIMEOUT);
|
|
}
|
|
if (newResult) {
|
|
reportFailedAttempt();
|
|
}
|
|
}
|
|
}
|
|
|
|
@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) {
|
|
long elapsedRealtime = SystemClock.elapsedRealtime();
|
|
mPasswordEntry.setEnabled(false);
|
|
mCountdownTimer = new CountDownTimer(
|
|
elapsedRealtimeDeadline - 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() {
|
|
resetState();
|
|
mErrorTextView.setText("");
|
|
updateErrorMessage(
|
|
mLockPatternUtils.getCurrentFailedPasswordAttempts(mEffectiveUserId));
|
|
}
|
|
}.start();
|
|
}
|
|
|
|
public void onClick(View v) {
|
|
switch (v.getId()) {
|
|
case R.id.next_button:
|
|
handleNext();
|
|
break;
|
|
|
|
case R.id.cancel_button:
|
|
getActivity().setResult(RESULT_CANCELED);
|
|
getActivity().finish();
|
|
break;
|
|
}
|
|
}
|
|
|
|
// {@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;
|
|
}
|
|
}
|
|
}
|