From dc0193004485c8a2700ecd78fc2a907df6bd03ee Mon Sep 17 00:00:00 2001 From: Dave Mankoff Date: Wed, 23 Sep 2020 14:01:33 +0000 Subject: [PATCH] Revert^2 "7/N controllers for remaining Keyguard Password ..." Revert submission 12656261-revert-12585643-b166448040-keyguard-message-area-NUUJOVRYJS This also fixes the original issue introduced in this CL. KeyguardAbsKeyInputViewController#reset would call mView#resetState instead of its own internal resetState method. However, all the logic from the various resetState methods had been moved from the views to their controllers. Simply calling resetState direclty resolves the issue (line 105 of KeyguardAbsKeyInputViewController). Reason for revert: Fixing http://b/169231892 and http://b/169145796 Reverted Changes: I7683b2234:Revert "4/N Setup Controller fo KeyguardSecurityCo... I5cbe04c0c:Revert "5/N Add KeyguardSecurityViewFlipperControl... I11dff38ad:Revert "6/N Add Controller for KeyguardPatternView... I55c250121:Revert "7/N controllers for remaining Keyguard Pas... Ie84234cb8:Revert "8/N Remove View Injection from KeyguardMes... Ic62f199a5:Revert "9/N Clean Up Keyguard Class Structure" Change-Id: Ie3669e0c9a23ffd4443ced0fb08ec754f1df55db Fixes: 169145796 --- .../keyguard/KeyguardAbsKeyInputView.java | 229 +--------- .../KeyguardAbsKeyInputViewController.java | 257 +++++++++++ .../keyguard/KeyguardHostViewController.java | 10 +- .../keyguard/KeyguardInputViewController.java | 54 ++- .../com/android/keyguard/KeyguardPINView.java | 39 +- .../keyguard/KeyguardPasswordView.java | 235 +--------- .../KeyguardPasswordViewController.java | 270 ++++++++++++ .../KeyguardPatternViewController.java | 8 +- .../keyguard/KeyguardPinBasedInputView.java | 140 +----- .../KeyguardPinBasedInputViewController.java | 113 +++++ .../keyguard/KeyguardPinViewController.java | 50 +++ .../KeyguardSecurityContainerController.java | 1 + .../android/keyguard/KeyguardSimPinView.java | 310 +------------- .../KeyguardSimPinViewController.java | 350 +++++++++++++++ .../android/keyguard/KeyguardSimPukView.java | 376 +--------------- .../KeyguardSimPukViewController.java | 404 ++++++++++++++++++ .../keyguard/LiftToActivateListener.java | 9 +- .../src/com/android/keyguard/NumPadKey.java | 4 +- .../dagger/FrameworkServicesModule.java | 7 + .../android/systemui/util/ViewController.java | 15 +- ...KeyguardAbsKeyInputViewControllerTest.java | 120 ++++++ ...yguardPinBasedInputViewControllerTest.java | 106 +++++ .../KeyguardPinBasedInputViewTest.java | 79 ---- 23 files changed, 1830 insertions(+), 1356 deletions(-) create mode 100644 packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java create mode 100644 packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java create mode 100644 packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java create mode 100644 packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java create mode 100644 packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java create mode 100644 packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java create mode 100644 packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java create mode 100644 packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java delete mode 100644 packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewTest.java diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java index 889309dd72e72..1296990d49314 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputView.java @@ -16,45 +16,30 @@ package com.android.keyguard; -import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL; -import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED; - import android.content.Context; import android.content.res.ColorStateList; -import android.os.AsyncTask; -import android.os.CountDownTimer; -import android.os.SystemClock; import android.util.AttributeSet; import android.view.HapticFeedbackConstants; import android.view.KeyEvent; import android.view.View; -import com.android.internal.util.LatencyTracker; -import com.android.internal.widget.LockPatternChecker; import com.android.internal.widget.LockPatternUtils; import com.android.internal.widget.LockscreenCredential; -import com.android.systemui.Dependency; import com.android.systemui.R; /** * Base class for PIN and password unlock screens. */ -public abstract class KeyguardAbsKeyInputView extends KeyguardInputView - implements EmergencyButton.EmergencyButtonCallback { +public abstract class KeyguardAbsKeyInputView extends KeyguardInputView { protected KeyguardSecurityCallback mCallback; - protected LockPatternUtils mLockPatternUtils; - protected AsyncTask mPendingLockCheck; protected SecurityMessageDisplay mSecurityMessageDisplay; protected View mEcaView; protected boolean mEnableHaptics; - private boolean mDismissing; - protected boolean mResumed; - private CountDownTimer mCountdownTimer = null; - private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; // To avoid accidental lockout due to events while the device in in the pocket, ignore // any passwords with length less than or equal to this length. protected static final int MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT = 3; + private KeyDownListener mKeyDownListener; public KeyguardAbsKeyInputView(Context context) { this(context, null); @@ -62,7 +47,10 @@ public abstract class KeyguardAbsKeyInputView extends KeyguardInputView public KeyguardAbsKeyInputView(Context context, AttributeSet attrs) { super(context, attrs); - mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class); + } + + void setEnableHaptics(boolean enableHaptics) { + mEnableHaptics = enableHaptics; } @Override @@ -72,28 +60,10 @@ public abstract class KeyguardAbsKeyInputView extends KeyguardInputView @Override public void setLockPatternUtils(LockPatternUtils utils) { - mLockPatternUtils = utils; - mEnableHaptics = mLockPatternUtils.isTactileFeedbackEnabled(); } @Override public void reset() { - // start fresh - mDismissing = false; - resetPasswordText(false /* animate */, false /* announce */); - // if the user is currently locked out, enforce it. - long deadline = mLockPatternUtils.getLockoutAttemptDeadline( - KeyguardUpdateMonitor.getCurrentUser()); - if (shouldLockout(deadline)) { - handleAttemptLockout(deadline); - } else { - resetState(); - } - } - - // Allow subclasses to override this behavior - protected boolean shouldLockout(long deadline) { - return deadline != 0; } protected abstract int getPasswordTextViewId(); @@ -101,24 +71,7 @@ public abstract class KeyguardAbsKeyInputView extends KeyguardInputView @Override protected void onFinishInflate() { - mLockPatternUtils = new LockPatternUtils(mContext); mEcaView = findViewById(R.id.keyguard_selector_fade_container); - - EmergencyButton button = findViewById(R.id.emergency_call_button); - if (button != null) { - button.setCallback(this); - } - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mSecurityMessageDisplay = KeyguardMessageArea.findSecurityMessageDisplay(this); - } - - @Override - public void onEmergencyButtonClickedWhenInCall() { - mCallback.reset(); } /* @@ -130,147 +83,14 @@ public abstract class KeyguardAbsKeyInputView extends KeyguardInputView return R.string.kg_wrong_password; } - protected void verifyPasswordAndUnlock() { - if (mDismissing) return; // already verified but haven't been dismissed; don't do it again. - - final LockscreenCredential password = getEnteredCredential(); - setPasswordEntryInputEnabled(false); - if (mPendingLockCheck != null) { - mPendingLockCheck.cancel(false); - } - - final int userId = KeyguardUpdateMonitor.getCurrentUser(); - if (password.size() <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) { - // to avoid accidental lockout, only count attempts that are long enough to be a - // real password. This may require some tweaking. - setPasswordEntryInputEnabled(true); - onPasswordChecked(userId, false /* matched */, 0, false /* not valid - too short */); - password.zeroize(); - return; - } - - if (LatencyTracker.isEnabled(mContext)) { - LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL); - LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED); - } - - mKeyguardUpdateMonitor.setCredentialAttempted(); - mPendingLockCheck = LockPatternChecker.checkCredential( - mLockPatternUtils, - password, - userId, - new LockPatternChecker.OnCheckCallback() { - - @Override - public void onEarlyMatched() { - if (LatencyTracker.isEnabled(mContext)) { - LatencyTracker.getInstance(mContext).onActionEnd( - ACTION_CHECK_CREDENTIAL); - } - onPasswordChecked(userId, true /* matched */, 0 /* timeoutMs */, - true /* isValidPassword */); - password.zeroize(); - } - - @Override - public void onChecked(boolean matched, int timeoutMs) { - if (LatencyTracker.isEnabled(mContext)) { - LatencyTracker.getInstance(mContext).onActionEnd( - ACTION_CHECK_CREDENTIAL_UNLOCKED); - } - setPasswordEntryInputEnabled(true); - mPendingLockCheck = null; - if (!matched) { - onPasswordChecked(userId, false /* matched */, timeoutMs, - true /* isValidPassword */); - } - password.zeroize(); - } - - @Override - public void onCancelled() { - // We already got dismissed with the early matched callback, so we cancelled - // the check. However, we still need to note down the latency. - if (LatencyTracker.isEnabled(mContext)) { - LatencyTracker.getInstance(mContext).onActionEnd( - ACTION_CHECK_CREDENTIAL_UNLOCKED); - } - password.zeroize(); - } - }); - } - - private void onPasswordChecked(int userId, boolean matched, int timeoutMs, - boolean isValidPassword) { - boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId; - if (matched) { - mCallback.reportUnlockAttempt(userId, true, 0); - if (dismissKeyguard) { - mDismissing = true; - mCallback.dismiss(true, userId); - } - } else { - if (isValidPassword) { - mCallback.reportUnlockAttempt(userId, false, timeoutMs); - if (timeoutMs > 0) { - long deadline = mLockPatternUtils.setLockoutAttemptDeadline( - userId, timeoutMs); - handleAttemptLockout(deadline); - } - } - if (timeoutMs == 0) { - mSecurityMessageDisplay.setMessage(getWrongPasswordStringId()); - } - } - resetPasswordText(true /* animate */, !matched /* announce deletion if no match */); - } - protected abstract void resetPasswordText(boolean animate, boolean announce); protected abstract LockscreenCredential getEnteredCredential(); protected abstract void setPasswordEntryEnabled(boolean enabled); protected abstract void setPasswordEntryInputEnabled(boolean enabled); - // Prevent user from using the PIN/Password entry until scheduled deadline. - protected void handleAttemptLockout(long elapsedRealtimeDeadline) { - setPasswordEntryEnabled(false); - long elapsedRealtime = SystemClock.elapsedRealtime(); - long secondsInFuture = (long) Math.ceil( - (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0); - mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) { - - @Override - public void onTick(long millisUntilFinished) { - int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0); - mSecurityMessageDisplay.setMessage(mContext.getResources().getQuantityString( - R.plurals.kg_too_many_failed_attempts_countdown, - secondsRemaining, secondsRemaining)); - } - - @Override - public void onFinish() { - mSecurityMessageDisplay.setMessage(""); - resetState(); - } - }.start(); - } - - protected void onUserInput() { - if (mCallback != null) { - mCallback.userActivity(); - mCallback.onUserInput(); - } - mSecurityMessageDisplay.setMessage(""); - } - @Override public boolean onKeyDown(int keyCode, KeyEvent event) { - // Fingerprint sensor sends a KeyEvent.KEYCODE_UNKNOWN. - // We don't want to consider it valid user input because the UI - // will already respond to the event. - if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { - onUserInput(); - } - return false; + return mKeyDownListener != null && mKeyDownListener.onKeyDown(keyCode, event); } @Override @@ -278,26 +98,6 @@ public abstract class KeyguardAbsKeyInputView extends KeyguardInputView return false; } - @Override - public void onPause() { - mResumed = false; - - if (mCountdownTimer != null) { - mCountdownTimer.cancel(); - mCountdownTimer = null; - } - if (mPendingLockCheck != null) { - mPendingLockCheck.cancel(false); - mPendingLockCheck = null; - } - reset(); - } - - @Override - public void onResume(int reason) { - mResumed = true; - } - @Override public KeyguardSecurityCallback getCallback() { return mCallback; @@ -305,12 +105,7 @@ public abstract class KeyguardAbsKeyInputView extends KeyguardInputView @Override public void showPromptReason(int reason) { - if (reason != PROMPT_REASON_NONE) { - int promtReasonStringRes = getPromptReasonStringRes(reason); - if (promtReasonStringRes != 0) { - mSecurityMessageDisplay.setMessage(promtReasonStringRes); - } - } + } @Override @@ -336,5 +131,13 @@ public abstract class KeyguardAbsKeyInputView extends KeyguardInputView public boolean startDisappearAnimation(Runnable finishRunnable) { return false; } + + public void setKeyDownListener(KeyDownListener keyDownListener) { + mKeyDownListener = keyDownListener; + } + + public interface KeyDownListener { + boolean onKeyDown(int keyCode, KeyEvent keyEvent); + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java new file mode 100644 index 0000000000000..a33cef3bbd796 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java @@ -0,0 +1,257 @@ +/* + * 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.keyguard; + +import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL; +import static com.android.internal.util.LatencyTracker.ACTION_CHECK_CREDENTIAL_UNLOCKED; +import static com.android.keyguard.KeyguardAbsKeyInputView.MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT; + +import android.os.AsyncTask; +import android.os.CountDownTimer; +import android.os.SystemClock; +import android.view.KeyEvent; + +import com.android.internal.util.LatencyTracker; +import com.android.internal.widget.LockPatternChecker; +import com.android.internal.widget.LockPatternUtils; +import com.android.internal.widget.LockscreenCredential; +import com.android.keyguard.EmergencyButton.EmergencyButtonCallback; +import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.R; + +public abstract class KeyguardAbsKeyInputViewController + extends KeyguardInputViewController { + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final LockPatternUtils mLockPatternUtils; + protected final KeyguardSecurityCallback mKeyguardSecurityCallback; + private final LatencyTracker mLatencyTracker; + private CountDownTimer mCountdownTimer; + protected KeyguardMessageAreaController mMessageAreaController; + private boolean mDismissing; + protected AsyncTask mPendingLockCheck; + protected boolean mResumed; + + private final KeyDownListener mKeyDownListener = (keyCode, keyEvent) -> { + // Fingerprint sensor sends a KeyEvent.KEYCODE_UNKNOWN. + // We don't want to consider it valid user input because the UI + // will already respond to the event. + if (keyCode != KeyEvent.KEYCODE_UNKNOWN) { + onUserInput(); + } + return false; + }; + + private final EmergencyButtonCallback mEmergencyButtonCallback = new EmergencyButtonCallback() { + @Override + public void onEmergencyButtonClickedWhenInCall() { + mKeyguardSecurityCallback.reset(); + } + }; + + protected KeyguardAbsKeyInputViewController(T view, + KeyguardUpdateMonitor keyguardUpdateMonitor, + SecurityMode securityMode, + LockPatternUtils lockPatternUtils, + KeyguardSecurityCallback keyguardSecurityCallback, + KeyguardMessageAreaController.Factory messageAreaControllerFactory, + LatencyTracker latencyTracker) { + super(view, securityMode, keyguardSecurityCallback); + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mLockPatternUtils = lockPatternUtils; + mKeyguardSecurityCallback = keyguardSecurityCallback; + mLatencyTracker = latencyTracker; + KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView); + mMessageAreaController = messageAreaControllerFactory.create(kma); + } + + abstract void resetState(); + + @Override + protected void onViewAttached() { + mView.setKeyDownListener(mKeyDownListener); + mView.setEnableHaptics(mLockPatternUtils.isTactileFeedbackEnabled()); + EmergencyButton button = mView.findViewById(R.id.emergency_call_button); + if (button != null) { + button.setCallback(mEmergencyButtonCallback); + } + } + + @Override + public void reset() { + // start fresh + mDismissing = false; + mView.resetPasswordText(false /* animate */, false /* announce */); + // if the user is currently locked out, enforce it. + long deadline = mLockPatternUtils.getLockoutAttemptDeadline( + KeyguardUpdateMonitor.getCurrentUser()); + if (shouldLockout(deadline)) { + handleAttemptLockout(deadline); + } else { + resetState(); + } + } + + // Allow subclasses to override this behavior + protected boolean shouldLockout(long deadline) { + return deadline != 0; + } + + // Prevent user from using the PIN/Password entry until scheduled deadline. + protected void handleAttemptLockout(long elapsedRealtimeDeadline) { + mView.setPasswordEntryEnabled(false); + long elapsedRealtime = SystemClock.elapsedRealtime(); + long secondsInFuture = (long) Math.ceil( + (elapsedRealtimeDeadline - elapsedRealtime) / 1000.0); + mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) { + + @Override + public void onTick(long millisUntilFinished) { + int secondsRemaining = (int) Math.round(millisUntilFinished / 1000.0); + mMessageAreaController.setMessage(mView.getResources().getQuantityString( + R.plurals.kg_too_many_failed_attempts_countdown, + secondsRemaining, secondsRemaining)); + } + + @Override + public void onFinish() { + mMessageAreaController.setMessage(""); + resetState(); + } + }.start(); + } + + void onPasswordChecked(int userId, boolean matched, int timeoutMs, boolean isValidPassword) { + boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId; + if (matched) { + mKeyguardSecurityCallback.reportUnlockAttempt(userId, true, 0); + if (dismissKeyguard) { + mDismissing = true; + mKeyguardSecurityCallback.dismiss(true, userId); + } + } else { + if (isValidPassword) { + mKeyguardSecurityCallback.reportUnlockAttempt(userId, false, timeoutMs); + if (timeoutMs > 0) { + long deadline = mLockPatternUtils.setLockoutAttemptDeadline( + userId, timeoutMs); + handleAttemptLockout(deadline); + } + } + if (timeoutMs == 0) { + mMessageAreaController.setMessage(mView.getWrongPasswordStringId()); + } + } + mView.resetPasswordText(true /* animate */, !matched /* announce deletion if no match */); + } + + protected void verifyPasswordAndUnlock() { + if (mDismissing) return; // already verified but haven't been dismissed; don't do it again. + + final LockscreenCredential password = mView.getEnteredCredential(); + mView.setPasswordEntryInputEnabled(false); + if (mPendingLockCheck != null) { + mPendingLockCheck.cancel(false); + } + + final int userId = KeyguardUpdateMonitor.getCurrentUser(); + if (password.size() <= MINIMUM_PASSWORD_LENGTH_BEFORE_REPORT) { + // to avoid accidental lockout, only count attempts that are long enough to be a + // real password. This may require some tweaking. + mView.setPasswordEntryInputEnabled(true); + onPasswordChecked(userId, false /* matched */, 0, false /* not valid - too short */); + password.zeroize(); + return; + } + + mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL); + mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED); + + mKeyguardUpdateMonitor.setCredentialAttempted(); + mPendingLockCheck = LockPatternChecker.checkCredential( + mLockPatternUtils, + password, + userId, + new LockPatternChecker.OnCheckCallback() { + + @Override + public void onEarlyMatched() { + mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL); + + onPasswordChecked(userId, true /* matched */, 0 /* timeoutMs */, + true /* isValidPassword */); + password.zeroize(); + } + + @Override + public void onChecked(boolean matched, int timeoutMs) { + mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED); + mView.setPasswordEntryInputEnabled(true); + mPendingLockCheck = null; + if (!matched) { + onPasswordChecked(userId, false /* matched */, timeoutMs, + true /* isValidPassword */); + } + password.zeroize(); + } + + @Override + public void onCancelled() { + // We already got dismissed with the early matched callback, so we cancelled + // the check. However, we still need to note down the latency. + mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED); + password.zeroize(); + } + }); + } + + @Override + public void showPromptReason(int reason) { + if (reason != PROMPT_REASON_NONE) { + int promtReasonStringRes = mView.getPromptReasonStringRes(reason); + if (promtReasonStringRes != 0) { + mMessageAreaController.setMessage(promtReasonStringRes); + } + } + } + + protected void onUserInput() { + mKeyguardSecurityCallback.userActivity(); + mKeyguardSecurityCallback.onUserInput(); + mMessageAreaController.setMessage(""); + } + + @Override + public void onResume(int reason) { + mResumed = true; + } + + @Override + public void onPause() { + mResumed = false; + + if (mCountdownTimer != null) { + mCountdownTimer.cancel(); + mCountdownTimer = null; + } + if (mPendingLockCheck != null) { + mPendingLockCheck.cancel(false); + mPendingLockCheck = null; + } + reset(); + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java index ad6ca8582bf08..9ffa658da0e89 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java @@ -178,18 +178,18 @@ public class KeyguardHostViewController extends ViewController /** Initialize the Controller. */ public void init() { super.init(); - mView.setViewMediatorCallback(mViewMediatorCallback); - // Update ViewMediator with the current input method requirements - mViewMediatorCallback.setNeedsInput(mKeyguardSecurityContainerController.needsInput()); mKeyguardSecurityContainerController.init(); - mKeyguardSecurityContainerController.setSecurityCallback(mSecurityCallback); - mKeyguardSecurityContainerController.showPrimarySecurityScreen(false); } @Override protected void onViewAttached() { + mView.setViewMediatorCallback(mViewMediatorCallback); + // Update ViewMediator with the current input method requirements + mViewMediatorCallback.setNeedsInput(mKeyguardSecurityContainerController.needsInput()); mKeyguardUpdateMonitor.registerCallback(mUpdateCallback); mView.setOnKeyListener(mOnKeyListener); + mKeyguardSecurityContainerController.setSecurityCallback(mSecurityCallback); + mKeyguardSecurityContainerController.showPrimarySecurityScreen(false); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index 552e62052e6f0..c73149c7a8b97 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -17,32 +17,31 @@ package com.android.keyguard; import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.telephony.TelephonyManager; import android.view.MotionEvent; +import android.view.inputmethod.InputMethodManager; import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.util.ViewController; +import com.android.systemui.util.concurrency.DelayableExecutor; import javax.inject.Inject; /** Controller for a {@link KeyguardSecurityView}. */ -public class KeyguardInputViewController extends ViewController - implements KeyguardSecurityView { +public abstract class KeyguardInputViewController + extends ViewController implements KeyguardSecurityView { private final SecurityMode mSecurityMode; - private final LockPatternUtils mLockPatternUtils; - private final KeyguardSecurityCallback mKeyguardSecurityCallback; - private KeyguardMessageAreaController mMessageAreaController; protected KeyguardInputViewController(T view, SecurityMode securityMode, - LockPatternUtils lockPatternUtils, KeyguardSecurityCallback keyguardSecurityCallback) { super(view); mSecurityMode = securityMode; - mLockPatternUtils = lockPatternUtils; - mKeyguardSecurityCallback = keyguardSecurityCallback; mView.setKeyguardCallback(keyguardSecurityCallback); } @@ -151,16 +150,29 @@ public class KeyguardInputViewController extends Vi private final LockPatternUtils mLockPatternUtils; private final LatencyTracker mLatencyTracker; private final KeyguardMessageAreaController.Factory mMessageAreaControllerFactory; + private final InputMethodManager mInputMethodManager; + private final DelayableExecutor mMainExecutor; + private final Resources mResources; + private LiftToActivateListener mLiftToActivateListener; + private TelephonyManager mTelephonyManager; @Inject public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils, LatencyTracker latencyTracker, - KeyguardMessageAreaController.Factory messageAreaControllerFactory) { + KeyguardMessageAreaController.Factory messageAreaControllerFactory, + InputMethodManager inputMethodManager, @Main DelayableExecutor mainExecutor, + @Main Resources resources, LiftToActivateListener liftToActivateListener, + TelephonyManager telephonyManager) { mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; mLatencyTracker = latencyTracker; mMessageAreaControllerFactory = messageAreaControllerFactory; + mInputMethodManager = inputMethodManager; + mMainExecutor = mainExecutor; + mResources = resources; + mLiftToActivateListener = liftToActivateListener; + mTelephonyManager = telephonyManager; } /** Create a new {@link KeyguardInputViewController}. */ @@ -170,9 +182,29 @@ public class KeyguardInputViewController extends Vi return new KeyguardPatternViewController((KeyguardPatternView) keyguardInputView, mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mLatencyTracker, mMessageAreaControllerFactory); + } else if (keyguardInputView instanceof KeyguardPasswordView) { + return new KeyguardPasswordViewController((KeyguardPasswordView) keyguardInputView, + mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, + keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, + mInputMethodManager, mMainExecutor, mResources); + } else if (keyguardInputView instanceof KeyguardPINView) { + return new KeyguardPinViewController((KeyguardPINView) keyguardInputView, + mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, + keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, + mLiftToActivateListener); + } else if (keyguardInputView instanceof KeyguardSimPinView) { + return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView, + mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, + keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, + mLiftToActivateListener, mTelephonyManager); + } else if (keyguardInputView instanceof KeyguardSimPukView) { + return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView, + mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, + keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, + mLiftToActivateListener, mTelephonyManager); } - return new KeyguardInputViewController<>(keyguardInputView, securityMode, - mLockPatternUtils, keyguardSecurityCallback); + + throw new RuntimeException("Unable to find controller for " + keyguardInputView); } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java index 12ea1d586e103..bfa187ea099b5 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java @@ -24,7 +24,6 @@ import android.view.animation.AnimationUtils; import com.android.settingslib.animation.AppearAnimationUtils; import com.android.settingslib.animation.DisappearAnimationUtils; -import com.android.systemui.Dependency; import com.android.systemui.R; /** @@ -40,10 +39,8 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { private ViewGroup mRow1; private ViewGroup mRow2; private ViewGroup mRow3; - private View mDivider; private int mDisappearYTranslation; private View[][] mViews; - private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; public KeyguardPINView(Context context) { this(context, null); @@ -63,15 +60,10 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { mContext, android.R.interpolator.fast_out_linear_in)); mDisappearYTranslation = getResources().getDimensionPixelSize( R.dimen.disappear_y_translation); - mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class); } @Override protected void resetState() { - super.resetState(); - if (mSecurityMessageDisplay != null) { - mSecurityMessageDisplay.setMessage(""); - } } @Override @@ -88,7 +80,6 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { mRow1 = findViewById(R.id.row1); mRow2 = findViewById(R.id.row2); mRow3 = findViewById(R.id.row3); - mDivider = findViewById(R.id.divider); mViews = new View[][]{ new View[]{ mRow0, null, null @@ -122,6 +113,16 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { } } + @Override + public void onPause() { + + } + + @Override + public void onResume(int reason) { + + } + @Override public void showUsabilityHint() { } @@ -149,22 +150,24 @@ public class KeyguardPINView extends KeyguardPinBasedInputView { @Override public boolean startDisappearAnimation(final Runnable finishRunnable) { + return false; + } + + public boolean startDisappearAnimation(boolean needsSlowUnlockTransition, + final Runnable finishRunnable) { + enableClipping(false); setTranslationY(0); AppearAnimationUtils.startTranslationYAnimation(this, 0 /* delay */, 280 /* duration */, mDisappearYTranslation, mDisappearAnimationUtils.getInterpolator()); - DisappearAnimationUtils disappearAnimationUtils = mKeyguardUpdateMonitor - .needsSlowUnlockTransition() + DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition ? mDisappearAnimationUtilsLocked : mDisappearAnimationUtils; disappearAnimationUtils.startAnimation2d(mViews, - new Runnable() { - @Override - public void run() { - enableClipping(true); - if (finishRunnable != null) { - finishRunnable.run(); - } + () -> { + enableClipping(true); + if (finishRunnable != null) { + finishRunnable.run(); } }); return true; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java index 97317cf5580f7..3b865922503aa 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java @@ -18,48 +18,29 @@ package com.android.keyguard; import android.content.Context; import android.graphics.Rect; -import android.os.UserHandle; -import android.text.Editable; -import android.text.InputType; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.text.method.TextKeyListener; import android.util.AttributeSet; -import android.view.KeyEvent; -import android.view.View; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodInfo; -import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.InputMethodSubtype; import android.widget.TextView; -import android.widget.TextView.OnEditorActionListener; import com.android.internal.widget.LockscreenCredential; import com.android.internal.widget.TextViewInputDisabler; import com.android.systemui.R; - -import java.util.List; /** * Displays an alphanumeric (latin-1) key entry for the user to enter * an unlock password */ public class KeyguardPasswordView extends KeyguardAbsKeyInputView - implements KeyguardSecurityView, OnEditorActionListener, TextWatcher { + implements KeyguardSecurityView { - private final boolean mShowImeAtScreenOn; private final int mDisappearYTranslation; // A delay constant to be used in a workaround for the situation where InputMethodManagerService // is not switched to the new user yet. // TODO: Remove this by ensuring such a race condition never happens. - private static final int DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON = 500; // 500ms - InputMethodManager mImm; private TextView mPasswordEntry; private TextViewInputDisabler mPasswordEntryDisabler; - private View mSwitchImeButton; private Interpolator mLinearOutSlowInInterpolator; private Interpolator mFastOutLinearInInterpolator; @@ -70,8 +51,6 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView public KeyguardPasswordView(Context context, AttributeSet attrs) { super(context, attrs); - mShowImeAtScreenOn = context.getResources(). - getBoolean(R.bool.kg_show_ime_at_screen_on); mDisappearYTranslation = getResources().getDimensionPixelSize( R.dimen.disappear_y_translation); mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator( @@ -82,20 +61,6 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView @Override protected void resetState() { - mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser())); - if (mSecurityMessageDisplay != null) { - mSecurityMessageDisplay.setMessage(""); - } - final boolean wasDisabled = mPasswordEntry.isEnabled(); - setPasswordEntryEnabled(true); - setPasswordEntryInputEnabled(true); - // Don't call showSoftInput when PasswordEntry is invisible or in pausing stage. - if (!mResumed || !mPasswordEntry.isVisibleToUser()) { - return; - } - if (wasDisabled) { - mImm.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT); - } } @Override @@ -103,28 +68,20 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView return R.id.passwordEntry; } + @Override + public void onPause() { + } + + @Override + public void onResume(int reason) { + + } + @Override public boolean needsInput() { return true; } - @Override - public void onResume(final int reason) { - super.onResume(reason); - - // Wait a bit to focus the field so the focusable flag on the window is already set then. - post(new Runnable() { - @Override - public void run() { - if (isShown() && mPasswordEntry.isEnabled()) { - mPasswordEntry.requestFocus(); - if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) { - mImm.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT); - } - } - } - }); - } @Override protected int getPromptReasonStringRes(int reason) { @@ -146,97 +103,13 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView } } - @Override - public void onPause() { - super.onPause(); - mImm.hideSoftInputFromWindow(getWindowToken(), 0); - } - - @Override - public void onStartingToHide() { - mImm.hideSoftInputFromWindow(getWindowToken(), 0); - } - - private void updateSwitchImeButton() { - // If there's more than one IME, enable the IME switcher button - final boolean wasVisible = mSwitchImeButton.getVisibility() == View.VISIBLE; - final boolean shouldBeVisible = hasMultipleEnabledIMEsOrSubtypes(mImm, false); - if (wasVisible != shouldBeVisible) { - mSwitchImeButton.setVisibility(shouldBeVisible ? View.VISIBLE : View.GONE); - } - - // TODO: Check if we still need this hack. - // If no icon is visible, reset the start margin on the password field so the text is - // still centered. - if (mSwitchImeButton.getVisibility() != View.VISIBLE) { - android.view.ViewGroup.LayoutParams params = mPasswordEntry.getLayoutParams(); - if (params instanceof MarginLayoutParams) { - final MarginLayoutParams mlp = (MarginLayoutParams) params; - mlp.setMarginStart(0); - mPasswordEntry.setLayoutParams(params); - } - } - } @Override protected void onFinishInflate() { super.onFinishInflate(); - mImm = (InputMethodManager) getContext().getSystemService( - Context.INPUT_METHOD_SERVICE); - mPasswordEntry = findViewById(getPasswordTextViewId()); - mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser())); mPasswordEntryDisabler = new TextViewInputDisabler(mPasswordEntry); - mPasswordEntry.setKeyListener(TextKeyListener.getInstance()); - mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT - | InputType.TYPE_TEXT_VARIATION_PASSWORD); - mPasswordEntry.setOnEditorActionListener(this); - mPasswordEntry.addTextChangedListener(this); - - // Poke the wakelock any time the text is selected or modified - mPasswordEntry.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - mCallback.userActivity(); - } - }); - - // Set selected property on so the view can send accessibility events. - mPasswordEntry.setSelected(true); - - mSwitchImeButton = findViewById(R.id.switch_ime_button); - mSwitchImeButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - mCallback.userActivity(); // Leave the screen on a bit longer - // Do not show auxiliary subtypes in password lock screen. - mImm.showInputMethodPickerFromSystem(false /* showAuxiliarySubtypes */, - getContext().getDisplayId()); - } - }); - - View cancelBtn = findViewById(R.id.cancel_button); - if (cancelBtn != null) { - cancelBtn.setOnClickListener(view -> { - mCallback.reset(); - mCallback.onCancelClicked(); - }); - } - - // If there's more than one IME, enable the IME switcher button - updateSwitchImeButton(); - - // When we the current user is switching, InputMethodManagerService sometimes has not - // switched internal state yet here. As a quick workaround, we check the keyboard state - // again. - // TODO: Remove this workaround by ensuring such a race condition never happens. - postDelayed(new Runnable() { - @Override - public void run() { - updateSwitchImeButton(); - } - }, DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON); } @Override @@ -265,55 +138,6 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView mPasswordEntryDisabler.setInputEnabled(enabled); } - /** - * Method adapted from com.android.inputmethod.latin.Utils - * - * @param imm The input method manager - * @param shouldIncludeAuxiliarySubtypes - * @return true if we have multiple IMEs to choose from - */ - private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm, - final boolean shouldIncludeAuxiliarySubtypes) { - final List enabledImis = - imm.getEnabledInputMethodListAsUser(KeyguardUpdateMonitor.getCurrentUser()); - - // Number of the filtered IMEs - int filteredImisCount = 0; - - for (InputMethodInfo imi : enabledImis) { - // We can return true immediately after we find two or more filtered IMEs. - if (filteredImisCount > 1) return true; - final List subtypes = - imm.getEnabledInputMethodSubtypeList(imi, true); - // IMEs that have no subtypes should be counted. - if (subtypes.isEmpty()) { - ++filteredImisCount; - continue; - } - - int auxCount = 0; - for (InputMethodSubtype subtype : subtypes) { - if (subtype.isAuxiliary()) { - ++auxCount; - } - } - final int nonAuxCount = subtypes.size() - auxCount; - - // IMEs that have one or more non-auxiliary subtypes should be counted. - // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary - // subtypes should be counted as well. - if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) { - ++filteredImisCount; - continue; - } - } - - return filteredImisCount > 1 - // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's enabled - // input method subtype (The current IME should be LatinIME.) - || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1; - } - @Override public void showUsabilityHint() { } @@ -345,46 +169,9 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView return true; } - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - if (mCallback != null) { - mCallback.userActivity(); - } - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - } - - @Override - public void afterTextChanged(Editable s) { - // Poor man's user edit detection, assuming empty text is programmatic and everything else - // is from the user. - if (!TextUtils.isEmpty(s)) { - onUserInput(); - } - } - - @Override - public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - // Check if this was the result of hitting the enter key - final boolean isSoftImeEvent = event == null - && (actionId == EditorInfo.IME_NULL - || actionId == EditorInfo.IME_ACTION_DONE - || actionId == EditorInfo.IME_ACTION_NEXT); - final boolean isKeyboardEnterKey = event != null - && KeyEvent.isConfirmKey(event.getKeyCode()) - && event.getAction() == KeyEvent.ACTION_DOWN; - if (isSoftImeEvent || isKeyboardEnterKey) { - verifyPasswordAndUnlock(); - return true; - } - return false; - } - @Override public CharSequence getTitle() { - return getContext().getString( + return getResources().getString( com.android.internal.R.string.keyguard_accessibility_password_unlock); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java new file mode 100644 index 0000000000000..52afdcd1cacb2 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java @@ -0,0 +1,270 @@ +/* + * 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.keyguard; + +import android.content.res.Resources; +import android.os.UserHandle; +import android.text.Editable; +import android.text.InputType; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.text.method.TextKeyListener; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup.MarginLayoutParams; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodInfo; +import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.InputMethodSubtype; +import android.widget.TextView; +import android.widget.TextView.OnEditorActionListener; + +import com.android.internal.util.LatencyTracker; +import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.util.concurrency.DelayableExecutor; + +import java.util.List; + +public class KeyguardPasswordViewController + extends KeyguardAbsKeyInputViewController { + + private static final int DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON = 500; // 500ms + + private final KeyguardSecurityCallback mKeyguardSecurityCallback; + private final InputMethodManager mInputMethodManager; + private final DelayableExecutor mMainExecutor; + private final boolean mShowImeAtScreenOn; + private TextView mPasswordEntry; + private View mSwitchImeButton; + + private final OnEditorActionListener mOnEditorActionListener = (v, actionId, event) -> { + // Check if this was the result of hitting the enter key + final boolean isSoftImeEvent = event == null + && (actionId == EditorInfo.IME_NULL + || actionId == EditorInfo.IME_ACTION_DONE + || actionId == EditorInfo.IME_ACTION_NEXT); + final boolean isKeyboardEnterKey = event != null + && KeyEvent.isConfirmKey(event.getKeyCode()) + && event.getAction() == KeyEvent.ACTION_DOWN; + if (isSoftImeEvent || isKeyboardEnterKey) { + verifyPasswordAndUnlock(); + return true; + } + return false; + }; + + private final TextWatcher mTextWatcher = new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + mKeyguardSecurityCallback.userActivity(); + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable s) { + if (!TextUtils.isEmpty(s)) { + onUserInput(); + } + } + }; + + protected KeyguardPasswordViewController(KeyguardPasswordView view, + KeyguardUpdateMonitor keyguardUpdateMonitor, + SecurityMode securityMode, + LockPatternUtils lockPatternUtils, + KeyguardSecurityCallback keyguardSecurityCallback, + KeyguardMessageAreaController.Factory messageAreaControllerFactory, + LatencyTracker latencyTracker, + InputMethodManager inputMethodManager, + @Main DelayableExecutor mainExecutor, + @Main Resources resources) { + super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, + messageAreaControllerFactory, latencyTracker); + mKeyguardSecurityCallback = keyguardSecurityCallback; + mInputMethodManager = inputMethodManager; + mMainExecutor = mainExecutor; + mShowImeAtScreenOn = resources.getBoolean(R.bool.kg_show_ime_at_screen_on); + mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId()); + mSwitchImeButton = mView.findViewById(R.id.switch_ime_button); + } + + @Override + protected void onViewAttached() { + super.onViewAttached(); + mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser())); + mPasswordEntry.setKeyListener(TextKeyListener.getInstance()); + mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT + | InputType.TYPE_TEXT_VARIATION_PASSWORD); + + // Set selected property on so the view can send accessibility events. + mPasswordEntry.setSelected(true); + mPasswordEntry.setOnEditorActionListener(mOnEditorActionListener); + mPasswordEntry.addTextChangedListener(mTextWatcher); + // Poke the wakelock any time the text is selected or modified + mPasswordEntry.setOnClickListener(v -> mKeyguardSecurityCallback.userActivity()); + + mSwitchImeButton.setOnClickListener(v -> { + mKeyguardSecurityCallback.userActivity(); // Leave the screen on a bit longer + // Do not show auxiliary subtypes in password lock screen. + mInputMethodManager.showInputMethodPickerFromSystem(false, + mView.getContext().getDisplayId()); + }); + + View cancelBtn = mView.findViewById(R.id.cancel_button); + if (cancelBtn != null) { + cancelBtn.setOnClickListener(view -> { + mKeyguardSecurityCallback.reset(); + mKeyguardSecurityCallback.onCancelClicked(); + }); + } + + // If there's more than one IME, enable the IME switcher button + updateSwitchImeButton(); + + // When we the current user is switching, InputMethodManagerService sometimes has not + // switched internal state yet here. As a quick workaround, we check the keyboard state + // again. + // TODO: Remove this workaround by ensuring such a race condition never happens. + mMainExecutor.executeDelayed( + this::updateSwitchImeButton, DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON); + } + + @Override + protected void onViewDetached() { + super.onViewDetached(); + mPasswordEntry.setOnEditorActionListener(null); + } + + @Override + void resetState() { + mPasswordEntry.setTextOperationUser(UserHandle.of(KeyguardUpdateMonitor.getCurrentUser())); + mMessageAreaController.setMessage(""); + final boolean wasDisabled = mPasswordEntry.isEnabled(); + mView.setPasswordEntryEnabled(true); + mView.setPasswordEntryInputEnabled(true); + // Don't call showSoftInput when PasswordEntry is invisible or in pausing stage. + if (!mResumed || !mPasswordEntry.isVisibleToUser()) { + return; + } + if (wasDisabled) { + mInputMethodManager.showSoftInput(mPasswordEntry, InputMethodManager.SHOW_IMPLICIT); + } + } + + @Override + public void onResume(int reason) { + super.onResume(reason); + // Wait a bit to focus the field so the focusable flag on the window is already set then. + mMainExecutor.execute(() -> { + if (mView.isShown() && mPasswordEntry.isEnabled()) { + mPasswordEntry.requestFocus(); + if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) { + mInputMethodManager.showSoftInput( + mPasswordEntry, InputMethodManager.SHOW_IMPLICIT); + } + } + }); + } + + @Override + public void onPause() { + super.onPause(); + mInputMethodManager.hideSoftInputFromWindow(mView.getWindowToken(), 0); + } + + @Override + public void onStartingToHide() { + mInputMethodManager.hideSoftInputFromWindow(mView.getWindowToken(), 0); + } + + private void updateSwitchImeButton() { + // If there's more than one IME, enable the IME switcher button + final boolean wasVisible = mSwitchImeButton.getVisibility() == View.VISIBLE; + final boolean shouldBeVisible = hasMultipleEnabledIMEsOrSubtypes( + mInputMethodManager, false); + if (wasVisible != shouldBeVisible) { + mSwitchImeButton.setVisibility(shouldBeVisible ? View.VISIBLE : View.GONE); + } + + // TODO: Check if we still need this hack. + // If no icon is visible, reset the start margin on the password field so the text is + // still centered. + if (mSwitchImeButton.getVisibility() != View.VISIBLE) { + android.view.ViewGroup.LayoutParams params = mPasswordEntry.getLayoutParams(); + if (params instanceof MarginLayoutParams) { + final MarginLayoutParams mlp = (MarginLayoutParams) params; + mlp.setMarginStart(0); + mPasswordEntry.setLayoutParams(params); + } + } + } + + /** + * Method adapted from com.android.inputmethod.latin.Utils + * + * @param imm The input method manager + * @param shouldIncludeAuxiliarySubtypes + * @return true if we have multiple IMEs to choose from + */ + private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm, + final boolean shouldIncludeAuxiliarySubtypes) { + final List enabledImis = + imm.getEnabledInputMethodListAsUser(KeyguardUpdateMonitor.getCurrentUser()); + + // Number of the filtered IMEs + int filteredImisCount = 0; + + for (InputMethodInfo imi : enabledImis) { + // We can return true immediately after we find two or more filtered IMEs. + if (filteredImisCount > 1) return true; + final List subtypes = + imm.getEnabledInputMethodSubtypeList(imi, true); + // IMEs that have no subtypes should be counted. + if (subtypes.isEmpty()) { + ++filteredImisCount; + continue; + } + + int auxCount = 0; + for (InputMethodSubtype subtype : subtypes) { + if (subtype.isAuxiliary()) { + ++auxCount; + } + } + final int nonAuxCount = subtypes.size() - auxCount; + + // IMEs that have one or more non-auxiliary subtypes should be counted. + // If shouldIncludeAuxiliarySubtypes is true, IMEs that have two or more auxiliary + // subtypes should be counted as well. + if (nonAuxCount > 0 || (shouldIncludeAuxiliarySubtypes && auxCount > 1)) { + ++filteredImisCount; + continue; + } + } + + return filteredImisCount > 1 + // imm.getEnabledInputMethodSubtypeList(null, false) will return the current IME's + //enabled input method subtype (The current IME should be LatinIME.) + || imm.getEnabledInputMethodSubtypeList(null, false).size() > 1; + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java index 5e547d3120bfe..13a16360be040 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java @@ -180,18 +180,18 @@ public class KeyguardPatternViewController KeyguardSecurityCallback keyguardSecurityCallback, LatencyTracker latencyTracker, KeyguardMessageAreaController.Factory messageAreaControllerFactory) { - super(view, securityMode, lockPatternUtils, keyguardSecurityCallback); + super(view, securityMode, keyguardSecurityCallback); mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; mLatencyTracker = latencyTracker; mMessageAreaControllerFactory = messageAreaControllerFactory; + KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView); + mMessageAreaController = mMessageAreaControllerFactory.create(kma); + mLockPatternView = mView.findViewById(R.id.lockPatternView); } @Override protected void onViewAttached() { - KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView); - mMessageAreaController = mMessageAreaControllerFactory.create(kma); - mLockPatternView = mView.findViewById(R.id.lockPatternView); mLockPatternView.setOnPatternListener(new UnlockPatternListener()); mLockPatternView.setSaveEnabled(false); mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled( diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java index c7f27cf8a71a9..e579380fc8d59 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java @@ -20,7 +20,6 @@ import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; import android.view.KeyEvent; -import android.view.MotionEvent; import android.view.View; import com.android.internal.widget.LockscreenCredential; @@ -29,22 +28,12 @@ import com.android.systemui.R; /** * A Pin based Keyguard input view */ -public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView - implements View.OnKeyListener, View.OnTouchListener { +public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView { protected PasswordTextView mPasswordEntry; private View mOkButton; private View mDeleteButton; - private View mButton0; - private View mButton1; - private View mButton2; - private View mButton3; - private View mButton4; - private View mButton5; - private View mButton6; - private View mButton7; - private View mButton8; - private View mButton9; + private View[] mButtons = new View[10]; public KeyguardPinBasedInputView(Context context) { this(context, null); @@ -62,7 +51,6 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView @Override protected void resetState() { - setPasswordEntryEnabled(true); } @Override @@ -86,10 +74,10 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (KeyEvent.isConfirmKey(keyCode)) { - performClick(mOkButton); + mOkButton.performClick(); return true; } else if (keyCode == KeyEvent.KEYCODE_DEL) { - performClick(mDeleteButton); + mDeleteButton.performClick(); return true; } if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) { @@ -125,42 +113,9 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView } } - private void performClick(View view) { - view.performClick(); - } - private void performNumberClick(int number) { - switch (number) { - case 0: - performClick(mButton0); - break; - case 1: - performClick(mButton1); - break; - case 2: - performClick(mButton2); - break; - case 3: - performClick(mButton3); - break; - case 4: - performClick(mButton4); - break; - case 5: - performClick(mButton5); - break; - case 6: - performClick(mButton6); - break; - case 7: - performClick(mButton7); - break; - case 8: - performClick(mButton8); - break; - case 9: - performClick(mButton9); - break; + if (number >= 0 && number <= 9) { + mButtons[number].performClick(); } } @@ -177,93 +132,30 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView @Override protected void onFinishInflate() { mPasswordEntry = findViewById(getPasswordTextViewId()); - mPasswordEntry.setOnKeyListener(this); // Set selected property on so the view can send accessibility events. mPasswordEntry.setSelected(true); - mPasswordEntry.setUserActivityListener(new PasswordTextView.UserActivityListener() { - @Override - public void onUserActivity() { - onUserInput(); - } - }); - mOkButton = findViewById(R.id.key_enter); - if (mOkButton != null) { - mOkButton.setOnTouchListener(this); - mOkButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if (mPasswordEntry.isEnabled()) { - verifyPasswordAndUnlock(); - } - } - }); - mOkButton.setOnHoverListener(new LiftToActivateListener(getContext())); - } mDeleteButton = findViewById(R.id.delete_button); mDeleteButton.setVisibility(View.VISIBLE); - mDeleteButton.setOnTouchListener(this); - mDeleteButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - // check for time-based lockouts - if (mPasswordEntry.isEnabled()) { - mPasswordEntry.deleteLastChar(); - } - } - }); - mDeleteButton.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - // check for time-based lockouts - if (mPasswordEntry.isEnabled()) { - resetPasswordText(true /* animate */, true /* announce */); - } - doHapticKeyClick(); - return true; - } - }); - mButton0 = findViewById(R.id.key0); - mButton1 = findViewById(R.id.key1); - mButton2 = findViewById(R.id.key2); - mButton3 = findViewById(R.id.key3); - mButton4 = findViewById(R.id.key4); - mButton5 = findViewById(R.id.key5); - mButton6 = findViewById(R.id.key6); - mButton7 = findViewById(R.id.key7); - mButton8 = findViewById(R.id.key8); - mButton9 = findViewById(R.id.key9); + mButtons[0] = findViewById(R.id.key0); + mButtons[1] = findViewById(R.id.key1); + mButtons[2] = findViewById(R.id.key2); + mButtons[3] = findViewById(R.id.key3); + mButtons[4] = findViewById(R.id.key4); + mButtons[5] = findViewById(R.id.key5); + mButtons[6] = findViewById(R.id.key6); + mButtons[7] = findViewById(R.id.key7); + mButtons[8] = findViewById(R.id.key8); + mButtons[9] = findViewById(R.id.key9); mPasswordEntry.requestFocus(); super.onFinishInflate(); } - @Override - public void onResume(int reason) { - super.onResume(reason); - mPasswordEntry.requestFocus(); - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - doHapticKeyClick(); - } - return false; - } - - @Override - public boolean onKey(View v, int keyCode, KeyEvent event) { - if (event.getAction() == KeyEvent.ACTION_DOWN) { - return onKeyDown(keyCode, event); - } - return false; - } - @Override public CharSequence getTitle() { return getContext().getString( diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java new file mode 100644 index 0000000000000..4d0ebfffbe04f --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java @@ -0,0 +1,113 @@ +/* + * 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.keyguard; + +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnKeyListener; +import android.view.View.OnTouchListener; + +import com.android.internal.util.LatencyTracker; +import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.R; + +public abstract class KeyguardPinBasedInputViewController + extends KeyguardAbsKeyInputViewController { + + private final LiftToActivateListener mLiftToActivateListener; + protected PasswordTextView mPasswordEntry; + + private final OnKeyListener mOnKeyListener = (v, keyCode, event) -> { + if (event.getAction() == KeyEvent.ACTION_DOWN) { + return mView.onKeyDown(keyCode, event); + } + return false; + }; + + private final OnTouchListener mOnTouchListener = (v, event) -> { + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + mView.doHapticKeyClick(); + } + return false; + }; + + protected KeyguardPinBasedInputViewController(T view, + KeyguardUpdateMonitor keyguardUpdateMonitor, + SecurityMode securityMode, + LockPatternUtils lockPatternUtils, + KeyguardSecurityCallback keyguardSecurityCallback, + KeyguardMessageAreaController.Factory messageAreaControllerFactory, + LatencyTracker latencyTracker, + LiftToActivateListener liftToActivateListener) { + super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, + messageAreaControllerFactory, latencyTracker); + mLiftToActivateListener = liftToActivateListener; + mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId()); + } + + @Override + protected void onViewAttached() { + super.onViewAttached(); + + mPasswordEntry.setOnKeyListener(mOnKeyListener); + mPasswordEntry.setUserActivityListener(this::onUserInput); + + View deleteButton = mView.findViewById(R.id.delete_button); + deleteButton.setOnTouchListener(mOnTouchListener); + deleteButton.setOnClickListener(v -> { + // check for time-based lockouts + if (mPasswordEntry.isEnabled()) { + mPasswordEntry.deleteLastChar(); + } + }); + deleteButton.setOnLongClickListener(v -> { + // check for time-based lockouts + if (mPasswordEntry.isEnabled()) { + mView.resetPasswordText(true /* animate */, true /* announce */); + } + mView.doHapticKeyClick(); + return true; + }); + + View okButton = mView.findViewById(R.id.key_enter); + if (okButton != null) { + okButton.setOnTouchListener(mOnTouchListener); + okButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mPasswordEntry.isEnabled()) { + verifyPasswordAndUnlock(); + } + } + }); + okButton.setOnHoverListener(mLiftToActivateListener); + } + } + + @Override + public void onResume(int reason) { + super.onResume(reason); + mPasswordEntry.requestFocus(); + } + + @Override + void resetState() { + mView.setPasswordEntryEnabled(true); + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java new file mode 100644 index 0000000000000..625ab5214da15 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java @@ -0,0 +1,50 @@ +/* + * 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.keyguard; + +import com.android.internal.util.LatencyTracker; +import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; + +public class KeyguardPinViewController + extends KeyguardPinBasedInputViewController { + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + + protected KeyguardPinViewController(KeyguardPINView view, + KeyguardUpdateMonitor keyguardUpdateMonitor, + SecurityMode securityMode, LockPatternUtils lockPatternUtils, + KeyguardSecurityCallback keyguardSecurityCallback, + KeyguardMessageAreaController.Factory messageAreaControllerFactory, + LatencyTracker latencyTracker, + LiftToActivateListener liftToActivateListener) { + super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, + messageAreaControllerFactory, latencyTracker, liftToActivateListener); + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + } + + @Override + void resetState() { + super.resetState(); + mMessageAreaController.setMessage(""); + } + + @Override + public boolean startDisappearAnimation(Runnable finishRunnable) { + return mView.startDisappearAnimation( + mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable); + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 342f3de2ede31..2c7dce6e43021 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -197,6 +197,7 @@ public class KeyguardSecurityContainerController extends ViewController= 0) { - return; - } - - // Sending empty PIN here to query the number of remaining PIN attempts - new CheckSimPin("", mSubId) { - void onSimCheckResponse(final PinResult result) { - Log.d(LOG_TAG, "onSimCheckResponse " + " empty One result " - + result.toString()); - if (result.getAttemptsRemaining() >= 0) { - mRemainingAttempts = result.getAttemptsRemaining(); - setLockedSimMessage(); - } - } - }.start(); - } - - private void handleSubInfoChangeIfNeeded() { - KeyguardUpdateMonitor monitor = Dependency.get(KeyguardUpdateMonitor.class); - int subId = monitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PIN_REQUIRED); - if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) { - mSubId = subId; - mShowDefaultMessage = true; - mRemainingAttempts = -1; - } + esimButton.setVisibility(locked ? View.VISIBLE : View.GONE); } @Override @@ -173,35 +54,6 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { return 0; } - private String getPinPasswordErrorMessage(int attemptsRemaining, boolean isDefault) { - String displayMessage; - int msgId; - if (attemptsRemaining == 0) { - displayMessage = getContext().getString(R.string.kg_password_wrong_pin_code_pukked); - } else if (attemptsRemaining > 0) { - msgId = isDefault ? R.plurals.kg_password_default_pin_message : - R.plurals.kg_password_wrong_pin_code; - displayMessage = getContext().getResources() - .getQuantityString(msgId, attemptsRemaining, attemptsRemaining); - } else { - msgId = isDefault ? R.string.kg_sim_pin_instructions : R.string.kg_password_pin_failed; - displayMessage = getContext().getString(msgId); - } - if (KeyguardEsimArea.isEsimLocked(mContext, mSubId)) { - displayMessage = getResources() - .getString(R.string.kg_sim_lock_esim_instructions, displayMessage); - } - if (DEBUG) Log.d(LOG_TAG, "getPinPasswordErrorMessage:" - + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage); - return displayMessage; - } - - @Override - protected boolean shouldLockout(long deadline) { - // SIM PIN doesn't have a timed lockout - return false; - } - @Override protected int getPasswordTextViewId() { return R.id.simPinEntry; @@ -214,173 +66,21 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView { if (mEcaView instanceof EmergencyCarrierArea) { ((EmergencyCarrierArea) mEcaView).setCarrierTextVisible(true); } - mSimImageView = findViewById(R.id.keyguard_sim); } @Override - public void showUsabilityHint() { + public void onPause() { } @Override public void onResume(int reason) { - super.onResume(reason); - Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mUpdateMonitorCallback); - resetState(); + } @Override - public void onPause() { - // dismiss the dialog. - if (mSimUnlockProgressDialog != null) { - mSimUnlockProgressDialog.dismiss(); - mSimUnlockProgressDialog = null; - } - Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mUpdateMonitorCallback); - } + public void showUsabilityHint() { - /** - * Since the IPC can block, we want to run the request in a separate thread - * with a callback. - */ - private abstract class CheckSimPin extends Thread { - private final String mPin; - private int mSubId; - - protected CheckSimPin(String pin, int subId) { - mPin = pin; - mSubId = subId; - } - - abstract void onSimCheckResponse(@NonNull PinResult result); - - @Override - public void run() { - if (DEBUG) { - Log.v(TAG, "call supplyPinReportResultForSubscriber(subid=" + mSubId + ")"); - } - TelephonyManager telephonyManager = - ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE)) - .createForSubscriptionId(mSubId); - final PinResult result = telephonyManager.supplyPinReportPinResult(mPin); - if (result == null) { - Log.e(TAG, "Error result for supplyPinReportResult."); - post(new Runnable() { - @Override - public void run() { - onSimCheckResponse(PinResult.getDefaultFailedResult()); - } - }); - } else { - if (DEBUG) { - Log.v(TAG, "supplyPinReportResult returned: " + result.toString()); - } - post(new Runnable() { - @Override - public void run() { - onSimCheckResponse(result); - } - }); - } - } - } - - private Dialog getSimUnlockProgressDialog() { - if (mSimUnlockProgressDialog == null) { - mSimUnlockProgressDialog = new ProgressDialog(mContext); - mSimUnlockProgressDialog.setMessage( - mContext.getString(R.string.kg_sim_unlock_progress_dialog_message)); - mSimUnlockProgressDialog.setIndeterminate(true); - mSimUnlockProgressDialog.setCancelable(false); - mSimUnlockProgressDialog.getWindow().setType( - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - } - return mSimUnlockProgressDialog; - } - - private Dialog getSimRemainingAttemptsDialog(int remaining) { - String msg = getPinPasswordErrorMessage(remaining, false); - if (mRemainingAttemptsDialog == null) { - Builder builder = new AlertDialog.Builder(mContext); - builder.setMessage(msg); - builder.setCancelable(false); - builder.setNeutralButton(R.string.ok, null); - mRemainingAttemptsDialog = builder.create(); - mRemainingAttemptsDialog.getWindow().setType( - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - } else { - mRemainingAttemptsDialog.setMessage(msg); - } - return mRemainingAttemptsDialog; - } - - @Override - protected void verifyPasswordAndUnlock() { - String entry = mPasswordEntry.getText(); - - if (entry.length() < 4) { - // otherwise, display a message to the user, and don't submit. - mSecurityMessageDisplay.setMessage(R.string.kg_invalid_sim_pin_hint); - resetPasswordText(true /* animate */, true /* announce */); - mCallback.userActivity(); - return; - } - - getSimUnlockProgressDialog().show(); - - if (mCheckSimPinThread == null) { - mCheckSimPinThread = new CheckSimPin(mPasswordEntry.getText(), mSubId) { - @Override - void onSimCheckResponse(final PinResult result) { - post(new Runnable() { - @Override - public void run() { - mRemainingAttempts = result.getAttemptsRemaining(); - if (mSimUnlockProgressDialog != null) { - mSimUnlockProgressDialog.hide(); - } - resetPasswordText(true /* animate */, - /* announce */ - result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS); - if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) { - Dependency.get(KeyguardUpdateMonitor.class) - .reportSimUnlocked(mSubId); - mRemainingAttempts = -1; - mShowDefaultMessage = true; - if (mCallback != null) { - mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser()); - } - } else { - mShowDefaultMessage = false; - if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) { - if (result.getAttemptsRemaining() <= 2) { - // this is getting critical - show dialog - getSimRemainingAttemptsDialog( - result.getAttemptsRemaining()).show(); - } else { - // show message - mSecurityMessageDisplay.setMessage( - getPinPasswordErrorMessage( - result.getAttemptsRemaining(), false)); - } - } else { - // "PIN operation failed!" - no idea what this was and no way to - // find out. :/ - mSecurityMessageDisplay.setMessage(getContext().getString( - R.string.kg_password_pin_failed)); - } - if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock " - + " CheckSimPin.onSimCheckResponse: " + result - + " attemptsRemaining=" + result.getAttemptsRemaining()); - } - mCallback.userActivity(); - mCheckSimPinThread = null; - } - }); - } - }; - mCheckSimPinThread.start(); - } } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java new file mode 100644 index 0000000000000..3c29058e0afc2 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java @@ -0,0 +1,350 @@ +/* + * 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.keyguard; + +import android.annotation.NonNull; +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.telephony.PinResult; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.util.Log; +import android.view.View; +import android.view.WindowManager; +import android.widget.ImageView; + +import com.android.internal.util.LatencyTracker; +import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.R; + +public class KeyguardSimPinViewController + extends KeyguardPinBasedInputViewController { + public static final String TAG = "KeyguardSimPinView"; + private static final String LOG_TAG = "KeyguardSimPinView"; + private static final boolean DEBUG = KeyguardConstants.DEBUG_SIM_STATES; + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final TelephonyManager mTelephonyManager; + + private ProgressDialog mSimUnlockProgressDialog; + private CheckSimPin mCheckSimPinThread; + private int mRemainingAttempts; + // Below flag is set to true during power-up or when a new SIM card inserted on device. + // When this is true and when SIM card is PIN locked state, on PIN lock screen, message would + // be displayed to inform user about the number of remaining PIN attempts left. + private boolean mShowDefaultMessage; + private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + private AlertDialog mRemainingAttemptsDialog; + private ImageView mSimImageView; + + KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { + @Override + public void onSimStateChanged(int subId, int slotId, int simState) { + if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")"); + if (simState == TelephonyManager.SIM_STATE_READY) { + mRemainingAttempts = -1; + resetState(); + } else { + resetState(); + } + } + }; + + protected KeyguardSimPinViewController(KeyguardSimPinView view, + KeyguardUpdateMonitor keyguardUpdateMonitor, + SecurityMode securityMode, LockPatternUtils lockPatternUtils, + KeyguardSecurityCallback keyguardSecurityCallback, + KeyguardMessageAreaController.Factory messageAreaControllerFactory, + LatencyTracker latencyTracker, + LiftToActivateListener liftToActivateListener, + TelephonyManager telephonyManager) { + super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, + messageAreaControllerFactory, latencyTracker, liftToActivateListener); + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mTelephonyManager = telephonyManager; + mSimImageView = mView.findViewById(R.id.keyguard_sim); + } + + @Override + protected void onViewAttached() { + super.onViewAttached(); + } + + @Override + void resetState() { + super.resetState(); + if (DEBUG) Log.v(TAG, "Resetting state"); + handleSubInfoChangeIfNeeded(); + mMessageAreaController.setMessage(""); + if (mShowDefaultMessage) { + showDefaultMessage(); + } + + mView.setEsimLocked(KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId)); + } + + @Override + public boolean startDisappearAnimation(Runnable finishRunnable) { + return mView.startDisappearAnimation(finishRunnable); + } + + @Override + public void onResume(int reason) { + super.onResume(reason); + mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback); + mView.resetState(); + } + + @Override + public void onPause() { + super.onPause(); + mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback); + + // dismiss the dialog. + if (mSimUnlockProgressDialog != null) { + mSimUnlockProgressDialog.dismiss(); + mSimUnlockProgressDialog = null; + } + } + + @Override + protected void verifyPasswordAndUnlock() { + String entry = mPasswordEntry.getText(); + + if (entry.length() < 4) { + // otherwise, display a message to the user, and don't submit. + mMessageAreaController.setMessage( + com.android.systemui.R.string.kg_invalid_sim_pin_hint); + mView.resetPasswordText(true /* animate */, true /* announce */); + mKeyguardSecurityCallback.userActivity(); + return; + } + + getSimUnlockProgressDialog().show(); + + if (mCheckSimPinThread == null) { + mCheckSimPinThread = new CheckSimPin(mPasswordEntry.getText(), mSubId) { + @Override + void onSimCheckResponse(final PinResult result) { + mView.post(() -> { + mRemainingAttempts = result.getAttemptsRemaining(); + if (mSimUnlockProgressDialog != null) { + mSimUnlockProgressDialog.hide(); + } + mView.resetPasswordText(true /* animate */, + /* announce */ + result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS); + if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) { + mKeyguardUpdateMonitor.reportSimUnlocked(mSubId); + mRemainingAttempts = -1; + mShowDefaultMessage = true; + mKeyguardSecurityCallback.dismiss( + true, KeyguardUpdateMonitor.getCurrentUser()); + } else { + mShowDefaultMessage = false; + if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) { + if (result.getAttemptsRemaining() <= 2) { + // this is getting critical - show dialog + getSimRemainingAttemptsDialog( + result.getAttemptsRemaining()).show(); + } else { + // show message + mMessageAreaController.setMessage( + getPinPasswordErrorMessage( + result.getAttemptsRemaining(), false)); + } + } else { + // "PIN operation failed!" - no idea what this was and no way to + // find out. :/ + mMessageAreaController.setMessage(mView.getResources().getString( + R.string.kg_password_pin_failed)); + } + if (DEBUG) { + Log.d(LOG_TAG, "verifyPasswordAndUnlock " + + " CheckSimPin.onSimCheckResponse: " + result + + " attemptsRemaining=" + result.getAttemptsRemaining()); + } + } + mKeyguardSecurityCallback.userActivity(); + mCheckSimPinThread = null; + }); + } + }; + mCheckSimPinThread.start(); + } + } + + private Dialog getSimUnlockProgressDialog() { + if (mSimUnlockProgressDialog == null) { + mSimUnlockProgressDialog = new ProgressDialog(mView.getContext()); + mSimUnlockProgressDialog.setMessage( + mView.getResources().getString(R.string.kg_sim_unlock_progress_dialog_message)); + mSimUnlockProgressDialog.setIndeterminate(true); + mSimUnlockProgressDialog.setCancelable(false); + mSimUnlockProgressDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } + return mSimUnlockProgressDialog; + } + + + private Dialog getSimRemainingAttemptsDialog(int remaining) { + String msg = getPinPasswordErrorMessage(remaining, false); + if (mRemainingAttemptsDialog == null) { + Builder builder = new AlertDialog.Builder(mView.getContext()); + builder.setMessage(msg); + builder.setCancelable(false); + builder.setNeutralButton(R.string.ok, null); + mRemainingAttemptsDialog = builder.create(); + mRemainingAttemptsDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } else { + mRemainingAttemptsDialog.setMessage(msg); + } + return mRemainingAttemptsDialog; + } + + + private String getPinPasswordErrorMessage(int attemptsRemaining, boolean isDefault) { + String displayMessage; + int msgId; + if (attemptsRemaining == 0) { + displayMessage = mView.getResources().getString( + R.string.kg_password_wrong_pin_code_pukked); + } else if (attemptsRemaining > 0) { + msgId = isDefault ? R.plurals.kg_password_default_pin_message : + R.plurals.kg_password_wrong_pin_code; + displayMessage = mView.getResources() + .getQuantityString(msgId, attemptsRemaining, attemptsRemaining); + } else { + msgId = isDefault ? R.string.kg_sim_pin_instructions : R.string.kg_password_pin_failed; + displayMessage = mView.getResources().getString(msgId); + } + if (KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId)) { + displayMessage = mView.getResources() + .getString(R.string.kg_sim_lock_esim_instructions, displayMessage); + } + if (DEBUG) { + Log.d(LOG_TAG, "getPinPasswordErrorMessage: attemptsRemaining=" + + attemptsRemaining + " displayMessage=" + displayMessage); + } + return displayMessage; + } + + private void showDefaultMessage() { + setLockedSimMessage(); + if (mRemainingAttempts >= 0) { + return; + } + + // Sending empty PIN here to query the number of remaining PIN attempts + new CheckSimPin("", mSubId) { + void onSimCheckResponse(final PinResult result) { + Log.d(LOG_TAG, "onSimCheckResponse " + " empty One result " + + result.toString()); + if (result.getAttemptsRemaining() >= 0) { + mRemainingAttempts = result.getAttemptsRemaining(); + setLockedSimMessage(); + } + } + }.start(); + } + + /** + * Since the IPC can block, we want to run the request in a separate thread + * with a callback. + */ + private abstract class CheckSimPin extends Thread { + private final String mPin; + private int mSubId; + + protected CheckSimPin(String pin, int subId) { + mPin = pin; + mSubId = subId; + } + + abstract void onSimCheckResponse(@NonNull PinResult result); + + @Override + public void run() { + if (DEBUG) { + Log.v(TAG, "call supplyPinReportResultForSubscriber(subid=" + mSubId + ")"); + } + TelephonyManager telephonyManager = + mTelephonyManager.createForSubscriptionId(mSubId); + final PinResult result = telephonyManager.supplyPinReportPinResult(mPin); + if (result == null) { + Log.e(TAG, "Error result for supplyPinReportResult."); + mView.post(() -> onSimCheckResponse(PinResult.getDefaultFailedResult())); + } else { + if (DEBUG) { + Log.v(TAG, "supplyPinReportResult returned: " + result.toString()); + } + mView.post(() -> onSimCheckResponse(result)); + } + } + } + + private void setLockedSimMessage() { + boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId); + int count = 1; + if (mTelephonyManager != null) { + count = mTelephonyManager.getActiveModemCount(); + } + Resources rez = mView.getResources(); + String msg; + TypedArray array = mView.getContext().obtainStyledAttributes( + new int[] { R.attr.wallpaperTextColor }); + int color = array.getColor(0, Color.WHITE); + array.recycle(); + if (count < 2) { + msg = rez.getString(R.string.kg_sim_pin_instructions); + } else { + SubscriptionInfo info = mKeyguardUpdateMonitor.getSubscriptionInfoForSubId(mSubId); + CharSequence displayName = info != null ? info.getDisplayName() : ""; // don't crash + msg = rez.getString(R.string.kg_sim_pin_instructions_multi, displayName); + if (info != null) { + color = info.getIconTint(); + } + } + if (isEsimLocked) { + msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg); + } + + if (mView.getVisibility() == View.VISIBLE) { + mMessageAreaController.setMessage(msg); + } + mSimImageView.setImageTintList(ColorStateList.valueOf(color)); + } + + private void handleSubInfoChangeIfNeeded() { + int subId = mKeyguardUpdateMonitor + .getNextSubIdForState(TelephonyManager.SIM_STATE_PIN_REQUIRED); + if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) { + mSubId = subId; + mShowDefaultMessage = true; + mRemainingAttempts = -1; + } + } +} diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java index 5148dd709026f..932d9b966fa39 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java @@ -16,27 +16,11 @@ package com.android.keyguard; -import android.annotation.NonNull; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; import android.app.ProgressDialog; import android.content.Context; -import android.content.res.ColorStateList; -import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.telephony.PinResult; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; -import android.telephony.TelephonyManager; import android.util.AttributeSet; import android.util.Log; -import android.view.View; -import android.view.WindowManager; -import android.widget.ImageView; -import com.android.systemui.Dependency; import com.android.systemui.R; @@ -44,47 +28,10 @@ import com.android.systemui.R; * Displays a PIN pad for entering a PUK (Pin Unlock Kode) provided by a carrier. */ public class KeyguardSimPukView extends KeyguardPinBasedInputView { - private static final String LOG_TAG = "KeyguardSimPukView"; private static final boolean DEBUG = KeyguardConstants.DEBUG; public static final String TAG = "KeyguardSimPukView"; private ProgressDialog mSimUnlockProgressDialog = null; - private CheckSimPuk mCheckSimPukThread; - - // Below flag is set to true during power-up or when a new SIM card inserted on device. - // When this is true and when SIM card is PUK locked state, on PIN lock screen, message would - // be displayed to inform user about the number of remaining PUK attempts left. - private boolean mShowDefaultMessage = true; - private int mRemainingAttempts = -1; - private String mPukText; - private String mPinText; - private StateMachine mStateMachine = new StateMachine(); - private AlertDialog mRemainingAttemptsDialog; - private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; - private ImageView mSimImageView; - - KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { - @Override - public void onSimStateChanged(int subId, int slotId, int simState) { - if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")"); - switch(simState) { - // If the SIM is unlocked via a key sequence through the emergency dialer, it will - // move into the READY state and the PUK lock keyguard should be removed. - case TelephonyManager.SIM_STATE_READY: { - mRemainingAttempts = -1; - mShowDefaultMessage = true; - // mCallback can be null if onSimStateChanged callback is called when keyguard - // isn't active. - if (mCallback != null) { - mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser()); - } - break; - } - default: - resetState(); - } - } - }; public KeyguardSimPukView(Context context) { this(context, null); @@ -94,136 +41,14 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { super(context, attrs); } - private class StateMachine { - final int ENTER_PUK = 0; - final int ENTER_PIN = 1; - final int CONFIRM_PIN = 2; - final int DONE = 3; - private int state = ENTER_PUK; - - public void next() { - int msg = 0; - if (state == ENTER_PUK) { - if (checkPuk()) { - state = ENTER_PIN; - msg = R.string.kg_puk_enter_pin_hint; - } else { - msg = R.string.kg_invalid_sim_puk_hint; - } - } else if (state == ENTER_PIN) { - if (checkPin()) { - state = CONFIRM_PIN; - msg = R.string.kg_enter_confirm_pin_hint; - } else { - msg = R.string.kg_invalid_sim_pin_hint; - } - } else if (state == CONFIRM_PIN) { - if (confirmPin()) { - state = DONE; - msg = R.string.keyguard_sim_unlock_progress_dialog_message; - updateSim(); - } else { - state = ENTER_PIN; // try again? - msg = R.string.kg_invalid_confirm_pin_hint; - } - } - resetPasswordText(true /* animate */, true /* announce */); - if (msg != 0) { - mSecurityMessageDisplay.setMessage(msg); - } - } - - - void reset() { - mPinText=""; - mPukText=""; - state = ENTER_PUK; - handleSubInfoChangeIfNeeded(); - if (mShowDefaultMessage) { - showDefaultMessage(); - } - boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId); - - KeyguardEsimArea esimButton = findViewById(R.id.keyguard_esim_area); - esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE); - mPasswordEntry.requestFocus(); - } - - - } - - private void showDefaultMessage() { - if (mRemainingAttempts >= 0) { - mSecurityMessageDisplay.setMessage(getPukPasswordErrorMessage( - mRemainingAttempts, true)); - return; - } - - boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId); - int count = 1; - TelephonyManager telephonyManager = - (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); - if (telephonyManager != null) { - count = telephonyManager.getActiveModemCount(); - } - Resources rez = getResources(); - String msg; - TypedArray array = mContext.obtainStyledAttributes(new int[] { R.attr.wallpaperTextColor }); - int color = array.getColor(0, Color.WHITE); - array.recycle(); - if (count < 2) { - msg = rez.getString(R.string.kg_puk_enter_puk_hint); - } else { - SubscriptionInfo info = Dependency.get(KeyguardUpdateMonitor.class) - .getSubscriptionInfoForSubId(mSubId); - CharSequence displayName = info != null ? info.getDisplayName() : ""; - msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName); - if (info != null) { - color = info.getIconTint(); - } - } - if (isEsimLocked) { - msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg); - } - if (mSecurityMessageDisplay != null) { - mSecurityMessageDisplay.setMessage(msg); - } - mSimImageView.setImageTintList(ColorStateList.valueOf(color)); - - // Sending empty PUK here to query the number of remaining PIN attempts - new CheckSimPuk("", "", mSubId) { - void onSimLockChangedResponse(final PinResult result) { - if (result == null) Log.e(LOG_TAG, "onSimCheckResponse, pin result is NULL"); - else { - Log.d(LOG_TAG, "onSimCheckResponse " + " empty One result " - + result.toString()); - if (result.getAttemptsRemaining() >= 0) { - mRemainingAttempts = result.getAttemptsRemaining(); - mSecurityMessageDisplay.setMessage( - getPukPasswordErrorMessage(result.getAttemptsRemaining(), true)); - } - } - } - }.start(); - } - - private void handleSubInfoChangeIfNeeded() { - KeyguardUpdateMonitor monitor = Dependency.get(KeyguardUpdateMonitor.class); - int subId = monitor.getNextSubIdForState(TelephonyManager.SIM_STATE_PUK_REQUIRED); - if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) { - mSubId = subId; - mShowDefaultMessage = true; - mRemainingAttempts = -1; - } - } - @Override protected int getPromptReasonStringRes(int reason) { // No message on SIM Puk return 0; } - private String getPukPasswordErrorMessage(int attemptsRemaining, boolean isDefault) { + String getPukPasswordErrorMessage( + int attemptsRemaining, boolean isDefault, boolean isEsimLocked) { String displayMessage; if (attemptsRemaining == 0) { @@ -238,27 +63,18 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { R.string.kg_password_puk_failed; displayMessage = getContext().getString(msgId); } - if (KeyguardEsimArea.isEsimLocked(mContext, mSubId)) { + if (isEsimLocked) { displayMessage = getResources() .getString(R.string.kg_sim_lock_esim_instructions, displayMessage); } - if (DEBUG) Log.d(LOG_TAG, "getPukPasswordErrorMessage:" - + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage); + if (DEBUG) { + Log.d(TAG, "getPukPasswordErrorMessage:" + + " attemptsRemaining=" + attemptsRemaining + + " displayMessage=" + displayMessage); + } return displayMessage; } - @Override - public void resetState() { - super.resetState(); - mStateMachine.reset(); - } - - @Override - protected boolean shouldLockout(long deadline) { - // SIM PUK doesn't have a timed lockout - return false; - } - @Override protected int getPasswordTextViewId() { return R.id.pukEntry; @@ -271,20 +87,6 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { if (mEcaView instanceof EmergencyCarrierArea) { ((EmergencyCarrierArea) mEcaView).setCarrierTextVisible(true); } - mSimImageView = findViewById(R.id.keyguard_sim); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mUpdateMonitorCallback); - resetState(); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - Dependency.get(KeyguardUpdateMonitor.class).removeCallback(mUpdateMonitorCallback); } @Override @@ -300,168 +102,8 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView { } } - /** - * Since the IPC can block, we want to run the request in a separate thread - * with a callback. - */ - private abstract class CheckSimPuk extends Thread { - - private final String mPin, mPuk; - private final int mSubId; - - protected CheckSimPuk(String puk, String pin, int subId) { - mPuk = puk; - mPin = pin; - mSubId = subId; - } - - abstract void onSimLockChangedResponse(@NonNull PinResult result); - - @Override - public void run() { - if (DEBUG) Log.v(TAG, "call supplyPukReportResult()"); - TelephonyManager telephonyManager = - ((TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE)) - .createForSubscriptionId(mSubId); - final PinResult result = telephonyManager.supplyPukReportPinResult(mPuk, mPin); - if (result == null) { - Log.e(TAG, "Error result for supplyPukReportResult."); - post(new Runnable() { - @Override - public void run() { - onSimLockChangedResponse(PinResult.getDefaultFailedResult()); - } - }); - } else { - if (DEBUG) { - Log.v(TAG, "supplyPukReportResult returned: " + result.toString()); - } - post(new Runnable() { - @Override - public void run() { - onSimLockChangedResponse(result); - } - }); - } - } - } - - private Dialog getSimUnlockProgressDialog() { - if (mSimUnlockProgressDialog == null) { - mSimUnlockProgressDialog = new ProgressDialog(mContext); - mSimUnlockProgressDialog.setMessage( - mContext.getString(R.string.kg_sim_unlock_progress_dialog_message)); - mSimUnlockProgressDialog.setIndeterminate(true); - mSimUnlockProgressDialog.setCancelable(false); - if (!(mContext instanceof Activity)) { - mSimUnlockProgressDialog.getWindow().setType( - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - } - } - return mSimUnlockProgressDialog; - } - - private Dialog getPukRemainingAttemptsDialog(int remaining) { - String msg = getPukPasswordErrorMessage(remaining, false); - if (mRemainingAttemptsDialog == null) { - AlertDialog.Builder builder = new AlertDialog.Builder(mContext); - builder.setMessage(msg); - builder.setCancelable(false); - builder.setNeutralButton(R.string.ok, null); - mRemainingAttemptsDialog = builder.create(); - mRemainingAttemptsDialog.getWindow().setType( - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - } else { - mRemainingAttemptsDialog.setMessage(msg); - } - return mRemainingAttemptsDialog; - } - - private boolean checkPuk() { - // make sure the puk is at least 8 digits long. - if (mPasswordEntry.getText().length() == 8) { - mPukText = mPasswordEntry.getText(); - return true; - } - return false; - } - - private boolean checkPin() { - // make sure the PIN is between 4 and 8 digits - int length = mPasswordEntry.getText().length(); - if (length >= 4 && length <= 8) { - mPinText = mPasswordEntry.getText(); - return true; - } - return false; - } - - public boolean confirmPin() { - return mPinText.equals(mPasswordEntry.getText()); - } - - private void updateSim() { - getSimUnlockProgressDialog().show(); - - if (mCheckSimPukThread == null) { - mCheckSimPukThread = new CheckSimPuk(mPukText, mPinText, mSubId) { - @Override - void onSimLockChangedResponse(final PinResult result) { - post(new Runnable() { - @Override - public void run() { - if (mSimUnlockProgressDialog != null) { - mSimUnlockProgressDialog.hide(); - } - resetPasswordText(true /* animate */, - /* announce */ - result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS); - if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) { - Dependency.get(KeyguardUpdateMonitor.class) - .reportSimUnlocked(mSubId); - mRemainingAttempts = -1; - mShowDefaultMessage = true; - if (mCallback != null) { - mCallback.dismiss(true, - KeyguardUpdateMonitor.getCurrentUser()); - } - } else { - mShowDefaultMessage = false; - if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) { - // show message - mSecurityMessageDisplay.setMessage(getPukPasswordErrorMessage( - result.getAttemptsRemaining(), false)); - if (result.getAttemptsRemaining() <= 2) { - // this is getting critical - show dialog - getPukRemainingAttemptsDialog( - result.getAttemptsRemaining()).show(); - } else { - // show message - mSecurityMessageDisplay.setMessage( - getPukPasswordErrorMessage( - result.getAttemptsRemaining(), false)); - } - } else { - mSecurityMessageDisplay.setMessage(getContext().getString( - R.string.kg_password_puk_failed)); - } - if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock " - + " UpdateSim.onSimCheckResponse: " - + " attemptsRemaining=" + result.getAttemptsRemaining()); - } - mStateMachine.reset(); - mCheckSimPukThread = null; - } - }); - } - }; - mCheckSimPukThread.start(); - } - } - @Override - protected void verifyPasswordAndUnlock() { - mStateMachine.next(); + public void onResume(int reason) { } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java new file mode 100644 index 0000000000000..9b2166b044155 --- /dev/null +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java @@ -0,0 +1,404 @@ +/* + * 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.keyguard; + +import android.annotation.NonNull; +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.telephony.PinResult; +import android.telephony.SubscriptionInfo; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.util.Log; +import android.view.View; +import android.view.WindowManager; +import android.widget.ImageView; + +import com.android.internal.util.LatencyTracker; +import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.Dependency; +import com.android.systemui.R; + +public class KeyguardSimPukViewController + extends KeyguardPinBasedInputViewController { + private static final boolean DEBUG = KeyguardConstants.DEBUG; + public static final String TAG = "KeyguardSimPukView"; + + private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + private final TelephonyManager mTelephonyManager; + + private String mPukText; + private String mPinText; + private int mRemainingAttempts; + // Below flag is set to true during power-up or when a new SIM card inserted on device. + // When this is true and when SIM card is PUK locked state, on PIN lock screen, message would + // be displayed to inform user about the number of remaining PUK attempts left. + private boolean mShowDefaultMessage; + private StateMachine mStateMachine = new StateMachine(); + private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + private CheckSimPuk mCheckSimPukThread; + private ProgressDialog mSimUnlockProgressDialog; + + KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { + @Override + public void onSimStateChanged(int subId, int slotId, int simState) { + if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")"); + // If the SIM is unlocked via a key sequence through the emergency dialer, it will + // move into the READY state and the PUK lock keyguard should be removed. + if (simState == TelephonyManager.SIM_STATE_READY) { + mRemainingAttempts = -1; + mShowDefaultMessage = true; + mKeyguardSecurityCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser()); + } else { + resetState(); + } + } + }; + private ImageView mSimImageView; + private AlertDialog mRemainingAttemptsDialog; + + protected KeyguardSimPukViewController(KeyguardSimPukView view, + KeyguardUpdateMonitor keyguardUpdateMonitor, + SecurityMode securityMode, LockPatternUtils lockPatternUtils, + KeyguardSecurityCallback keyguardSecurityCallback, + KeyguardMessageAreaController.Factory messageAreaControllerFactory, + LatencyTracker latencyTracker, + LiftToActivateListener liftToActivateListener, + TelephonyManager telephonyManager) { + super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, + messageAreaControllerFactory, latencyTracker, liftToActivateListener); + mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mTelephonyManager = telephonyManager; + mSimImageView = mView.findViewById(R.id.keyguard_sim); + } + + @Override + protected void onViewAttached() { + super.onViewAttached(); + mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback); + } + + @Override + protected void onViewDetached() { + super.onViewDetached(); + mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback); + } + + @Override + void resetState() { + super.resetState(); + mStateMachine.reset(); + } + + @Override + protected void verifyPasswordAndUnlock() { + mStateMachine.next(); + } + + private class StateMachine { + static final int ENTER_PUK = 0; + static final int ENTER_PIN = 1; + static final int CONFIRM_PIN = 2; + static final int DONE = 3; + + private int mState = ENTER_PUK; + + public void next() { + int msg = 0; + if (mState == ENTER_PUK) { + if (checkPuk()) { + mState = ENTER_PIN; + msg = com.android.systemui.R.string.kg_puk_enter_pin_hint; + } else { + msg = com.android.systemui.R.string.kg_invalid_sim_puk_hint; + } + } else if (mState == ENTER_PIN) { + if (checkPin()) { + mState = CONFIRM_PIN; + msg = com.android.systemui.R.string.kg_enter_confirm_pin_hint; + } else { + msg = com.android.systemui.R.string.kg_invalid_sim_pin_hint; + } + } else if (mState == CONFIRM_PIN) { + if (confirmPin()) { + mState = DONE; + msg = com.android.systemui.R.string.keyguard_sim_unlock_progress_dialog_message; + updateSim(); + } else { + mState = ENTER_PIN; // try again? + msg = com.android.systemui.R.string.kg_invalid_confirm_pin_hint; + } + } + mView.resetPasswordText(true /* animate */, true /* announce */); + if (msg != 0) { + mMessageAreaController.setMessage(msg); + } + } + + + void reset() { + mPinText = ""; + mPukText = ""; + mState = ENTER_PUK; + handleSubInfoChangeIfNeeded(); + if (mShowDefaultMessage) { + showDefaultMessage(); + } + boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId); + + KeyguardEsimArea esimButton = mView.findViewById(R.id.keyguard_esim_area); + esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE); + mPasswordEntry.requestFocus(); + } + } + + private void showDefaultMessage() { + if (mRemainingAttempts >= 0) { + mMessageAreaController.setMessage(mView.getPukPasswordErrorMessage( + mRemainingAttempts, true, + KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId))); + return; + } + + boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId); + int count = 1; + if (mTelephonyManager != null) { + count = mTelephonyManager.getActiveModemCount(); + } + Resources rez = mView.getResources(); + String msg; + TypedArray array = mView.getContext().obtainStyledAttributes( + new int[] { R.attr.wallpaperTextColor }); + int color = array.getColor(0, Color.WHITE); + array.recycle(); + if (count < 2) { + msg = rez.getString(R.string.kg_puk_enter_puk_hint); + } else { + SubscriptionInfo info = Dependency.get(KeyguardUpdateMonitor.class) + .getSubscriptionInfoForSubId(mSubId); + CharSequence displayName = info != null ? info.getDisplayName() : ""; + msg = rez.getString(R.string.kg_puk_enter_puk_hint_multi, displayName); + if (info != null) { + color = info.getIconTint(); + } + } + if (isEsimLocked) { + msg = rez.getString(R.string.kg_sim_lock_esim_instructions, msg); + } + mMessageAreaController.setMessage(msg); + mSimImageView.setImageTintList(ColorStateList.valueOf(color)); + + // Sending empty PUK here to query the number of remaining PIN attempts + new CheckSimPuk("", "", mSubId) { + void onSimLockChangedResponse(final PinResult result) { + if (result == null) Log.e(TAG, "onSimCheckResponse, pin result is NULL"); + else { + Log.d(TAG, "onSimCheckResponse " + " empty One result " + + result.toString()); + if (result.getAttemptsRemaining() >= 0) { + mRemainingAttempts = result.getAttemptsRemaining(); + mMessageAreaController.setMessage( + mView.getPukPasswordErrorMessage( + result.getAttemptsRemaining(), true, + KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId))); + } + } + } + }.start(); + } + + private boolean checkPuk() { + // make sure the puk is at least 8 digits long. + if (mPasswordEntry.getText().length() == 8) { + mPukText = mPasswordEntry.getText(); + return true; + } + return false; + } + + private boolean checkPin() { + // make sure the PIN is between 4 and 8 digits + int length = mPasswordEntry.getText().length(); + if (length >= 4 && length <= 8) { + mPinText = mPasswordEntry.getText(); + return true; + } + return false; + } + + public boolean confirmPin() { + return mPinText.equals(mPasswordEntry.getText()); + } + + + + + private void updateSim() { + getSimUnlockProgressDialog().show(); + + if (mCheckSimPukThread == null) { + mCheckSimPukThread = new CheckSimPuk(mPukText, mPinText, mSubId) { + @Override + void onSimLockChangedResponse(final PinResult result) { + mView.post(() -> { + if (mSimUnlockProgressDialog != null) { + mSimUnlockProgressDialog.hide(); + } + mView.resetPasswordText(true /* animate */, + /* announce */ + result.getType() != PinResult.PIN_RESULT_TYPE_SUCCESS); + if (result.getType() == PinResult.PIN_RESULT_TYPE_SUCCESS) { + mKeyguardUpdateMonitor.reportSimUnlocked(mSubId); + mRemainingAttempts = -1; + mShowDefaultMessage = true; + + mKeyguardSecurityCallback.dismiss( + true, KeyguardUpdateMonitor.getCurrentUser()); + } else { + mShowDefaultMessage = false; + if (result.getType() == PinResult.PIN_RESULT_TYPE_INCORRECT) { + // show message + mMessageAreaController.setMessage(mView.getPukPasswordErrorMessage( + result.getAttemptsRemaining(), false, + KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId))); + if (result.getAttemptsRemaining() <= 2) { + // this is getting critical - show dialog + getPukRemainingAttemptsDialog( + result.getAttemptsRemaining()).show(); + } else { + // show message + mMessageAreaController.setMessage( + mView.getPukPasswordErrorMessage( + result.getAttemptsRemaining(), false, + KeyguardEsimArea.isEsimLocked( + mView.getContext(), mSubId))); + } + } else { + mMessageAreaController.setMessage(mView.getResources().getString( + R.string.kg_password_puk_failed)); + } + if (DEBUG) { + Log.d(TAG, "verifyPasswordAndUnlock " + + " UpdateSim.onSimCheckResponse: " + + " attemptsRemaining=" + result.getAttemptsRemaining()); + } + } + mStateMachine.reset(); + mCheckSimPukThread = null; + }); + } + }; + mCheckSimPukThread.start(); + } + } + + @Override + protected boolean shouldLockout(long deadline) { + // SIM PUK doesn't have a timed lockout + return false; + } + + private Dialog getSimUnlockProgressDialog() { + if (mSimUnlockProgressDialog == null) { + mSimUnlockProgressDialog = new ProgressDialog(mView.getContext()); + mSimUnlockProgressDialog.setMessage( + mView.getResources().getString(R.string.kg_sim_unlock_progress_dialog_message)); + mSimUnlockProgressDialog.setIndeterminate(true); + mSimUnlockProgressDialog.setCancelable(false); + if (!(mView.getContext() instanceof Activity)) { + mSimUnlockProgressDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } + } + return mSimUnlockProgressDialog; + } + + private void handleSubInfoChangeIfNeeded() { + int subId = mKeyguardUpdateMonitor.getNextSubIdForState( + TelephonyManager.SIM_STATE_PUK_REQUIRED); + if (subId != mSubId && SubscriptionManager.isValidSubscriptionId(subId)) { + mSubId = subId; + mShowDefaultMessage = true; + mRemainingAttempts = -1; + } + } + + + private Dialog getPukRemainingAttemptsDialog(int remaining) { + String msg = mView.getPukPasswordErrorMessage(remaining, false, + KeyguardEsimArea.isEsimLocked(mView.getContext(), mSubId)); + if (mRemainingAttemptsDialog == null) { + AlertDialog.Builder builder = new AlertDialog.Builder(mView.getContext()); + builder.setMessage(msg); + builder.setCancelable(false); + builder.setNeutralButton(R.string.ok, null); + mRemainingAttemptsDialog = builder.create(); + mRemainingAttemptsDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } else { + mRemainingAttemptsDialog.setMessage(msg); + } + return mRemainingAttemptsDialog; + } + + /** + * Since the IPC can block, we want to run the request in a separate thread + * with a callback. + */ + private abstract class CheckSimPuk extends Thread { + + private final String mPin, mPuk; + private final int mSubId; + + protected CheckSimPuk(String puk, String pin, int subId) { + mPuk = puk; + mPin = pin; + mSubId = subId; + } + + abstract void onSimLockChangedResponse(@NonNull PinResult result); + + @Override + public void run() { + if (DEBUG) Log.v(TAG, "call supplyPukReportResult()"); + TelephonyManager telephonyManager = mTelephonyManager.createForSubscriptionId(mSubId); + final PinResult result = telephonyManager.supplyPukReportPinResult(mPuk, mPin); + if (result == null) { + Log.e(TAG, "Error result for supplyPukReportResult."); + mView.post(() -> onSimLockChangedResponse(PinResult.getDefaultFailedResult())); + } else { + if (DEBUG) { + Log.v(TAG, "supplyPukReportResult returned: " + result.toString()); + } + mView.post(new Runnable() { + @Override + public void run() { + onSimLockChangedResponse(result); + } + }); + } + } + } + +} diff --git a/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java b/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java index e59602b1cfffc..425e50ed63975 100644 --- a/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java +++ b/packages/SystemUI/src/com/android/keyguard/LiftToActivateListener.java @@ -16,11 +16,12 @@ package com.android.keyguard; -import android.content.Context; import android.view.MotionEvent; import android.view.View; import android.view.accessibility.AccessibilityManager; +import javax.inject.Inject; + /** * Hover listener that implements lift-to-activate interaction for * accessibility. May be added to multiple views. @@ -31,9 +32,9 @@ class LiftToActivateListener implements View.OnHoverListener { private boolean mCachedClickableState; - public LiftToActivateListener(Context context) { - mAccessibilityManager = (AccessibilityManager) context.getSystemService( - Context.ACCESSIBILITY_SERVICE); + @Inject + LiftToActivateListener(AccessibilityManager accessibilityManager) { + mAccessibilityManager = accessibilityManager; } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java index b0457fce6a1a0..2205fdd4267d5 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java @@ -26,6 +26,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityManager; import android.widget.TextView; import com.android.internal.widget.LockPatternUtils; @@ -90,7 +91,8 @@ public class NumPadKey extends ViewGroup { } setOnClickListener(mListener); - setOnHoverListener(new LiftToActivateListener(context)); + setOnHoverListener(new LiftToActivateListener( + (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE))); mLockPatternUtils = new LockPatternUtils(context); mPM = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java index b35579d3624ba..79925bad3cc78 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java @@ -62,6 +62,7 @@ import android.view.ViewConfiguration; import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.view.accessibility.AccessibilityManager; +import android.view.inputmethod.InputMethodManager; import com.android.internal.app.IBatteryStats; import com.android.internal.statusbar.IStatusBarService; @@ -181,6 +182,12 @@ public class FrameworkServicesModule { return context.getSystemService(FingerprintManager.class); } + @Provides + @Singleton + static InputMethodManager provideInputMethodManager(Context context) { + return context.getSystemService(InputMethodManager.class); + } + @Provides @Singleton static IPackageManager provideIPackageManager() { diff --git a/packages/SystemUI/src/com/android/systemui/util/ViewController.java b/packages/SystemUI/src/com/android/systemui/util/ViewController.java index 64f8dbbb9e34d..f30472f75d298 100644 --- a/packages/SystemUI/src/com/android/systemui/util/ViewController.java +++ b/packages/SystemUI/src/com/android/systemui/util/ViewController.java @@ -23,7 +23,20 @@ import android.view.View.OnAttachStateChangeListener; * Utility class that handles view lifecycle events for View Controllers. * * Implementations should handle setup and teardown related activities inside of - * {@link #onViewAttached()} and {@link #onViewDetached()}. + * {@link #onViewAttached()} and {@link #onViewDetached()}. Be sure to call {@link #init()} on + * any child controllers that this uses. This can be done in {@link init()} if the controllers + * are injected, or right after creation time of the child controller. + * + * Tip: View "attachment" happens top down - parents are notified that they are attached before + * any children. That means that if you call a method on a child controller in + * {@link #onViewAttached()}, the child controller may not have had its onViewAttach method + * called, so it may not be fully set up. + * + * As such, make sure that methods on your controller are safe to call _before_ its {@link #init()} + * and {@link #onViewAttached()} methods are called. Specifically, if your controller must call + * {@link View#findViewById(int)} on its root view to setup member variables, do so in its + * constructor. Save {@link #onViewAttached()} for things that can happen post-construction - adding + * listeners, dynamically changing content, or other runtime decisions. * * @param View class that this ViewController is for. */ diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java new file mode 100644 index 0000000000000..2d6ecb6f877e4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java @@ -0,0 +1,120 @@ +/* + * 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.keyguard; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper.RunWithLooper; +import android.view.KeyEvent; + +import androidx.test.filters.SmallTest; + +import com.android.internal.util.LatencyTracker; +import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardAbsKeyInputView.KeyDownListener; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@RunWithLooper +public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase { + + @Mock + private KeyguardAbsKeyInputView mAbsKeyInputView; + @Mock + private PasswordTextView mPasswordEntry; + @Mock + private KeyguardMessageArea mKeyguardMessageArea; + @Mock + private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Mock + private SecurityMode mSecurityMode; + @Mock + private LockPatternUtils mLockPatternUtils; + @Mock + private KeyguardSecurityCallback mKeyguardSecurityCallback; + @Mock + private KeyguardMessageAreaController.Factory mKeyguardMessageAreaControllerFactory; + @Mock + private KeyguardMessageAreaController mKeyguardMessageAreaController; + @Mock + private LatencyTracker mLatencyTracker; + + private KeyguardAbsKeyInputViewController mKeyguardAbsKeyInputViewController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + when(mKeyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea.class))) + .thenReturn(mKeyguardMessageAreaController); + when(mAbsKeyInputView.getPasswordTextViewId()).thenReturn(1); + when(mAbsKeyInputView.findViewById(1)).thenReturn(mPasswordEntry); + when(mAbsKeyInputView.isAttachedToWindow()).thenReturn(true); + when(mAbsKeyInputView.findViewById(R.id.keyguard_message_area)) + .thenReturn(mKeyguardMessageArea); + mKeyguardAbsKeyInputViewController = new KeyguardAbsKeyInputViewController(mAbsKeyInputView, + mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback, + mKeyguardMessageAreaControllerFactory, mLatencyTracker) { + @Override + void resetState() { + } + + @Override + public void onResume(int reason) { + super.onResume(reason); + } + }; + mKeyguardAbsKeyInputViewController.init(); + } + + @Test + public void onKeyDown_clearsSecurityMessage() { + ArgumentCaptor onKeyDownListenerArgumentCaptor = + ArgumentCaptor.forClass(KeyDownListener.class); + verify(mAbsKeyInputView).setKeyDownListener(onKeyDownListenerArgumentCaptor.capture()); + onKeyDownListenerArgumentCaptor.getValue().onKeyDown( + KeyEvent.KEYCODE_0, mock(KeyEvent.class)); + verify(mKeyguardSecurityCallback).userActivity(); + verify(mKeyguardMessageAreaController).setMessage(eq("")); + } + + @Test + public void onKeyDown_noSecurityMessageInteraction() { + ArgumentCaptor onKeyDownListenerArgumentCaptor = + ArgumentCaptor.forClass(KeyDownListener.class); + verify(mAbsKeyInputView).setKeyDownListener(onKeyDownListenerArgumentCaptor.capture()); + onKeyDownListenerArgumentCaptor.getValue().onKeyDown( + KeyEvent.KEYCODE_UNKNOWN, mock(KeyEvent.class)); + verifyZeroInteractions(mKeyguardSecurityCallback); + verifyZeroInteractions(mKeyguardMessageAreaController); + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java new file mode 100644 index 0000000000000..4944284698a05 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java @@ -0,0 +1,106 @@ +/* + * 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.keyguard; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper.RunWithLooper; +import android.view.View; + +import androidx.test.filters.SmallTest; + +import com.android.internal.util.LatencyTracker; +import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@RunWithLooper +public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase { + + @Mock + private KeyguardPinBasedInputView mPinBasedInputView; + @Mock + private PasswordTextView mPasswordEntry; + @Mock + private KeyguardMessageArea mKeyguardMessageArea; + @Mock + private KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @Mock + private SecurityMode mSecurityMode; + @Mock + private LockPatternUtils mLockPatternUtils; + @Mock + private KeyguardSecurityCallback mKeyguardSecurityCallback; + @Mock + private KeyguardMessageAreaController.Factory mKeyguardMessageAreaControllerFactory; + @Mock + private KeyguardMessageAreaController mKeyguardMessageAreaController; + @Mock + private LatencyTracker mLatencyTracker; + @Mock + private LiftToActivateListener mLiftToactivateListener; + @Mock + private View mDeleteButton; + @Mock + private View mOkButton; + + private KeyguardPinBasedInputViewController mKeyguardPinViewController; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + when(mKeyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea.class))) + .thenReturn(mKeyguardMessageAreaController); + when(mPinBasedInputView.getPasswordTextViewId()).thenReturn(1); + when(mPinBasedInputView.findViewById(1)).thenReturn(mPasswordEntry); + when(mPinBasedInputView.isAttachedToWindow()).thenReturn(true); + when(mPinBasedInputView.findViewById(R.id.keyguard_message_area)) + .thenReturn(mKeyguardMessageArea); + when(mPinBasedInputView.findViewById(R.id.delete_button)) + .thenReturn(mDeleteButton); + when(mPinBasedInputView.findViewById(R.id.key_enter)) + .thenReturn(mOkButton); + mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView, + mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback, + mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener) { + @Override + public void onResume(int reason) { + super.onResume(reason); + } + }; + mKeyguardPinViewController.init(); + } + + @Test + public void onResume_requestsFocus() { + mKeyguardPinViewController.onResume(KeyguardSecurityView.SCREEN_ON); + verify(mPasswordEntry).requestFocus(); + } +} + diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewTest.java deleted file mode 100644 index 6666a926c68b7..0000000000000 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewTest.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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.keyguard; - -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; - -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper.RunWithLooper; -import android.view.KeyEvent; -import android.view.LayoutInflater; - -import androidx.test.filters.SmallTest; - -import com.android.systemui.R; -import com.android.systemui.SysuiTestCase; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@SmallTest -@RunWith(AndroidTestingRunner.class) -@RunWithLooper -public class KeyguardPinBasedInputViewTest extends SysuiTestCase { - - @Mock - private PasswordTextView mPasswordEntry; - @Mock - private SecurityMessageDisplay mSecurityMessageDisplay; - @InjectMocks - private KeyguardPinBasedInputView mKeyguardPinView; - - @Before - public void setup() { - LayoutInflater inflater = LayoutInflater.from(getContext()); - mDependency.injectMockDependency(KeyguardUpdateMonitor.class); - mKeyguardPinView = - (KeyguardPinBasedInputView) inflater.inflate(R.layout.keyguard_pin_view, null); - MockitoAnnotations.initMocks(this); - } - - @Test - public void onResume_requestsFocus() { - mKeyguardPinView.onResume(KeyguardSecurityView.SCREEN_ON); - verify(mPasswordEntry).requestFocus(); - } - - @Test - public void onKeyDown_clearsSecurityMessage() { - mKeyguardPinView.onKeyDown(KeyEvent.KEYCODE_0, mock(KeyEvent.class)); - verify(mSecurityMessageDisplay).setMessage(eq("")); - } - - @Test - public void onKeyDown_noSecurityMessageInteraction() { - mKeyguardPinView.onKeyDown(KeyEvent.KEYCODE_UNKNOWN, mock(KeyEvent.class)); - verifyZeroInteractions(mSecurityMessageDisplay); - } -}