Merge changes from topic "b166448040-keyguard-message-area"

* changes:
  9/N Clean Up Keyguard Class Structure
  8/N Remove View Injection from KeyguardMessageArea
  7/N controllers for remaining Keyguard Password Views.
  6/N Add Controller for KeyguardPatternView
  5/N Add KeyguardSecurityViewFlipperController.
  4/N Setup Controller fo KeyguardSecurityContainer.
This commit is contained in:
Dave Mankoff
2020-09-18 14:39:14 +00:00
committed by Android (Google) Code Review
49 changed files with 3547 additions and 2786 deletions

View File

@@ -33,9 +33,13 @@ import android.view.SurfaceView;
import android.view.ViewGroup;
import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.dagger.KeyguardBouncerScope;
import com.android.systemui.dagger.qualifiers.Main;
import java.util.NoSuchElementException;
import javax.inject.Inject;
/**
* Encapsulates all logic for secondary lockscreen state management.
*/
@@ -142,9 +146,9 @@ public class AdminSecondaryLockScreenController {
}
};
public AdminSecondaryLockScreenController(Context context, ViewGroup parent,
private AdminSecondaryLockScreenController(Context context, KeyguardSecurityContainer parent,
KeyguardUpdateMonitor updateMonitor, KeyguardSecurityCallback callback,
Handler handler) {
@Main Handler handler) {
mContext = context;
mHandler = handler;
mParent = parent;
@@ -234,4 +238,26 @@ public class AdminSecondaryLockScreenController {
getHolder().removeCallback(mSurfaceHolderCallback);
}
}
@KeyguardBouncerScope
public static class Factory {
private final Context mContext;
private final KeyguardSecurityContainer mParent;
private final KeyguardUpdateMonitor mUpdateMonitor;
private final Handler mHandler;
@Inject
public Factory(Context context, KeyguardSecurityContainer parent,
KeyguardUpdateMonitor updateMonitor, @Main Handler handler) {
mContext = context;
mParent = parent;
mUpdateMonitor = updateMonitor;
mHandler = handler;
}
public AdminSecondaryLockScreenController create(KeyguardSecurityCallback callback) {
return new AdminSecondaryLockScreenController(mContext, mParent, mUpdateMonitor,
callback, mHandler);
}
}
}

View File

@@ -16,46 +16,26 @@
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 android.widget.LinearLayout;
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 LinearLayout
implements KeyguardSecurityView, EmergencyButton.EmergencyButtonCallback {
protected KeyguardSecurityCallback mCallback;
protected LockPatternUtils mLockPatternUtils;
protected AsyncTask<?, ?, ?> mPendingLockCheck;
protected SecurityMessageDisplay mSecurityMessageDisplay;
public abstract class KeyguardAbsKeyInputView extends KeyguardInputView {
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);
@@ -63,38 +43,10 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout
public KeyguardAbsKeyInputView(Context context, AttributeSet attrs) {
super(context, attrs);
mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
}
@Override
public void setKeyguardCallback(KeyguardSecurityCallback callback) {
mCallback = callback;
}
@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;
void setEnableHaptics(boolean enableHaptics) {
mEnableHaptics = enableHaptics;
}
protected abstract int getPasswordTextViewId();
@@ -102,24 +54,7 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout
@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();
}
/*
@@ -131,195 +66,14 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout
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;
}
@Override
public boolean needsInput() {
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;
}
@Override
public void showPromptReason(int reason) {
if (reason != PROMPT_REASON_NONE) {
int promtReasonStringRes = getPromptReasonStringRes(reason);
if (promtReasonStringRes != 0) {
mSecurityMessageDisplay.setMessage(promtReasonStringRes);
}
}
}
@Override
public void showMessage(CharSequence message, ColorStateList colorState) {
if (colorState != null) {
mSecurityMessageDisplay.setNextMessageColor(colorState);
}
mSecurityMessageDisplay.setMessage(message);
return mKeyDownListener != null && mKeyDownListener.onKeyDown(keyCode, event);
}
protected abstract int getPromptReasonStringRes(int reason);
@@ -333,9 +87,12 @@ public abstract class KeyguardAbsKeyInputView extends LinearLayout
}
}
@Override
public boolean startDisappearAnimation(Runnable finishRunnable) {
return false;
public void setKeyDownListener(KeyDownListener keyDownListener) {
mKeyDownListener = keyDownListener;
}
public interface KeyDownListener {
boolean onKeyDown(int keyCode, KeyEvent keyEvent);
}
}

View File

@@ -0,0 +1,275 @@
/*
* 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.content.res.ColorStateList;
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<T extends KeyguardAbsKeyInputView>
extends KeyguardInputViewController<T> {
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final LockPatternUtils mLockPatternUtils;
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() {
getKeyguardSecurityCallback().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;
mLatencyTracker = latencyTracker;
KeyguardMessageArea kma = KeyguardMessageArea.findSecurityMessageDisplay(mView);
mMessageAreaController = messageAreaControllerFactory.create(kma);
}
abstract void resetState();
@Override
public void init() {
super.init();
mMessageAreaController.init();
}
@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 {
mView.resetState();
}
}
@Override
public boolean needsInput() {
return false;
}
@Override
public void showMessage(CharSequence message, ColorStateList colorState) {
if (colorState != null) {
mMessageAreaController.setNextMessageColor(colorState);
}
mMessageAreaController.setMessage(message);
}
// 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) {
getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0);
if (dismissKeyguard) {
mDismissing = true;
getKeyguardSecurityCallback().dismiss(true, userId);
}
} else {
if (isValidPassword) {
getKeyguardSecurityCallback().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() {
getKeyguardSecurityCallback().userActivity();
getKeyguardSecurityCallback().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();
}
}

View File

@@ -39,7 +39,6 @@ import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.navigationbar.NavigationBarController;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.util.InjectionInflationController;
import javax.inject.Inject;
@@ -49,7 +48,6 @@ public class KeyguardDisplayManager {
private final MediaRouter mMediaRouter;
private final DisplayManager mDisplayService;
private final InjectionInflationController mInjectableInflater;
private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
private final Context mContext;
@@ -92,10 +90,8 @@ public class KeyguardDisplayManager {
@Inject
public KeyguardDisplayManager(Context context,
InjectionInflationController injectableInflater,
KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory) {
mContext = context;
mInjectableInflater = injectableInflater;
mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
mMediaRouter = mContext.getSystemService(MediaRouter.class);
mDisplayService = mContext.getSystemService(DisplayManager.class);
@@ -131,8 +127,7 @@ public class KeyguardDisplayManager {
Presentation presentation = mPresentations.get(displayId);
if (presentation == null) {
final Presentation newPresentation = new KeyguardPresentation(mContext, display,
mKeyguardStatusViewComponentFactory,
mInjectableInflater.injectable(LayoutInflater.from(mContext)));
mKeyguardStatusViewComponentFactory, LayoutInflater.from(mContext));
newPresentation.setOnDismissListener(dialog -> {
if (newPresentation.equals(mPresentations.get(displayId))) {
mPresentations.remove(displayId);
@@ -250,7 +245,7 @@ public class KeyguardDisplayManager {
private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height
private static final int MOVE_CLOCK_TIMEOUT = 10000; // 10s
private final KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
private final LayoutInflater mInjectableLayoutInflater;
private final LayoutInflater mLayoutInflater;
private KeyguardClockSwitchController mKeyguardClockSwitchController;
private View mClock;
private int mUsableWidth;
@@ -270,10 +265,10 @@ public class KeyguardDisplayManager {
KeyguardPresentation(Context context, Display display,
KeyguardStatusViewComponent.Factory keyguardStatusViewComponentFactory,
LayoutInflater injectionLayoutInflater) {
LayoutInflater layoutInflater) {
super(context, display, R.style.Theme_SystemUI_KeyguardPresentation);
mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
mInjectableLayoutInflater = injectionLayoutInflater;
mLayoutInflater = layoutInflater;
getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
setCancelable(false);
}
@@ -299,7 +294,7 @@ public class KeyguardDisplayManager {
mMarginLeft = (100 - VIDEO_SAFE_REGION) * p.x / 200;
mMarginTop = (100 - VIDEO_SAFE_REGION) * p.y / 200;
setContentView(mInjectableLayoutInflater.inflate(R.layout.keyguard_presentation, null));
setContentView(mLayoutInflater.inflate(R.layout.keyguard_presentation, null));
// Logic to make the lock screen fullscreen
getWindow().getDecorView().setSystemUiVisibility(

View File

@@ -178,18 +178,18 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView>
/** 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
@@ -350,7 +350,7 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView>
}
public boolean handleBackKey() {
if (mKeyguardSecurityContainerController.getCurrentSecuritySelection()
if (mKeyguardSecurityContainerController.getCurrentSecurityMode()
!= SecurityMode.None) {
mKeyguardSecurityContainerController.dismiss(
false, KeyguardUpdateMonitor.getCurrentUser());

View File

@@ -0,0 +1,55 @@
/*
* 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.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.LinearLayout;
import androidx.annotation.Nullable;
/**
* A Base class for all Keyguard password/pattern/pin related inputs.
*/
public abstract class KeyguardInputView extends LinearLayout {
public KeyguardInputView(Context context) {
super(context);
}
public KeyguardInputView(Context context,
@Nullable AttributeSet attrs) {
super(context, attrs);
}
public KeyguardInputView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
abstract CharSequence getTitle();
boolean disallowInterceptTouch(MotionEvent event) {
return false;
}
void startAppearAnimation() {}
boolean startDisappearAnimation(Runnable finishRunnable) {
return false;
}
}

View File

@@ -0,0 +1,196 @@
/*
* 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.ColorStateList;
import android.content.res.Resources;
import android.telephony.TelephonyManager;
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 abstract class KeyguardInputViewController<T extends KeyguardInputView>
extends ViewController<T> implements KeyguardSecurityView {
private final SecurityMode mSecurityMode;
private final KeyguardSecurityCallback mKeyguardSecurityCallback;
private boolean mPaused;
// The following is used to ignore callbacks from SecurityViews that are no longer current
// (e.g. face unlock). This avoids unwanted asynchronous events from messing with the
// state for the current security method.
private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() {
@Override
public void userActivity() { }
@Override
public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) { }
@Override
public boolean isVerifyUnlockOnly() {
return false;
}
@Override
public void dismiss(boolean securityVerified, int targetUserId) { }
@Override
public void dismiss(boolean authenticated, int targetId,
boolean bypassSecondaryLockScreen) { }
@Override
public void onUserInput() { }
@Override
public void reset() {}
};
protected KeyguardInputViewController(T view, SecurityMode securityMode,
KeyguardSecurityCallback keyguardSecurityCallback) {
super(view);
mSecurityMode = securityMode;
mKeyguardSecurityCallback = keyguardSecurityCallback;
}
@Override
protected void onViewAttached() {
}
@Override
protected void onViewDetached() {
}
SecurityMode getSecurityMode() {
return mSecurityMode;
}
protected KeyguardSecurityCallback getKeyguardSecurityCallback() {
if (mPaused) {
return mNullCallback;
}
return mKeyguardSecurityCallback;
}
@Override
public void reset() {
}
@Override
public void onPause() {
mPaused = true;
}
@Override
public void onResume(int reason) {
mPaused = false;
}
@Override
public void showPromptReason(int reason) {
}
@Override
public void showMessage(CharSequence message, ColorStateList colorState) {
}
public void startAppearAnimation() {
mView.startAppearAnimation();
}
public boolean startDisappearAnimation(Runnable finishRunnable) {
return mView.startDisappearAnimation(finishRunnable);
}
@Override
public CharSequence getTitle() {
return mView.getTitle();
}
/** Finds the index of this view in the suppplied parent view. */
public int getIndexIn(KeyguardSecurityViewFlipper view) {
return view.indexOfChild(mView);
}
/** Factory for a {@link KeyguardInputViewController}. */
public static class Factory {
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
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,
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}. */
public KeyguardInputViewController create(KeyguardInputView keyguardInputView,
SecurityMode securityMode, KeyguardSecurityCallback keyguardSecurityCallback) {
if (keyguardInputView instanceof KeyguardPatternView) {
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);
}
throw new RuntimeException("Unable to find controller for " + keyguardInputView);
}
}
}

View File

@@ -16,8 +16,6 @@
package com.android.keyguard;
import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
@@ -31,20 +29,14 @@ import android.util.TypedValue;
import android.view.View;
import android.widget.TextView;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.statusbar.policy.ConfigurationController;
import java.lang.ref.WeakReference;
import javax.inject.Inject;
import javax.inject.Named;
/***
* Manages a number of views inside of the given layout. See below for a list of widgets.
*/
public class KeyguardMessageArea extends TextView implements SecurityMessageDisplay,
ConfigurationController.ConfigurationListener {
public class KeyguardMessageArea extends TextView implements SecurityMessageDisplay {
/** Handler token posted with accessibility announcement runnables. */
private static final Object ANNOUNCE_TOKEN = new Object();
@@ -56,71 +48,26 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp
private static final int DEFAULT_COLOR = -1;
private final Handler mHandler;
private final ConfigurationController mConfigurationController;
private ColorStateList mDefaultColorState;
private CharSequence mMessage;
private ColorStateList mNextMessageColorState = ColorStateList.valueOf(DEFAULT_COLOR);
private boolean mBouncerVisible;
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
public void onFinishedGoingToSleep(int why) {
setSelected(false);
}
public void onStartedWakingUp() {
setSelected(true);
}
@Override
public void onKeyguardBouncerChanged(boolean bouncer) {
mBouncerVisible = bouncer;
update();
}
};
public KeyguardMessageArea(Context context) {
super(context, null);
throw new IllegalStateException("This constructor should never be invoked");
}
@Inject
public KeyguardMessageArea(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
ConfigurationController configurationController) {
this(context, attrs, Dependency.get(KeyguardUpdateMonitor.class), configurationController);
}
public KeyguardMessageArea(Context context, AttributeSet attrs, KeyguardUpdateMonitor monitor,
ConfigurationController configurationController) {
public KeyguardMessageArea(Context context, AttributeSet attrs) {
super(context, attrs);
setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug
monitor.registerCallback(mInfoCallback);
mHandler = new Handler(Looper.myLooper());
mConfigurationController = configurationController;
onThemeChanged();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mConfigurationController.addCallback(this);
onThemeChanged();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mConfigurationController.removeCallback(this);
}
@Override
public void setNextMessageColor(ColorStateList colorState) {
mNextMessageColorState = colorState;
}
@Override
public void onThemeChanged() {
void onThemeChanged() {
TypedArray array = mContext.obtainStyledAttributes(new int[] {
R.attr.wallpaperTextColor
});
@@ -130,8 +77,7 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp
update();
}
@Override
public void onDensityOrFontScaleChanged() {
void onDensityOrFontScaleChanged() {
TypedArray array = mContext.obtainStyledAttributes(R.style.Keyguard_TextView, new int[] {
android.R.attr.textSize
});
@@ -177,12 +123,6 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp
return messageArea;
}
@Override
protected void onFinishInflate() {
boolean shouldMarquee = Dependency.get(KeyguardUpdateMonitor.class).isDeviceInteractive();
setSelected(shouldMarquee); // This is required to ensure marquee works
}
private void securityMessageChanged(CharSequence message) {
mMessage = message;
update();
@@ -196,7 +136,7 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp
update();
}
private void update() {
void update() {
CharSequence status = mMessage;
setVisibility(TextUtils.isEmpty(status) || !mBouncerVisible ? INVISIBLE : VISIBLE);
setText(status);
@@ -208,6 +148,9 @@ public class KeyguardMessageArea extends TextView implements SecurityMessageDisp
setTextColor(colorState);
}
public void setBouncerVisible(boolean bouncerVisible) {
mBouncerVisible = bouncerVisible;
}
/**
* Runnable used to delay accessibility announcements.

View File

@@ -16,7 +16,10 @@
package com.android.keyguard;
import android.content.res.ColorStateList;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.util.ViewController;
import javax.inject.Inject;
@@ -26,6 +29,35 @@ public class KeyguardMessageAreaController extends ViewController<KeyguardMessag
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final ConfigurationController mConfigurationController;
private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() {
public void onFinishedGoingToSleep(int why) {
mView.setSelected(false);
}
public void onStartedWakingUp() {
mView.setSelected(true);
}
@Override
public void onKeyguardBouncerChanged(boolean bouncer) {
mView.setBouncerVisible(bouncer);
mView.update();
}
};
private ConfigurationListener mConfigurationListener = new ConfigurationListener() {
@Override
public void onThemeChanged() {
mView.onThemeChanged();
}
@Override
public void onDensityOrFontScaleChanged() {
mView.onDensityOrFontScaleChanged();
}
};
private KeyguardMessageAreaController(KeyguardMessageArea view,
KeyguardUpdateMonitor keyguardUpdateMonitor,
ConfigurationController configurationController) {
@@ -37,17 +69,31 @@ public class KeyguardMessageAreaController extends ViewController<KeyguardMessag
@Override
protected void onViewAttached() {
//mConfigurationController.addCallback();
//mKeyguardUpdateMonitor.registerCallback();
mConfigurationController.addCallback(mConfigurationListener);
mKeyguardUpdateMonitor.registerCallback(mInfoCallback);
mView.setSelected(mKeyguardUpdateMonitor.isDeviceInteractive());
mView.onThemeChanged();
}
@Override
protected void onViewDetached() {
//mConfigurationController.removeCallback();
//mKeyguardUpdateMonitor.removeCallback();
mConfigurationController.removeCallback(mConfigurationListener);
mKeyguardUpdateMonitor.removeCallback(mInfoCallback);
}
/** Factory for createing {@link com.android.keyguard.KeyguardMessageAreaController}. */
public void setMessage(CharSequence s) {
mView.setMessage(s);
}
public void setMessage(int resId) {
mView.setMessage(resId);
}
public void setNextMessageColor(ColorStateList colorState) {
mView.setNextMessageColor(colorState);
}
/** Factory for creating {@link com.android.keyguard.KeyguardMessageAreaController}. */
public static class Factory {
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final ConfigurationController mConfigurationController;

View File

@@ -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
@@ -112,18 +103,6 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
new View[]{
null, mEcaView, null
}};
View cancelBtn = findViewById(R.id.cancel_button);
if (cancelBtn != null) {
cancelBtn.setOnClickListener(view -> {
mCallback.reset();
mCallback.onCancelClicked();
});
}
}
@Override
public void showUsabilityHint() {
}
@Override
@@ -147,24 +126,21 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
});
}
@Override
public boolean startDisappearAnimation(final Runnable finishRunnable) {
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;

View File

@@ -16,50 +16,37 @@
package com.android.keyguard;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
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 {
public class KeyguardPasswordView extends KeyguardAbsKeyInputView {
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 +57,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 +67,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,29 +74,6 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView
return R.id.passwordEntry;
}
@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) {
switch (reason) {
@@ -146,97 +94,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,59 +129,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<InputMethodInfo> 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<InputMethodSubtype> 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() {
}
@Override
public int getWrongPasswordStringId() {
return R.string.kg_wrong_password;
@@ -345,46 +156,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);
}
}

View File

@@ -0,0 +1,275 @@
/*
* 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<KeyguardPasswordView> {
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
public boolean needsInput() {
return true;
}
@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<InputMethodInfo> 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<InputMethodSubtype> 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;
}
}

View File

@@ -15,62 +15,39 @@
*/
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.graphics.Rect;
import android.os.AsyncTask;
import android.os.CountDownTimer;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.LinearLayout;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternChecker;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternView;
import com.android.internal.widget.LockscreenCredential;
import com.android.settingslib.animation.AppearAnimationCreator;
import com.android.settingslib.animation.AppearAnimationUtils;
import com.android.settingslib.animation.DisappearAnimationUtils;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import java.util.List;
public class KeyguardPatternView extends LinearLayout implements KeyguardSecurityView,
AppearAnimationCreator<LockPatternView.CellState>,
EmergencyButton.EmergencyButtonCallback {
public class KeyguardPatternView extends KeyguardInputView
implements AppearAnimationCreator<LockPatternView.CellState> {
private static final String TAG = "SecurityPatternView";
private static final boolean DEBUG = KeyguardConstants.DEBUG;
// how long before we clear the wrong pattern
private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000;
// how long we stay awake after each key beyond MIN_PATTERN_BEFORE_POKE_WAKELOCK
private static final int UNLOCK_PATTERN_WAKE_INTERVAL_MS = 7000;
// how many cells the user has to cross before we poke the wakelock
private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2;
// How much we scale up the duration of the disappear animation when the current user is locked
public static final float DISAPPEAR_MULTIPLIER_LOCKED = 1.5f;
// Extra padding, in pixels, that should eat touch events.
private static final int PATTERNS_TOUCH_AREA_EXTENSION = 40;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final AppearAnimationUtils mAppearAnimationUtils;
private final DisappearAnimationUtils mDisappearAnimationUtils;
private final DisappearAnimationUtils mDisappearAnimationUtilsLocked;
@@ -78,11 +55,7 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit
private final Rect mTempRect = new Rect();
private final Rect mLockPatternScreenBounds = new Rect();
private CountDownTimer mCountdownTimer = null;
private LockPatternUtils mLockPatternUtils;
private AsyncTask<?, ?, ?> mPendingLockCheck;
private LockPatternView mLockPatternView;
private KeyguardSecurityCallback mCallback;
/**
* Keeps track of the last time we poked the wake lock during dispatching of the touch event.
@@ -92,26 +65,9 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit
*/
private long mLastPokeTime = -UNLOCK_PATTERN_WAKE_INTERVAL_MS;
/**
* Useful for clearing out the wrong pattern after a delay
*/
private Runnable mCancelPatternRunnable = new Runnable() {
@Override
public void run() {
mLockPatternView.clearPattern();
}
};
@VisibleForTesting
KeyguardMessageArea mSecurityMessageDisplay;
private View mEcaView;
private ViewGroup mContainer;
private int mDisappearYTranslation;
enum FooterMode {
Normal,
ForgotLockPattern,
VerifyUnlocked
}
public KeyguardPatternView(Context context) {
this(context, null);
@@ -119,7 +75,6 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit
public KeyguardPatternView(Context context, AttributeSet attrs) {
super(context, attrs);
mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
mAppearAnimationUtils = new AppearAnimationUtils(context,
AppearAnimationUtils.DEFAULT_APPEAR_DURATION, 1.5f /* translationScale */,
2.0f /* delayScale */, AnimationUtils.loadInterpolator(
@@ -132,50 +87,16 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit
(long) (125 * DISAPPEAR_MULTIPLIER_LOCKED), 1.2f /* translationScale */,
0.6f /* delayScale */, AnimationUtils.loadInterpolator(
mContext, android.R.interpolator.fast_out_linear_in));
mDisappearYTranslation = getResources().getDimensionPixelSize(
R.dimen.disappear_y_translation);
}
@Override
public void setKeyguardCallback(KeyguardSecurityCallback callback) {
mCallback = callback;
}
@Override
public void setLockPatternUtils(LockPatternUtils utils) {
mLockPatternUtils = utils;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mLockPatternUtils = mLockPatternUtils == null
? new LockPatternUtils(mContext) : mLockPatternUtils;
mLockPatternView = findViewById(R.id.lockPatternView);
mLockPatternView.setSaveEnabled(false);
mLockPatternView.setOnPatternListener(new UnlockPatternListener());
mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
KeyguardUpdateMonitor.getCurrentUser()));
// vibrate mode will be the same for the life of this screen
mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
mEcaView = findViewById(R.id.keyguard_selector_fade_container);
mContainer = findViewById(R.id.container);
EmergencyButton button = findViewById(R.id.emergency_call_button);
if (button != null) {
button.setCallback(this);
}
View cancelBtn = findViewById(R.id.cancel_button);
if (cancelBtn != null) {
cancelBtn.setOnClickListener(view -> {
mCallback.reset();
mCallback.onCancelClicked();
});
}
}
@Override
@@ -184,11 +105,6 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit
mSecurityMessageDisplay = KeyguardMessageArea.findSecurityMessageDisplay(this);
}
@Override
public void onEmergencyButtonClickedWhenInCall() {
mCallback.reset();
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
boolean result = super.onTouchEvent(ev);
@@ -217,248 +133,11 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit
}
@Override
public void reset() {
// reset lock pattern
mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
KeyguardUpdateMonitor.getCurrentUser()));
mLockPatternView.enableInput();
mLockPatternView.setEnabled(true);
mLockPatternView.clearPattern();
if (mSecurityMessageDisplay == null) {
return;
}
// if the user is currently locked out, enforce it.
long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
KeyguardUpdateMonitor.getCurrentUser());
if (deadline != 0) {
handleAttemptLockout(deadline);
} else {
displayDefaultSecurityMessage();
}
}
private void displayDefaultSecurityMessage() {
if (mSecurityMessageDisplay != null) {
mSecurityMessageDisplay.setMessage("");
}
}
@Override
public void showUsabilityHint() {
}
@Override
public boolean disallowInterceptTouch(MotionEvent event) {
boolean disallowInterceptTouch(MotionEvent event) {
return !mLockPatternView.isEmpty()
|| mLockPatternScreenBounds.contains((int) event.getRawX(), (int) event.getRawY());
}
/** TODO: hook this up */
public void cleanUp() {
if (DEBUG) Log.v(TAG, "Cleanup() called on " + this);
mLockPatternUtils = null;
mLockPatternView.setOnPatternListener(null);
}
private class UnlockPatternListener implements LockPatternView.OnPatternListener {
@Override
public void onPatternStart() {
mLockPatternView.removeCallbacks(mCancelPatternRunnable);
mSecurityMessageDisplay.setMessage("");
}
@Override
public void onPatternCleared() {
}
@Override
public void onPatternCellAdded(List<LockPatternView.Cell> pattern) {
mCallback.userActivity();
mCallback.onUserInput();
}
@Override
public void onPatternDetected(final List<LockPatternView.Cell> pattern) {
mKeyguardUpdateMonitor.setCredentialAttempted();
mLockPatternView.disableInput();
if (mPendingLockCheck != null) {
mPendingLockCheck.cancel(false);
}
final int userId = KeyguardUpdateMonitor.getCurrentUser();
if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
mLockPatternView.enableInput();
onPatternChecked(userId, false, 0, false /* not valid - too short */);
return;
}
if (LatencyTracker.isEnabled(mContext)) {
LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL);
LatencyTracker.getInstance(mContext).onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
}
mPendingLockCheck = LockPatternChecker.checkCredential(
mLockPatternUtils,
LockscreenCredential.createPattern(pattern),
userId,
new LockPatternChecker.OnCheckCallback() {
@Override
public void onEarlyMatched() {
if (LatencyTracker.isEnabled(mContext)) {
LatencyTracker.getInstance(mContext).onActionEnd(
ACTION_CHECK_CREDENTIAL);
}
onPatternChecked(userId, true /* matched */, 0 /* timeoutMs */,
true /* isValidPattern */);
}
@Override
public void onChecked(boolean matched, int timeoutMs) {
if (LatencyTracker.isEnabled(mContext)) {
LatencyTracker.getInstance(mContext).onActionEnd(
ACTION_CHECK_CREDENTIAL_UNLOCKED);
}
mLockPatternView.enableInput();
mPendingLockCheck = null;
if (!matched) {
onPatternChecked(userId, false /* matched */, timeoutMs,
true /* isValidPattern */);
}
}
@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);
}
}
});
if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
mCallback.userActivity();
mCallback.onUserInput();
}
}
private void onPatternChecked(int userId, boolean matched, int timeoutMs,
boolean isValidPattern) {
boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId;
if (matched) {
mCallback.reportUnlockAttempt(userId, true, 0);
if (dismissKeyguard) {
mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct);
mCallback.dismiss(true, userId);
}
} else {
mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
if (isValidPattern) {
mCallback.reportUnlockAttempt(userId, false, timeoutMs);
if (timeoutMs > 0) {
long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
userId, timeoutMs);
handleAttemptLockout(deadline);
}
}
if (timeoutMs == 0) {
mSecurityMessageDisplay.setMessage(R.string.kg_wrong_pattern);
mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS);
}
}
}
}
private void handleAttemptLockout(long elapsedRealtimeDeadline) {
mLockPatternView.clearPattern();
mLockPatternView.setEnabled(false);
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long secondsInFuture = (long) Math.ceil(
(elapsedRealtimeDeadline - elapsedRealtime) / 1000.0);
mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) {
@Override
public void onTick(long millisUntilFinished) {
final 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() {
mLockPatternView.setEnabled(true);
displayDefaultSecurityMessage();
}
}.start();
}
@Override
public boolean needsInput() {
return false;
}
@Override
public void onPause() {
if (mCountdownTimer != null) {
mCountdownTimer.cancel();
mCountdownTimer = null;
}
if (mPendingLockCheck != null) {
mPendingLockCheck.cancel(false);
mPendingLockCheck = null;
}
displayDefaultSecurityMessage();
}
@Override
public void onResume(int reason) {
}
@Override
public KeyguardSecurityCallback getCallback() {
return mCallback;
}
@Override
public void showPromptReason(int reason) {
switch (reason) {
case PROMPT_REASON_RESTART:
mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_restart_pattern);
break;
case PROMPT_REASON_TIMEOUT:
mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern);
break;
case PROMPT_REASON_DEVICE_ADMIN:
mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_device_admin);
break;
case PROMPT_REASON_USER_REQUEST:
mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_user_request);
break;
case PROMPT_REASON_PREPARE_FOR_UPDATE:
mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern);
break;
case PROMPT_REASON_NONE:
break;
default:
mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_timeout_pattern);
break;
}
}
@Override
public void showMessage(CharSequence message, ColorStateList colorState) {
if (colorState != null) {
mSecurityMessageDisplay.setNextMessageColor(colorState);
}
mSecurityMessageDisplay.setMessage(message);
}
@Override
public void startAppearAnimation() {
enableClipping(false);
setAlpha(1f);
@@ -467,12 +146,7 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit
0, mAppearAnimationUtils.getInterpolator());
mAppearAnimationUtils.startAnimation2d(
mLockPatternView.getCellStates(),
new Runnable() {
@Override
public void run() {
enableClipping(true);
}
},
() -> enableClipping(true),
this);
if (!TextUtils.isEmpty(mSecurityMessageDisplay.getText())) {
mAppearAnimationUtils.createAnimation(mSecurityMessageDisplay, 0,
@@ -484,11 +158,9 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit
}
}
@Override
public boolean startDisappearAnimation(final Runnable finishRunnable) {
float durationMultiplier = mKeyguardUpdateMonitor.needsSlowUnlockTransition()
? DISAPPEAR_MULTIPLIER_LOCKED
: 1f;
public boolean startDisappearAnimation(boolean needsSlowUnlockTransition,
final Runnable finishRunnable) {
float durationMultiplier = needsSlowUnlockTransition ? DISAPPEAR_MULTIPLIER_LOCKED : 1f;
mLockPatternView.clearPattern();
enableClipping(false);
setTranslationY(0);
@@ -497,10 +169,8 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit
-mDisappearAnimationUtils.getStartTranslation(),
mDisappearAnimationUtils.getInterpolator());
DisappearAnimationUtils disappearAnimationUtils = mKeyguardUpdateMonitor
.needsSlowUnlockTransition()
? mDisappearAnimationUtilsLocked
: mDisappearAnimationUtils;
DisappearAnimationUtils disappearAnimationUtils = needsSlowUnlockTransition
? mDisappearAnimationUtilsLocked : mDisappearAnimationUtils;
disappearAnimationUtils.startAnimation2d(mLockPatternView.getCellStates(),
() -> {
enableClipping(true);
@@ -549,7 +219,7 @@ public class KeyguardPatternView extends LinearLayout implements KeyguardSecurit
@Override
public CharSequence getTitle() {
return getContext().getString(
return getResources().getString(
com.android.internal.R.string.keyguard_accessibility_pattern_unlock);
}
}

View File

@@ -0,0 +1,349 @@
/*
* 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 android.content.res.ColorStateList;
import android.os.AsyncTask;
import android.os.CountDownTimer;
import android.os.SystemClock;
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.LockPatternView;
import com.android.internal.widget.LockPatternView.Cell;
import com.android.internal.widget.LockscreenCredential;
import com.android.keyguard.EmergencyButton.EmergencyButtonCallback;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
import java.util.List;
public class KeyguardPatternViewController
extends KeyguardInputViewController<KeyguardPatternView> {
// how many cells the user has to cross before we poke the wakelock
private static final int MIN_PATTERN_BEFORE_POKE_WAKELOCK = 2;
// how long before we clear the wrong pattern
private static final int PATTERN_CLEAR_TIMEOUT_MS = 2000;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final LockPatternUtils mLockPatternUtils;
private final LatencyTracker mLatencyTracker;
private final KeyguardMessageAreaController.Factory mMessageAreaControllerFactory;
private KeyguardMessageAreaController mMessageAreaController;
private LockPatternView mLockPatternView;
private CountDownTimer mCountdownTimer;
private AsyncTask<?, ?, ?> mPendingLockCheck;
private EmergencyButtonCallback mEmergencyButtonCallback = new EmergencyButtonCallback() {
@Override
public void onEmergencyButtonClickedWhenInCall() {
getKeyguardSecurityCallback().reset();
}
};
/**
* Useful for clearing out the wrong pattern after a delay
*/
private Runnable mCancelPatternRunnable = new Runnable() {
@Override
public void run() {
mLockPatternView.clearPattern();
}
};
private class UnlockPatternListener implements LockPatternView.OnPatternListener {
@Override
public void onPatternStart() {
mLockPatternView.removeCallbacks(mCancelPatternRunnable);
mMessageAreaController.setMessage("");
}
@Override
public void onPatternCleared() {
}
@Override
public void onPatternCellAdded(List<Cell> pattern) {
getKeyguardSecurityCallback().userActivity();
getKeyguardSecurityCallback().onUserInput();
}
@Override
public void onPatternDetected(final List<LockPatternView.Cell> pattern) {
mKeyguardUpdateMonitor.setCredentialAttempted();
mLockPatternView.disableInput();
if (mPendingLockCheck != null) {
mPendingLockCheck.cancel(false);
}
final int userId = KeyguardUpdateMonitor.getCurrentUser();
if (pattern.size() < LockPatternUtils.MIN_PATTERN_REGISTER_FAIL) {
mLockPatternView.enableInput();
onPatternChecked(userId, false, 0, false /* not valid - too short */);
return;
}
mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL);
mLatencyTracker.onActionStart(ACTION_CHECK_CREDENTIAL_UNLOCKED);
mPendingLockCheck = LockPatternChecker.checkCredential(
mLockPatternUtils,
LockscreenCredential.createPattern(pattern),
userId,
new LockPatternChecker.OnCheckCallback() {
@Override
public void onEarlyMatched() {
mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL);
onPatternChecked(userId, true /* matched */, 0 /* timeoutMs */,
true /* isValidPattern */);
}
@Override
public void onChecked(boolean matched, int timeoutMs) {
mLatencyTracker.onActionEnd(ACTION_CHECK_CREDENTIAL_UNLOCKED);
mLockPatternView.enableInput();
mPendingLockCheck = null;
if (!matched) {
onPatternChecked(userId, false /* matched */, timeoutMs,
true /* isValidPattern */);
}
}
@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);
}
});
if (pattern.size() > MIN_PATTERN_BEFORE_POKE_WAKELOCK) {
getKeyguardSecurityCallback().userActivity();
getKeyguardSecurityCallback().onUserInput();
}
}
private void onPatternChecked(int userId, boolean matched, int timeoutMs,
boolean isValidPattern) {
boolean dismissKeyguard = KeyguardUpdateMonitor.getCurrentUser() == userId;
if (matched) {
getKeyguardSecurityCallback().reportUnlockAttempt(userId, true, 0);
if (dismissKeyguard) {
mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Correct);
getKeyguardSecurityCallback().dismiss(true, userId);
}
} else {
mLockPatternView.setDisplayMode(LockPatternView.DisplayMode.Wrong);
if (isValidPattern) {
getKeyguardSecurityCallback().reportUnlockAttempt(userId, false, timeoutMs);
if (timeoutMs > 0) {
long deadline = mLockPatternUtils.setLockoutAttemptDeadline(
userId, timeoutMs);
handleAttemptLockout(deadline);
}
}
if (timeoutMs == 0) {
mMessageAreaController.setMessage(R.string.kg_wrong_pattern);
mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS);
}
}
}
}
protected KeyguardPatternViewController(KeyguardPatternView view,
KeyguardUpdateMonitor keyguardUpdateMonitor,
SecurityMode securityMode,
LockPatternUtils lockPatternUtils,
KeyguardSecurityCallback keyguardSecurityCallback,
LatencyTracker latencyTracker,
KeyguardMessageAreaController.Factory messageAreaControllerFactory) {
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
public void init() {
super.init();
mMessageAreaController.init();
}
@Override
protected void onViewAttached() {
mLockPatternView.setOnPatternListener(new UnlockPatternListener());
mLockPatternView.setSaveEnabled(false);
mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
KeyguardUpdateMonitor.getCurrentUser()));
// vibrate mode will be the same for the life of this screen
mLockPatternView.setTactileFeedbackEnabled(mLockPatternUtils.isTactileFeedbackEnabled());
EmergencyButton button = mView.findViewById(R.id.emergency_call_button);
if (button != null) {
button.setCallback(mEmergencyButtonCallback);
}
View cancelBtn = mView.findViewById(R.id.cancel_button);
if (cancelBtn != null) {
cancelBtn.setOnClickListener(view -> {
getKeyguardSecurityCallback().reset();
getKeyguardSecurityCallback().onCancelClicked();
});
}
}
@Override
protected void onViewDetached() {
super.onViewDetached();
mLockPatternView.setOnPatternListener(null);
EmergencyButton button = mView.findViewById(R.id.emergency_call_button);
if (button != null) {
button.setCallback(null);
}
View cancelBtn = mView.findViewById(R.id.cancel_button);
if (cancelBtn != null) {
cancelBtn.setOnClickListener(null);
}
}
@Override
public void reset() {
// reset lock pattern
mLockPatternView.setInStealthMode(!mLockPatternUtils.isVisiblePatternEnabled(
KeyguardUpdateMonitor.getCurrentUser()));
mLockPatternView.enableInput();
mLockPatternView.setEnabled(true);
mLockPatternView.clearPattern();
// if the user is currently locked out, enforce it.
long deadline = mLockPatternUtils.getLockoutAttemptDeadline(
KeyguardUpdateMonitor.getCurrentUser());
if (deadline != 0) {
handleAttemptLockout(deadline);
} else {
displayDefaultSecurityMessage();
}
}
@Override
public void onPause() {
super.onPause();
if (mCountdownTimer != null) {
mCountdownTimer.cancel();
mCountdownTimer = null;
}
if (mPendingLockCheck != null) {
mPendingLockCheck.cancel(false);
mPendingLockCheck = null;
}
displayDefaultSecurityMessage();
}
@Override
public boolean needsInput() {
return false;
}
@Override
public void showPromptReason(int reason) {
/// TODO: move all this logic into the MessageAreaController?
switch (reason) {
case PROMPT_REASON_RESTART:
mMessageAreaController.setMessage(R.string.kg_prompt_reason_restart_pattern);
break;
case PROMPT_REASON_TIMEOUT:
mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
break;
case PROMPT_REASON_DEVICE_ADMIN:
mMessageAreaController.setMessage(R.string.kg_prompt_reason_device_admin);
break;
case PROMPT_REASON_USER_REQUEST:
mMessageAreaController.setMessage(R.string.kg_prompt_reason_user_request);
break;
case PROMPT_REASON_PREPARE_FOR_UPDATE:
mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
break;
case PROMPT_REASON_NONE:
break;
default:
mMessageAreaController.setMessage(R.string.kg_prompt_reason_timeout_pattern);
break;
}
}
@Override
public void showMessage(CharSequence message, ColorStateList colorState) {
if (colorState != null) {
mMessageAreaController.setNextMessageColor(colorState);
}
mMessageAreaController.setMessage(message);
}
@Override
public void startAppearAnimation() {
super.startAppearAnimation();
}
@Override
public boolean startDisappearAnimation(Runnable finishRunnable) {
return mView.startDisappearAnimation(
mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
}
private void displayDefaultSecurityMessage() {
mMessageAreaController.setMessage("");
}
private void handleAttemptLockout(long elapsedRealtimeDeadline) {
mLockPatternView.clearPattern();
mLockPatternView.setEnabled(false);
final long elapsedRealtime = SystemClock.elapsedRealtime();
final long secondsInFuture = (long) Math.ceil(
(elapsedRealtimeDeadline - elapsedRealtime) / 1000.0);
mCountdownTimer = new CountDownTimer(secondsInFuture * 1000, 1000) {
@Override
public void onTick(long millisUntilFinished) {
final 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() {
mLockPatternView.setEnabled(true);
displayDefaultSecurityMessage();
}
}.start();
}
}

View File

@@ -16,11 +16,17 @@
package com.android.keyguard;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
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 +35,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 +58,6 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView
@Override
protected void resetState() {
setPasswordEntryEnabled(true);
}
@Override
@@ -86,10 +81,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 +120,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 +139,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(

View File

@@ -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<T extends KeyguardPinBasedInputView>
extends KeyguardAbsKeyInputViewController<T> {
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);
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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.View;
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 KeyguardPinViewController
extends KeyguardPinBasedInputViewController<KeyguardPINView> {
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
protected void onViewAttached() {
super.onViewAttached();
View cancelBtn = mView.findViewById(R.id.cancel_button);
if (cancelBtn != null) {
cancelBtn.setOnClickListener(view -> {
getKeyguardSecurityCallback().reset();
getKeyguardSecurityCallback().onCancelClicked();
});
}
}
@Override
void resetState() {
super.resetState();
mMessageAreaController.setMessage("");
}
@Override
public boolean startDisappearAnimation(Runnable finishRunnable) {
return mView.startDisappearAnimation(
mKeyguardUpdateMonitor.needsSlowUnlockTransition(), finishRunnable);
}
}

View File

@@ -21,8 +21,6 @@ import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.systemBars;
import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
import static com.android.systemui.DejankUtils.whitelistIpcs;
import static java.lang.Integer.max;
import android.animation.Animator;
@@ -30,25 +28,14 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.Insets;
import android.graphics.Rect;
import android.metrics.LogMaker;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.Log;
import android.util.MathUtils;
import android.util.Slog;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
@@ -63,42 +50,30 @@ import androidx.annotation.VisibleForTesting;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.InjectionInflationController;
import java.util.List;
public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSecurityView {
private static final boolean DEBUG = KeyguardConstants.DEBUG;
private static final String TAG = "KeyguardSecurityView";
private static final int USER_TYPE_PRIMARY = 1;
private static final int USER_TYPE_WORK_PROFILE = 2;
private static final int USER_TYPE_SECONDARY_USER = 3;
public class KeyguardSecurityContainer extends FrameLayout {
static final int USER_TYPE_PRIMARY = 1;
static final int USER_TYPE_WORK_PROFILE = 2;
static final int USER_TYPE_SECONDARY_USER = 3;
// Bouncer is dismissed due to no security.
private static final int BOUNCER_DISMISS_NONE_SECURITY = 0;
static final int BOUNCER_DISMISS_NONE_SECURITY = 0;
// Bouncer is dismissed due to pin, password or pattern entered.
private static final int BOUNCER_DISMISS_PASSWORD = 1;
static final int BOUNCER_DISMISS_PASSWORD = 1;
// Bouncer is dismissed due to biometric (face, fingerprint or iris) authenticated.
private static final int BOUNCER_DISMISS_BIOMETRIC = 2;
static final int BOUNCER_DISMISS_BIOMETRIC = 2;
// Bouncer is dismissed due to extended access granted.
private static final int BOUNCER_DISMISS_EXTENDED_ACCESS = 3;
static final int BOUNCER_DISMISS_EXTENDED_ACCESS = 3;
// Bouncer is dismissed due to sim card unlock code entered.
private static final int BOUNCER_DISMISS_SIM = 4;
static final int BOUNCER_DISMISS_SIM = 4;
// Make the view move slower than the finger, as if the spring were applying force.
private static final float TOUCH_Y_MULTIPLIER = 0.25f;
@@ -107,36 +82,23 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
// How much to scale the default slop by, to avoid accidental drags.
private static final float SLOP_SCALE = 4f;
private static final UiEventLogger sUiEventLogger = new UiEventLoggerImpl();
private static final long IME_DISAPPEAR_DURATION_MS = 125;
private KeyguardSecurityModel mSecurityModel;
private LockPatternUtils mLockPatternUtils;
@VisibleForTesting
KeyguardSecurityViewFlipper mSecurityViewFlipper;
private boolean mIsVerifyUnlockOnly;
private SecurityMode mCurrentSecuritySelection = SecurityMode.Invalid;
private KeyguardSecurityView mCurrentSecurityView;
private SecurityCallback mSecurityCallback;
private AlertDialog mAlertDialog;
private InjectionInflationController mInjectionInflationController;
private boolean mSwipeUpToRetry;
private AdminSecondaryLockScreenController mSecondaryLockScreenController;
private final ViewConfiguration mViewConfiguration;
private final SpringAnimation mSpringAnimation;
private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
private final KeyguardUpdateMonitor mUpdateMonitor;
private final KeyguardStateController mKeyguardStateController;
private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
private float mLastTouchY = -1;
private int mActivePointerId = -1;
private boolean mIsDragging;
private float mStartTouchY = -1;
private boolean mDisappearAnimRunning;
private SwipeListener mSwipeListener;
private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
new WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
@@ -188,19 +150,22 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
// Used to notify the container when something interesting happens.
public interface SecurityCallback {
public boolean dismiss(boolean authenticated, int targetUserId,
boolean bypassSecondaryLockScreen);
public void userActivity();
public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput);
boolean dismiss(boolean authenticated, int targetUserId, boolean bypassSecondaryLockScreen);
void userActivity();
void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput);
/**
* @param strongAuth wheher the user has authenticated with strong authentication like
* pattern, password or PIN but not by trust agents or fingerprint
* @param targetUserId a user that needs to be the foreground user at the finish completion.
*/
public void finish(boolean strongAuth, int targetUserId);
public void reset();
public void onCancelClicked();
void finish(boolean strongAuth, int targetUserId);
void reset();
void onCancelClicked();
}
public interface SwipeListener {
void onSwipeUp();
}
@VisibleForTesting
@@ -251,51 +216,23 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
public KeyguardSecurityContainer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mSecurityModel = Dependency.get(KeyguardSecurityModel.class);
mLockPatternUtils = new LockPatternUtils(context);
mUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
mSpringAnimation = new SpringAnimation(this, DynamicAnimation.Y);
mInjectionInflationController = new InjectionInflationController(
SystemUIFactory.getInstance().getSysUIComponent().createViewInstanceCreatorFactory());
mViewConfiguration = ViewConfiguration.get(context);
mKeyguardStateController = Dependency.get(KeyguardStateController.class);
mSecondaryLockScreenController = new AdminSecondaryLockScreenController(context, this,
mUpdateMonitor, mCallback, new Handler(Looper.myLooper()));
}
public void setSecurityCallback(SecurityCallback callback) {
mSecurityCallback = callback;
}
@Override
public void onResume(int reason) {
if (mCurrentSecuritySelection != SecurityMode.None) {
getSecurityView(mCurrentSecuritySelection).onResume(reason);
}
void onResume(SecurityMode securityMode, boolean faceAuthEnabled) {
mSecurityViewFlipper.setWindowInsetsAnimationCallback(mWindowInsetsAnimationCallback);
updateBiometricRetry();
updateBiometricRetry(securityMode, faceAuthEnabled);
}
@Override
public void onPause() {
if (mAlertDialog != null) {
mAlertDialog.dismiss();
mAlertDialog = null;
}
mSecondaryLockScreenController.hide();
if (mCurrentSecuritySelection != SecurityMode.None) {
getSecurityView(mCurrentSecuritySelection).onPause();
}
mSecurityViewFlipper.setWindowInsetsAnimationCallback(null);
}
@Override
public void onStartingToHide() {
if (mCurrentSecuritySelection != SecurityMode.None) {
getSecurityView(mCurrentSecuritySelection).onStartingToHide();
}
}
@Override
public boolean shouldDelayChildPressedState() {
return true;
@@ -318,13 +255,12 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
return false;
}
// Avoid dragging the pattern view
if (mCurrentSecurityView.disallowInterceptTouch(event)) {
if (mSecurityViewFlipper.getSecurityView().disallowInterceptTouch(event)) {
return false;
}
int index = event.findPointerIndex(mActivePointerId);
float touchSlop = mViewConfiguration.getScaledTouchSlop() * SLOP_SCALE;
if (mCurrentSecurityView != null && index != -1
&& mStartTouchY - event.getY(index) > touchSlop) {
if (index != -1 && mStartTouchY - event.getY(index) > touchSlop) {
mIsDragging = true;
return true;
}
@@ -372,31 +308,28 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
}
if (action == MotionEvent.ACTION_UP) {
if (-getTranslationY() > TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
MIN_DRAG_SIZE, getResources().getDisplayMetrics())
&& !mUpdateMonitor.isFaceDetectionRunning()) {
mUpdateMonitor.requestFaceAuth();
mCallback.userActivity();
showMessage(null, null);
MIN_DRAG_SIZE, getResources().getDisplayMetrics())) {
if (mSwipeListener != null) {
mSwipeListener.onSwipeUp();
}
}
}
return true;
}
void setSwipeListener(SwipeListener swipeListener) {
mSwipeListener = swipeListener;
}
private void startSpringAnimation(float startVelocity) {
mSpringAnimation
.setStartVelocity(startVelocity)
.animateToFinalPosition(0);
}
public void startAppearAnimation() {
if (mCurrentSecuritySelection != SecurityMode.None) {
getSecurityView(mCurrentSecuritySelection).startAppearAnimation();
}
}
public boolean startDisappearAnimation(Runnable onFinishRunnable) {
public void startDisappearAnimation(SecurityMode securitySelection) {
mDisappearAnimRunning = true;
if (mCurrentSecuritySelection == SecurityMode.Password) {
if (securitySelection == SecurityMode.Password) {
mSecurityViewFlipper.getWindowInsetsController().controlWindowInsetsAnimation(ime(),
IME_DISAPPEAR_DURATION_MS,
Interpolators.LINEAR, null, new WindowInsetsAnimationControlListener() {
@@ -441,19 +374,13 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
}
});
}
if (mCurrentSecuritySelection != SecurityMode.None) {
return getSecurityView(mCurrentSecuritySelection).startDisappearAnimation(
onFinishRunnable);
}
return false;
}
/**
* Enables/disables swipe up to retry on the bouncer.
*/
private void updateBiometricRetry() {
SecurityMode securityMode = getSecurityMode();
mSwipeUpToRetry = mKeyguardStateController.isFaceAuthEnabled()
private void updateBiometricRetry(SecurityMode securityMode, boolean faceAuthEnabled) {
mSwipeUpToRetry = faceAuthEnabled
&& securityMode != SecurityMode.SimPin
&& securityMode != SecurityMode.SimPuk
&& securityMode != SecurityMode.None;
@@ -463,53 +390,11 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
return mSecurityViewFlipper.getTitle();
}
@VisibleForTesting
protected KeyguardSecurityView getSecurityView(SecurityMode securityMode) {
final int securityViewIdForMode = getSecurityViewIdForMode(securityMode);
KeyguardSecurityView view = null;
final int children = mSecurityViewFlipper.getChildCount();
for (int child = 0; child < children; child++) {
if (mSecurityViewFlipper.getChildAt(child).getId() == securityViewIdForMode) {
view = ((KeyguardSecurityView)mSecurityViewFlipper.getChildAt(child));
break;
}
}
int layoutId = getLayoutIdFor(securityMode);
if (view == null && layoutId != 0) {
final LayoutInflater inflater = LayoutInflater.from(mContext);
if (DEBUG) Log.v(TAG, "inflating id = " + layoutId);
View v = mInjectionInflationController.injectable(inflater)
.inflate(layoutId, mSecurityViewFlipper, false);
mSecurityViewFlipper.addView(v);
updateSecurityView(v);
view = (KeyguardSecurityView)v;
view.reset();
}
return view;
}
private void updateSecurityView(View view) {
if (view instanceof KeyguardSecurityView) {
KeyguardSecurityView ksv = (KeyguardSecurityView) view;
ksv.setKeyguardCallback(mCallback);
ksv.setLockPatternUtils(mLockPatternUtils);
} else {
Log.w(TAG, "View " + view + " is not a KeyguardSecurityView");
}
}
@Override
public void onFinishInflate() {
super.onFinishInflate();
mSecurityViewFlipper = findViewById(R.id.view_flipper);
mSecurityViewFlipper.setLockPatternUtils(mLockPatternUtils);
}
public void setLockPatternUtils(LockPatternUtils utils) {
mLockPatternUtils = utils;
mSecurityModel.setLockPatternUtils(utils);
mSecurityViewFlipper.setLockPatternUtils(mLockPatternUtils);
}
@Override
@@ -546,11 +431,12 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
mAlertDialog.show();
}
private void showTimeoutDialog(int userId, int timeoutMs) {
int timeoutInSeconds = (int) timeoutMs / 1000;
void showTimeoutDialog(int userId, int timeoutMs, LockPatternUtils lockPatternUtils,
SecurityMode securityMode) {
int timeoutInSeconds = timeoutMs / 1000;
int messageId = 0;
switch (mSecurityModel.getSecurityMode(userId)) {
switch (securityMode) {
case Pattern:
messageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message;
break;
@@ -570,13 +456,13 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
if (messageId != 0) {
final String message = mContext.getString(messageId,
mLockPatternUtils.getCurrentFailedPasswordAttempts(userId),
lockPatternUtils.getCurrentFailedPasswordAttempts(userId),
timeoutInSeconds);
showDialog(null, message);
}
}
private void showAlmostAtWipeDialog(int attempts, int remaining, int userType) {
void showAlmostAtWipeDialog(int attempts, int remaining, int userType) {
String message = null;
switch (userType) {
case USER_TYPE_PRIMARY:
@@ -595,7 +481,7 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
showDialog(null, message);
}
private void showWipeDialog(int attempts, int userType) {
void showWipeDialog(int attempts, int userType) {
String message = null;
switch (userType) {
case USER_TYPE_PRIMARY:
@@ -614,358 +500,8 @@ public class KeyguardSecurityContainer extends FrameLayout implements KeyguardSe
showDialog(null, message);
}
private void reportFailedUnlockAttempt(int userId, int timeoutMs) {
// +1 for this time
final int failedAttempts = mLockPatternUtils.getCurrentFailedPasswordAttempts(userId) + 1;
if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts);
final DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager();
final int failedAttemptsBeforeWipe =
dpm.getMaximumFailedPasswordsForWipe(null, userId);
final int remainingBeforeWipe = failedAttemptsBeforeWipe > 0 ?
(failedAttemptsBeforeWipe - failedAttempts)
: Integer.MAX_VALUE; // because DPM returns 0 if no restriction
if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) {
// The user has installed a DevicePolicyManager that requests a user/profile to be wiped
// N attempts. Once we get below the grace period, we post this dialog every time as a
// clear warning until the deletion fires.
// Check which profile has the strictest policy for failed password attempts
final int expiringUser = dpm.getProfileWithMinimumFailedPasswordsForWipe(userId);
int userType = USER_TYPE_PRIMARY;
if (expiringUser == userId) {
// TODO: http://b/23522538
if (expiringUser != UserHandle.USER_SYSTEM) {
userType = USER_TYPE_SECONDARY_USER;
}
} else if (expiringUser != UserHandle.USER_NULL) {
userType = USER_TYPE_WORK_PROFILE;
} // If USER_NULL, which shouldn't happen, leave it as USER_TYPE_PRIMARY
if (remainingBeforeWipe > 0) {
showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe, userType);
} else {
// Too many attempts. The device will be wiped shortly.
Slog.i(TAG, "Too many unlock attempts; user " + expiringUser + " will be wiped!");
showWipeDialog(failedAttempts, userType);
}
}
mLockPatternUtils.reportFailedPasswordAttempt(userId);
if (timeoutMs > 0) {
mLockPatternUtils.reportPasswordLockout(timeoutMs, userId);
showTimeoutDialog(userId, timeoutMs);
}
}
/**
* Shows the primary security screen for the user. This will be either the multi-selector
* or the user's security method.
* @param turningOff true if the device is being turned off
*/
void showPrimarySecurityScreen(boolean turningOff) {
SecurityMode securityMode = whitelistIpcs(() -> mSecurityModel.getSecurityMode(
KeyguardUpdateMonitor.getCurrentUser()));
if (DEBUG) Log.v(TAG, "showPrimarySecurityScreen(turningOff=" + turningOff + ")");
showSecurityScreen(securityMode);
}
/**
* Shows the next security screen if there is one.
* @param authenticated true if the user entered the correct authentication
* @param targetUserId a user that needs to be the foreground user at the finish (if called)
* completion.
* @param bypassSecondaryLockScreen true if the user is allowed to bypass the secondary
* secondary lock screen requirement, if any.
* @return true if keyguard is done
*/
boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId,
boolean bypassSecondaryLockScreen) {
if (DEBUG) Log.d(TAG, "showNextSecurityScreenOrFinish(" + authenticated + ")");
boolean finish = false;
boolean strongAuth = false;
int eventSubtype = -1;
BouncerUiEvent uiEvent = BouncerUiEvent.UNKNOWN;
if (mUpdateMonitor.getUserHasTrust(targetUserId)) {
finish = true;
eventSubtype = BOUNCER_DISMISS_EXTENDED_ACCESS;
uiEvent = BouncerUiEvent.BOUNCER_DISMISS_EXTENDED_ACCESS;
} else if (mUpdateMonitor.getUserUnlockedWithBiometric(targetUserId)) {
finish = true;
eventSubtype = BOUNCER_DISMISS_BIOMETRIC;
uiEvent = BouncerUiEvent.BOUNCER_DISMISS_BIOMETRIC;
} else if (SecurityMode.None == mCurrentSecuritySelection) {
SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
if (SecurityMode.None == securityMode) {
finish = true; // no security required
eventSubtype = BOUNCER_DISMISS_NONE_SECURITY;
uiEvent = BouncerUiEvent.BOUNCER_DISMISS_NONE_SECURITY;
} else {
showSecurityScreen(securityMode); // switch to the alternate security view
}
} else if (authenticated) {
switch (mCurrentSecuritySelection) {
case Pattern:
case Password:
case PIN:
strongAuth = true;
finish = true;
eventSubtype = BOUNCER_DISMISS_PASSWORD;
uiEvent = BouncerUiEvent.BOUNCER_DISMISS_PASSWORD;
break;
case SimPin:
case SimPuk:
// Shortcut for SIM PIN/PUK to go to directly to user's security screen or home
SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
if (securityMode == SecurityMode.None && mLockPatternUtils.isLockScreenDisabled(
KeyguardUpdateMonitor.getCurrentUser())) {
finish = true;
eventSubtype = BOUNCER_DISMISS_SIM;
uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM;
} else {
showSecurityScreen(securityMode);
}
break;
default:
Log.v(TAG, "Bad security screen " + mCurrentSecuritySelection + ", fail safe");
showPrimarySecurityScreen(false);
break;
}
}
// Check for device admin specified additional security measures.
if (finish && !bypassSecondaryLockScreen) {
Intent secondaryLockscreenIntent =
mUpdateMonitor.getSecondaryLockscreenRequirement(targetUserId);
if (secondaryLockscreenIntent != null) {
mSecondaryLockScreenController.show(secondaryLockscreenIntent);
return false;
}
}
if (eventSubtype != -1) {
mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER)
.setType(MetricsEvent.TYPE_DISMISS).setSubtype(eventSubtype));
}
if (uiEvent != BouncerUiEvent.UNKNOWN) {
sUiEventLogger.log(uiEvent);
}
if (finish) {
mSecurityCallback.finish(strongAuth, targetUserId);
}
return finish;
}
/**
* Switches to the given security view unless it's already being shown, in which case
* this is a no-op.
*
* @param securityMode
*/
private void showSecurityScreen(SecurityMode securityMode) {
if (DEBUG) Log.d(TAG, "showSecurityScreen(" + securityMode + ")");
if (securityMode == mCurrentSecuritySelection) return;
KeyguardSecurityView oldView = getSecurityView(mCurrentSecuritySelection);
KeyguardSecurityView newView = getSecurityView(securityMode);
// Emulate Activity life cycle
if (oldView != null) {
oldView.onPause();
oldView.setKeyguardCallback(mNullCallback); // ignore requests from old view
}
if (securityMode != SecurityMode.None) {
newView.onResume(KeyguardSecurityView.VIEW_REVEALED);
newView.setKeyguardCallback(mCallback);
}
// Find and show this child.
final int childCount = mSecurityViewFlipper.getChildCount();
final int securityViewIdForMode = getSecurityViewIdForMode(securityMode);
for (int i = 0; i < childCount; i++) {
if (mSecurityViewFlipper.getChildAt(i).getId() == securityViewIdForMode) {
mSecurityViewFlipper.setDisplayedChild(i);
break;
}
}
mCurrentSecuritySelection = securityMode;
mCurrentSecurityView = newView;
mSecurityCallback.onSecurityModeChanged(securityMode,
securityMode != SecurityMode.None && newView.needsInput());
}
private KeyguardSecurityCallback mCallback = new KeyguardSecurityCallback() {
public void userActivity() {
if (mSecurityCallback != null) {
mSecurityCallback.userActivity();
}
}
@Override
public void onUserInput() {
mUpdateMonitor.cancelFaceAuth();
}
@Override
public void dismiss(boolean authenticated, int targetId) {
dismiss(authenticated, targetId, /* bypassSecondaryLockScreen */ false);
}
@Override
public void dismiss(boolean authenticated, int targetId,
boolean bypassSecondaryLockScreen) {
mSecurityCallback.dismiss(authenticated, targetId, bypassSecondaryLockScreen);
}
public boolean isVerifyUnlockOnly() {
return mIsVerifyUnlockOnly;
}
public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) {
if (success) {
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS);
mLockPatternUtils.reportSuccessfulPasswordAttempt(userId);
// Force a garbage collection in an attempt to erase any lockscreen password left in
// memory. Do it asynchronously with a 5-sec delay to avoid making the keyguard
// dismiss animation janky.
ThreadUtils.postOnBackgroundThread(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException ignored) { }
Runtime.getRuntime().gc();
});
} else {
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE);
KeyguardSecurityContainer.this.reportFailedUnlockAttempt(userId, timeoutMs);
}
mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER)
.setType(success ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_FAILURE));
sUiEventLogger.log(success ? BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS
: BouncerUiEvent.BOUNCER_PASSWORD_FAILURE);
}
public void reset() {
mSecurityCallback.reset();
}
public void onCancelClicked() {
mSecurityCallback.onCancelClicked();
}
};
// The following is used to ignore callbacks from SecurityViews that are no longer current
// (e.g. face unlock). This avoids unwanted asynchronous events from messing with the
// state for the current security method.
private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() {
@Override
public void userActivity() { }
@Override
public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) { }
@Override
public boolean isVerifyUnlockOnly() { return false; }
@Override
public void dismiss(boolean securityVerified, int targetUserId) { }
@Override
public void dismiss(boolean authenticated, int targetId,
boolean bypassSecondaryLockScreen) { }
@Override
public void onUserInput() { }
@Override
public void reset() {}
};
private int getSecurityViewIdForMode(SecurityMode securityMode) {
switch (securityMode) {
case Pattern: return R.id.keyguard_pattern_view;
case PIN: return R.id.keyguard_pin_view;
case Password: return R.id.keyguard_password_view;
case SimPin: return R.id.keyguard_sim_pin_view;
case SimPuk: return R.id.keyguard_sim_puk_view;
}
return 0;
}
@VisibleForTesting
public int getLayoutIdFor(SecurityMode securityMode) {
switch (securityMode) {
case Pattern: return R.layout.keyguard_pattern_view;
case PIN: return R.layout.keyguard_pin_view;
case Password: return R.layout.keyguard_password_view;
case SimPin: return R.layout.keyguard_sim_pin_view;
case SimPuk: return R.layout.keyguard_sim_puk_view;
default:
return 0;
}
}
public SecurityMode getSecurityMode() {
return mSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser());
}
public SecurityMode getCurrentSecurityMode() {
return mCurrentSecuritySelection;
}
public KeyguardSecurityView getCurrentSecurityView() {
return mCurrentSecurityView;
}
public void verifyUnlock() {
mIsVerifyUnlockOnly = true;
showSecurityScreen(getSecurityMode());
}
public SecurityMode getCurrentSecuritySelection() {
return mCurrentSecuritySelection;
}
public void dismiss(boolean authenticated, int targetUserId) {
mCallback.dismiss(authenticated, targetUserId);
}
public boolean needsInput() {
return mSecurityViewFlipper.needsInput();
}
@Override
public void setKeyguardCallback(KeyguardSecurityCallback callback) {
mSecurityViewFlipper.setKeyguardCallback(callback);
}
@Override
public void reset() {
mSecurityViewFlipper.reset();
mDisappearAnimRunning = false;
}
@Override
public KeyguardSecurityCallback getCallback() {
return mSecurityViewFlipper.getCallback();
}
@Override
public void showPromptReason(int reason) {
if (mCurrentSecuritySelection != SecurityMode.None) {
if (reason != PROMPT_REASON_NONE) {
Log.i(TAG, "Strong auth required, reason: " + reason);
}
getSecurityView(mCurrentSecuritySelection).showPromptReason(reason);
}
}
public void showMessage(CharSequence message, ColorStateList colorState) {
if (mCurrentSecuritySelection != SecurityMode.None) {
getSecurityView(mCurrentSecuritySelection).showMessage(message, colorState);
}
}
@Override
public void showUsabilityHint() {
mSecurityViewFlipper.showUsabilityHint();
}
}

View File

@@ -16,33 +16,166 @@
package com.android.keyguard;
import android.content.res.ColorStateList;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_NONE_SECURITY;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_PASSWORD;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_SIM;
import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_PRIMARY;
import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_SECONDARY_USER;
import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_WORK_PROFILE;
import static com.android.systemui.DejankUtils.whitelistIpcs;
import android.app.admin.DevicePolicyManager;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.metrics.LogMaker;
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityContainer.BouncerUiEvent;
import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback;
import com.android.keyguard.KeyguardSecurityContainer.SwipeListener;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.keyguard.dagger.KeyguardBouncerScope;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.ViewController;
import javax.inject.Inject;
/** Controller for {@link KeyguardSecurityContainer} */
public class KeyguardSecurityContainerController extends ViewController<KeyguardSecurityContainer> {
@KeyguardBouncerScope
public class KeyguardSecurityContainerController extends ViewController<KeyguardSecurityContainer>
implements KeyguardSecurityView {
private static final boolean DEBUG = KeyguardConstants.DEBUG;
private static final String TAG = "KeyguardSecurityView";
private final AdminSecondaryLockScreenController mAdminSecondaryLockScreenController;
private final LockPatternUtils mLockPatternUtils;
private final KeyguardSecurityViewController.Factory mKeyguardSecurityViewControllerFactory;
private final KeyguardUpdateMonitor mUpdateMonitor;
private final KeyguardSecurityModel mSecurityModel;
private final MetricsLogger mMetricsLogger;
private final UiEventLogger mUiEventLogger;
private final KeyguardStateController mKeyguardStateController;
private final KeyguardSecurityViewFlipperController mSecurityViewFlipperController;
private SecurityCallback mSecurityCallback;
private SecurityMode mCurrentSecurityMode = SecurityMode.Invalid;
private KeyguardSecurityCallback mKeyguardSecurityCallback = new KeyguardSecurityCallback() {
public void userActivity() {
if (mSecurityCallback != null) {
mSecurityCallback.userActivity();
}
}
@Override
public void onUserInput() {
mUpdateMonitor.cancelFaceAuth();
}
@Override
public void dismiss(boolean authenticated, int targetId) {
dismiss(authenticated, targetId, /* bypassSecondaryLockScreen */ false);
}
@Override
public void dismiss(boolean authenticated, int targetId,
boolean bypassSecondaryLockScreen) {
mSecurityCallback.dismiss(authenticated, targetId, bypassSecondaryLockScreen);
}
public boolean isVerifyUnlockOnly() {
return false;
}
public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) {
if (success) {
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__SUCCESS);
mLockPatternUtils.reportSuccessfulPasswordAttempt(userId);
// Force a garbage collection in an attempt to erase any lockscreen password left in
// memory. Do it asynchronously with a 5-sec delay to avoid making the keyguard
// dismiss animation janky.
ThreadUtils.postOnBackgroundThread(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException ignored) { }
Runtime.getRuntime().gc();
});
} else {
SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED,
SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__RESULT__FAILURE);
reportFailedUnlockAttempt(userId, timeoutMs);
}
mMetricsLogger.write(new LogMaker(MetricsEvent.BOUNCER)
.setType(success ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_FAILURE));
mUiEventLogger.log(success ? BouncerUiEvent.BOUNCER_PASSWORD_SUCCESS
: BouncerUiEvent.BOUNCER_PASSWORD_FAILURE);
}
public void reset() {
mSecurityCallback.reset();
}
public void onCancelClicked() {
mSecurityCallback.onCancelClicked();
}
};
private SwipeListener mSwipeListener = new SwipeListener() {
@Override
public void onSwipeUp() {
if (!mUpdateMonitor.isFaceDetectionRunning()) {
mUpdateMonitor.requestFaceAuth();
mKeyguardSecurityCallback.userActivity();
showMessage(null, null);
}
}
};
@Inject
KeyguardSecurityContainerController(KeyguardSecurityContainer view,
AdminSecondaryLockScreenController.Factory adminSecondaryLockScreenControllerFactory,
LockPatternUtils lockPatternUtils,
KeyguardSecurityViewController.Factory keyguardSecurityViewControllerFactory) {
KeyguardUpdateMonitor keyguardUpdateMonitor,
KeyguardSecurityModel keyguardSecurityModel,
MetricsLogger metricsLogger,
UiEventLogger uiEventLogger,
KeyguardStateController keyguardStateController,
KeyguardSecurityViewFlipperController securityViewFlipperController) {
super(view);
mLockPatternUtils = lockPatternUtils;
view.setLockPatternUtils(mLockPatternUtils);
mKeyguardSecurityViewControllerFactory = keyguardSecurityViewControllerFactory;
mUpdateMonitor = keyguardUpdateMonitor;
mSecurityModel = keyguardSecurityModel;
mMetricsLogger = metricsLogger;
mUiEventLogger = uiEventLogger;
mKeyguardStateController = keyguardStateController;
mSecurityViewFlipperController = securityViewFlipperController;
mAdminSecondaryLockScreenController = adminSecondaryLockScreenControllerFactory.create(
mKeyguardSecurityCallback);
}
@Override
public void init() {
super.init();
mSecurityViewFlipperController.init();
}
@Override
protected void onViewAttached() {
mView.setSwipeListener(mSwipeListener);
}
@Override
@@ -51,68 +184,270 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
/** */
public void onPause() {
mAdminSecondaryLockScreenController.hide();
if (mCurrentSecurityMode != SecurityMode.None) {
getCurrentSecurityController().onPause();
}
mView.onPause();
}
/**
* Shows the primary security screen for the user. This will be either the multi-selector
* or the user's security method.
* @param turningOff true if the device is being turned off
*/
public void showPrimarySecurityScreen(boolean turningOff) {
mView.showPrimarySecurityScreen(turningOff);
SecurityMode securityMode = whitelistIpcs(() -> mSecurityModel.getSecurityMode(
KeyguardUpdateMonitor.getCurrentUser()));
if (DEBUG) Log.v(TAG, "showPrimarySecurityScreen(turningOff=" + turningOff + ")");
showSecurityScreen(securityMode);
}
@Override
public void showPromptReason(int reason) {
mView.showPromptReason(reason);
if (mCurrentSecurityMode != SecurityMode.None) {
if (reason != PROMPT_REASON_NONE) {
Log.i(TAG, "Strong auth required, reason: " + reason);
}
getCurrentSecurityController().showPromptReason(reason);
}
}
public void showMessage(CharSequence message, ColorStateList colorState) {
mView.showMessage(message, colorState);
if (mCurrentSecurityMode != SecurityMode.None) {
getCurrentSecurityController().showMessage(message, colorState);
}
}
public SecurityMode getCurrentSecuritySelection() {
return mView.getCurrentSecuritySelection();
public SecurityMode getCurrentSecurityMode() {
return mCurrentSecurityMode;
}
public void dismiss(boolean authenticated, int targetUserId) {
mView.dismiss(authenticated, targetUserId);
mKeyguardSecurityCallback.dismiss(authenticated, targetUserId);
}
public void reset() {
mView.reset();
mSecurityViewFlipperController.reset();
}
public CharSequence getTitle() {
return mView.getTitle();
}
public void onResume(int screenOn) {
mView.onResume(screenOn);
@Override
public void onResume(int reason) {
if (mCurrentSecurityMode != SecurityMode.None) {
getCurrentSecurityController().onResume(reason);
}
mView.onResume(
mSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser()),
mKeyguardStateController.isFaceAuthEnabled());
}
public void startAppearAnimation() {
mView.startAppearAnimation();
if (mCurrentSecurityMode != SecurityMode.None) {
getCurrentSecurityController().startAppearAnimation();
}
}
public boolean startDisappearAnimation(Runnable onFinishRunnable) {
return mView.startDisappearAnimation(onFinishRunnable);
mView.startDisappearAnimation(getCurrentSecurityMode());
if (mCurrentSecurityMode != SecurityMode.None) {
return getCurrentSecurityController().startDisappearAnimation(onFinishRunnable);
}
return false;
}
public void onStartingToHide() {
mView.onStartingToHide();
if (mCurrentSecurityMode != SecurityMode.None) {
getCurrentSecurityController().onStartingToHide();
}
}
public void setSecurityCallback(SecurityCallback securityCallback) {
mView.setSecurityCallback(securityCallback);
mSecurityCallback = securityCallback;
}
/**
* Shows the next security screen if there is one.
* @param authenticated true if the user entered the correct authentication
* @param targetUserId a user that needs to be the foreground user at the finish (if called)
* completion.
* @param bypassSecondaryLockScreen true if the user is allowed to bypass the secondary
* secondary lock screen requirement, if any.
* @return true if keyguard is done
*/
public boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId,
boolean bypassSecondaryLockScreen) {
return mView.showNextSecurityScreenOrFinish(
authenticated, targetUserId, bypassSecondaryLockScreen);
if (DEBUG) Log.d(TAG, "showNextSecurityScreenOrFinish(" + authenticated + ")");
boolean finish = false;
boolean strongAuth = false;
int eventSubtype = -1;
BouncerUiEvent uiEvent = BouncerUiEvent.UNKNOWN;
if (mUpdateMonitor.getUserHasTrust(targetUserId)) {
finish = true;
eventSubtype = BOUNCER_DISMISS_EXTENDED_ACCESS;
uiEvent = BouncerUiEvent.BOUNCER_DISMISS_EXTENDED_ACCESS;
} else if (mUpdateMonitor.getUserUnlockedWithBiometric(targetUserId)) {
finish = true;
eventSubtype = BOUNCER_DISMISS_BIOMETRIC;
uiEvent = BouncerUiEvent.BOUNCER_DISMISS_BIOMETRIC;
} else if (SecurityMode.None == getCurrentSecurityMode()) {
SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
if (SecurityMode.None == securityMode) {
finish = true; // no security required
eventSubtype = BOUNCER_DISMISS_NONE_SECURITY;
uiEvent = BouncerUiEvent.BOUNCER_DISMISS_NONE_SECURITY;
} else {
showSecurityScreen(securityMode); // switch to the alternate security view
}
} else if (authenticated) {
switch (getCurrentSecurityMode()) {
case Pattern:
case Password:
case PIN:
strongAuth = true;
finish = true;
eventSubtype = BOUNCER_DISMISS_PASSWORD;
uiEvent = BouncerUiEvent.BOUNCER_DISMISS_PASSWORD;
break;
case SimPin:
case SimPuk:
// Shortcut for SIM PIN/PUK to go to directly to user's security screen or home
SecurityMode securityMode = mSecurityModel.getSecurityMode(targetUserId);
if (securityMode == SecurityMode.None && mLockPatternUtils.isLockScreenDisabled(
KeyguardUpdateMonitor.getCurrentUser())) {
finish = true;
eventSubtype = BOUNCER_DISMISS_SIM;
uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM;
} else {
showSecurityScreen(securityMode);
}
break;
default:
Log.v(TAG, "Bad security screen " + getCurrentSecurityMode()
+ ", fail safe");
showPrimarySecurityScreen(false);
break;
}
}
// Check for device admin specified additional security measures.
if (finish && !bypassSecondaryLockScreen) {
Intent secondaryLockscreenIntent =
mUpdateMonitor.getSecondaryLockscreenRequirement(targetUserId);
if (secondaryLockscreenIntent != null) {
mAdminSecondaryLockScreenController.show(secondaryLockscreenIntent);
return false;
}
}
if (eventSubtype != -1) {
mMetricsLogger.write(new LogMaker(MetricsProto.MetricsEvent.BOUNCER)
.setType(MetricsProto.MetricsEvent.TYPE_DISMISS).setSubtype(eventSubtype));
}
if (uiEvent != BouncerUiEvent.UNKNOWN) {
mUiEventLogger.log(uiEvent);
}
if (finish) {
mSecurityCallback.finish(strongAuth, targetUserId);
}
return finish;
}
public boolean needsInput() {
return mView.needsInput();
return getCurrentSecurityController().needsInput();
}
public SecurityMode getCurrentSecurityMode() {
return mView.getCurrentSecurityMode();
/**
* Switches to the given security view unless it's already being shown, in which case
* this is a no-op.
*
* @param securityMode
*/
@VisibleForTesting
void showSecurityScreen(SecurityMode securityMode) {
if (DEBUG) Log.d(TAG, "showSecurityScreen(" + securityMode + ")");
if (securityMode == SecurityMode.Invalid || securityMode == mCurrentSecurityMode) {
return;
}
KeyguardInputViewController<KeyguardInputView> oldView = getCurrentSecurityController();
// Emulate Activity life cycle
if (oldView != null) {
oldView.onPause();
}
KeyguardInputViewController<KeyguardInputView> newView = changeSecurityMode(securityMode);
if (newView != null) {
newView.onResume(KeyguardSecurityView.VIEW_REVEALED);
mSecurityViewFlipperController.show(newView);
}
mSecurityCallback.onSecurityModeChanged(
securityMode, newView != null && newView.needsInput());
}
public void reportFailedUnlockAttempt(int userId, int timeoutMs) {
// +1 for this time
final int failedAttempts = mLockPatternUtils.getCurrentFailedPasswordAttempts(userId) + 1;
if (DEBUG) Log.d(TAG, "reportFailedPatternAttempt: #" + failedAttempts);
final DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager();
final int failedAttemptsBeforeWipe =
dpm.getMaximumFailedPasswordsForWipe(null, userId);
final int remainingBeforeWipe = failedAttemptsBeforeWipe > 0
? (failedAttemptsBeforeWipe - failedAttempts)
: Integer.MAX_VALUE; // because DPM returns 0 if no restriction
if (remainingBeforeWipe < LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE) {
// The user has installed a DevicePolicyManager that requests a user/profile to be wiped
// N attempts. Once we get below the grace period, we post this dialog every time as a
// clear warning until the deletion fires.
// Check which profile has the strictest policy for failed password attempts
final int expiringUser = dpm.getProfileWithMinimumFailedPasswordsForWipe(userId);
int userType = USER_TYPE_PRIMARY;
if (expiringUser == userId) {
// TODO: http://b/23522538
if (expiringUser != UserHandle.USER_SYSTEM) {
userType = USER_TYPE_SECONDARY_USER;
}
} else if (expiringUser != UserHandle.USER_NULL) {
userType = USER_TYPE_WORK_PROFILE;
} // If USER_NULL, which shouldn't happen, leave it as USER_TYPE_PRIMARY
if (remainingBeforeWipe > 0) {
mView.showAlmostAtWipeDialog(failedAttempts, remainingBeforeWipe, userType);
} else {
// Too many attempts. The device will be wiped shortly.
Slog.i(TAG, "Too many unlock attempts; user " + expiringUser + " will be wiped!");
mView.showWipeDialog(failedAttempts, userType);
}
}
mLockPatternUtils.reportFailedPasswordAttempt(userId);
if (timeoutMs > 0) {
mLockPatternUtils.reportPasswordLockout(timeoutMs, userId);
mView.showTimeoutDialog(userId, timeoutMs, mLockPatternUtils,
mSecurityModel.getSecurityMode(userId));
}
}
private KeyguardInputViewController<KeyguardInputView> getCurrentSecurityController() {
return mSecurityViewFlipperController
.getSecurityView(mCurrentSecurityMode, mKeyguardSecurityCallback);
}
private KeyguardInputViewController<KeyguardInputView> changeSecurityMode(
SecurityMode securityMode) {
mCurrentSecurityMode = securityMode;
return getCurrentSecurityController();
}
}

View File

@@ -18,13 +18,14 @@ package com.android.keyguard;
import static com.android.systemui.DejankUtils.whitelistIpcs;
import android.app.admin.DevicePolicyManager;
import android.content.Context;
import android.content.res.Resources;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.Dependency;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import javax.inject.Inject;
@@ -33,7 +34,7 @@ public class KeyguardSecurityModel {
/**
* The different types of security available.
* @see KeyguardSecurityContainer#showSecurityScreen
* @see KeyguardSecurityContainerController#showSecurityScreen
*/
public enum SecurityMode {
Invalid, // NULL state
@@ -45,21 +46,15 @@ public class KeyguardSecurityModel {
SimPuk // Unlock by entering a sim puk
}
private final Context mContext;
private final boolean mIsPukScreenAvailable;
private LockPatternUtils mLockPatternUtils;
private final LockPatternUtils mLockPatternUtils;
@Inject
KeyguardSecurityModel(Context context) {
mContext = context;
mLockPatternUtils = new LockPatternUtils(context);
mIsPukScreenAvailable = mContext.getResources().getBoolean(
KeyguardSecurityModel(@Main Resources resources, LockPatternUtils lockPatternUtils) {
mIsPukScreenAvailable = resources.getBoolean(
com.android.internal.R.bool.config_enable_puk_unlock_screen);
}
void setLockPatternUtils(LockPatternUtils utils) {
mLockPatternUtils = utils;
mLockPatternUtils = lockPatternUtils;
}
public SecurityMode getSecurityMode(int userId) {

View File

@@ -18,11 +18,9 @@ package com.android.keyguard;
import android.content.res.ColorStateList;
import android.view.MotionEvent;
import com.android.internal.widget.LockPatternUtils;
public interface KeyguardSecurityView {
static public final int SCREEN_ON = 1;
static public final int VIEW_REVEALED = 2;
int SCREEN_ON = 1;
int VIEW_REVEALED = 2;
int PROMPT_REASON_NONE = 0;
@@ -62,18 +60,6 @@ public interface KeyguardSecurityView {
*/
int PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT = 7;
/**
* Interface back to keyguard to tell it when security
* @param callback
*/
void setKeyguardCallback(KeyguardSecurityCallback callback);
/**
* Set {@link LockPatternUtils} object. Useful for providing a mock interface.
* @param utils
*/
void setLockPatternUtils(LockPatternUtils utils);
/**
* Reset the view and prepare to take input. This should do things like clearing the
* password or pattern and clear error messages.
@@ -100,12 +86,6 @@ public interface KeyguardSecurityView {
*/
boolean needsInput();
/**
* Get {@link KeyguardSecurityCallback} for the given object
* @return KeyguardSecurityCallback
*/
KeyguardSecurityCallback getCallback();
/**
* Show a string explaining why the security view needs to be solved.
*
@@ -122,12 +102,6 @@ public interface KeyguardSecurityView {
*/
void showMessage(CharSequence message, ColorStateList colorState);
/**
* Instruct the view to show usability hints, if any.
*
*/
void showUsabilityHint();
/**
* Starts the animation which should run when the security view appears.
*/

View File

@@ -1,58 +0,0 @@
/*
* 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.View;
import com.android.systemui.util.ViewController;
import javax.inject.Inject;
/** Controller for a {@link KeyguardSecurityView}. */
public class KeyguardSecurityViewController extends ViewController<View> {
private final KeyguardSecurityView mView;
private KeyguardSecurityViewController(KeyguardSecurityView view) {
super((View) view);
// KeyguardSecurityView isn't actually a View, so we need to track it ourselves.
mView = view;
}
@Override
protected void onViewAttached() {
}
@Override
protected void onViewDetached() {
}
/** Factory for a {@link KeyguardSecurityViewController}. */
public static class Factory {
@Inject
public Factory() {
}
/** Create a new {@link KeyguardSecurityViewController}. */
public KeyguardSecurityViewController create(KeyguardSecurityView view) {
return new KeyguardSecurityViewController(view);
}
}
}

View File

@@ -18,7 +18,6 @@ package com.android.keyguard;
import android.annotation.NonNull;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.util.AttributeSet;
@@ -31,7 +30,6 @@ import android.view.ViewHierarchyEncoder;
import android.widget.FrameLayout;
import android.widget.ViewFlipper;
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.R;
/**
@@ -39,7 +37,7 @@ import com.android.systemui.R;
* we can emulate {@link android.view.WindowManager.LayoutParams#FLAG_SLIPPERY} within a view
* hierarchy.
*/
public class KeyguardSecurityViewFlipper extends ViewFlipper implements KeyguardSecurityView {
public class KeyguardSecurityViewFlipper extends ViewFlipper {
private static final String TAG = "KeyguardSecurityViewFlipper";
private static final boolean DEBUG = KeyguardConstants.DEBUG;
@@ -69,111 +67,16 @@ public class KeyguardSecurityViewFlipper extends ViewFlipper implements Keyguard
return result;
}
KeyguardSecurityView getSecurityView() {
KeyguardInputView getSecurityView() {
View child = getChildAt(getDisplayedChild());
if (child instanceof KeyguardSecurityView) {
return (KeyguardSecurityView) child;
if (child instanceof KeyguardInputView) {
return (KeyguardInputView) child;
}
return null;
}
@Override
public void setKeyguardCallback(KeyguardSecurityCallback callback) {
KeyguardSecurityView ksv = getSecurityView();
if (ksv != null) {
ksv.setKeyguardCallback(callback);
}
}
@Override
public void setLockPatternUtils(LockPatternUtils utils) {
KeyguardSecurityView ksv = getSecurityView();
if (ksv != null) {
ksv.setLockPatternUtils(utils);
}
}
@Override
public void reset() {
KeyguardSecurityView ksv = getSecurityView();
if (ksv != null) {
ksv.reset();
}
}
@Override
public void onPause() {
KeyguardSecurityView ksv = getSecurityView();
if (ksv != null) {
ksv.onPause();
}
}
@Override
public void onResume(int reason) {
KeyguardSecurityView ksv = getSecurityView();
if (ksv != null) {
ksv.onResume(reason);
}
}
@Override
public boolean needsInput() {
KeyguardSecurityView ksv = getSecurityView();
return (ksv != null) ? ksv.needsInput() : false;
}
@Override
public KeyguardSecurityCallback getCallback() {
KeyguardSecurityView ksv = getSecurityView();
return (ksv != null) ? ksv.getCallback() : null;
}
@Override
public void showPromptReason(int reason) {
KeyguardSecurityView ksv = getSecurityView();
if (ksv != null) {
ksv.showPromptReason(reason);
}
}
@Override
public void showMessage(CharSequence message, ColorStateList colorState) {
KeyguardSecurityView ksv = getSecurityView();
if (ksv != null) {
ksv.showMessage(message, colorState);
}
}
@Override
public void showUsabilityHint() {
KeyguardSecurityView ksv = getSecurityView();
if (ksv != null) {
ksv.showUsabilityHint();
}
}
@Override
public void startAppearAnimation() {
KeyguardSecurityView ksv = getSecurityView();
if (ksv != null) {
ksv.startAppearAnimation();
}
}
@Override
public boolean startDisappearAnimation(Runnable finishRunnable) {
KeyguardSecurityView ksv = getSecurityView();
if (ksv != null) {
return ksv.startDisappearAnimation(finishRunnable);
} else {
return false;
}
}
@Override
public CharSequence getTitle() {
KeyguardSecurityView ksv = getSecurityView();
KeyguardInputView ksv = getSecurityView();
if (ksv != null) {
return ksv.getTitle();
}

View File

@@ -0,0 +1,148 @@
/*
* 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.util.Log;
import android.view.LayoutInflater;
import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.KeyguardInputViewController.Factory;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.keyguard.dagger.KeyguardBouncerScope;
import com.android.systemui.R;
import com.android.systemui.util.ViewController;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
/**
* Controller for a {@link KeyguardSecurityViewFlipper}.
*/
@KeyguardBouncerScope
public class KeyguardSecurityViewFlipperController
extends ViewController<KeyguardSecurityViewFlipper> {
private static final boolean DEBUG = KeyguardConstants.DEBUG;
private static final String TAG = "KeyguardSecurityView";
private final List<KeyguardInputViewController<KeyguardInputView>> mChildren =
new ArrayList<>();
private final LayoutInflater mLayoutInflater;
private final Factory mKeyguardSecurityViewControllerFactory;
@Inject
protected KeyguardSecurityViewFlipperController(KeyguardSecurityViewFlipper view,
LayoutInflater layoutInflater,
KeyguardInputViewController.Factory keyguardSecurityViewControllerFactory) {
super(view);
mKeyguardSecurityViewControllerFactory = keyguardSecurityViewControllerFactory;
mLayoutInflater = layoutInflater;
}
@Override
protected void onViewAttached() {
}
@Override
protected void onViewDetached() {
}
public void reset() {
for (KeyguardInputViewController<KeyguardInputView> child : mChildren) {
child.reset();
}
}
@VisibleForTesting
KeyguardInputViewController<KeyguardInputView> getSecurityView(SecurityMode securityMode,
KeyguardSecurityCallback keyguardSecurityCallback) {
KeyguardInputViewController<KeyguardInputView> childController = null;
for (KeyguardInputViewController<KeyguardInputView> child : mChildren) {
if (child.getSecurityMode() == securityMode) {
childController = child;
break;
}
}
if (childController == null
&& securityMode != SecurityMode.None && securityMode != SecurityMode.Invalid) {
int layoutId = getLayoutIdFor(securityMode);
KeyguardInputView view = null;
if (layoutId != 0) {
if (DEBUG) Log.v(TAG, "inflating id = " + layoutId);
view = (KeyguardInputView) mLayoutInflater.inflate(
layoutId, mView, false);
mView.addView(view);
childController = mKeyguardSecurityViewControllerFactory.create(
view, securityMode, keyguardSecurityCallback);
childController.init();
mChildren.add(childController);
}
}
if (childController == null) {
childController = new NullKeyguardInputViewController(
securityMode, keyguardSecurityCallback);
}
return childController;
}
private int getLayoutIdFor(SecurityMode securityMode) {
switch (securityMode) {
case Pattern: return com.android.systemui.R.layout.keyguard_pattern_view;
case PIN: return com.android.systemui.R.layout.keyguard_pin_view;
case Password: return com.android.systemui.R.layout.keyguard_password_view;
case SimPin: return com.android.systemui.R.layout.keyguard_sim_pin_view;
case SimPuk: return R.layout.keyguard_sim_puk_view;
default:
return 0;
}
}
/** Makes the supplied child visible if it is contained win this view, */
public void show(KeyguardInputViewController<KeyguardInputView> childController) {
int index = childController.getIndexIn(mView);
if (index != -1) {
mView.setDisplayedChild(index);
}
}
private static class NullKeyguardInputViewController
extends KeyguardInputViewController<KeyguardInputView> {
protected NullKeyguardInputViewController(SecurityMode securityMode,
KeyguardSecurityCallback keyguardSecurityCallback) {
super(null, securityMode, keyguardSecurityCallback);
}
@Override
public boolean needsInput() {
return false;
}
@Override
public void onStartingToHide() {
}
}
}

View File

@@ -16,66 +16,19 @@
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.Context;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
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;
/**
* Displays a PIN pad for unlocking.
*/
public class KeyguardSimPinView extends KeyguardPinBasedInputView {
private static final String LOG_TAG = "KeyguardSimPinView";
private static final boolean DEBUG = KeyguardConstants.DEBUG_SIM_STATES;
public static final String TAG = "KeyguardSimPinView";
private ProgressDialog mSimUnlockProgressDialog = null;
private CheckSimPin mCheckSimPinThread;
// 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 = true;
private int mRemainingAttempts = -1;
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) {
case TelephonyManager.SIM_STATE_READY: {
mRemainingAttempts = -1;
resetState();
break;
}
default:
resetState();
}
}
};
public KeyguardSimPinView(Context context) {
this(context, null);
}
@@ -84,81 +37,9 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView {
super(context, attrs);
}
@Override
public void resetState() {
super.resetState();
if (DEBUG) Log.v(TAG, "Resetting state");
handleSubInfoChangeIfNeeded();
if (mShowDefaultMessage) {
showDefaultMessage();
}
boolean isEsimLocked = KeyguardEsimArea.isEsimLocked(mContext, mSubId);
public void setEsimLocked(boolean locked) {
KeyguardEsimArea esimButton = findViewById(R.id.keyguard_esim_area);
esimButton.setVisibility(isEsimLocked ? View.VISIBLE : View.GONE);
}
private void setLockedSimMessage() {
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_sim_pin_instructions);
} else {
SubscriptionInfo info = Dependency.get(KeyguardUpdateMonitor.class)
.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 (mSecurityMessageDisplay != null && getVisibility() == VISIBLE) {
mSecurityMessageDisplay.setMessage(msg);
}
mSimImageView.setImageTintList(ColorStateList.valueOf(color));
}
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();
}
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,6 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView {
if (mEcaView instanceof EmergencyCarrierArea) {
((EmergencyCarrierArea) mEcaView).setCarrierTextVisible(true);
}
mSimImageView = findViewById(R.id.keyguard_sim);
}
@Override
public void showUsabilityHint() {
}
@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);
}
/**
* 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
@@ -388,11 +73,6 @@ public class KeyguardSimPinView extends KeyguardPinBasedInputView {
// noop.
}
@Override
public boolean startDisappearAnimation(Runnable finishRunnable) {
return false;
}
@Override
public CharSequence getTitle() {
return getContext().getString(

View File

@@ -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<KeyguardSimPinView> {
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 false;
}
@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 */);
getKeyguardSecurityCallback().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;
getKeyguardSecurityCallback().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());
}
}
getKeyguardSecurityCallback().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;
}
}
}

View File

@@ -16,27 +16,10 @@
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,48 +27,9 @@ 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 +38,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 +60,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,197 +84,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
public void showUsabilityHint() {
}
@Override
public void onPause() {
// dismiss the dialog.
if (mSimUnlockProgressDialog != null) {
mSimUnlockProgressDialog.dismiss();
mSimUnlockProgressDialog = null;
}
}
/**
* 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();
}
@Override

View File

@@ -0,0 +1,413 @@
/*
* 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<KeyguardSimPukView> {
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;
getKeyguardSecurityCallback().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;
getKeyguardSecurityCallback().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;
}
@Override
public void onPause() {
// dismiss the dialog.
if (mSimUnlockProgressDialog != null) {
mSimUnlockProgressDialog.dismiss();
mSimUnlockProgressDialog = null;
}
}
/**
* 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);
}
});
}
}
}
}

View File

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

View File

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

View File

@@ -22,6 +22,7 @@ import android.view.ViewGroup;
import com.android.keyguard.KeyguardHostView;
import com.android.keyguard.KeyguardMessageArea;
import com.android.keyguard.KeyguardSecurityContainer;
import com.android.keyguard.KeyguardSecurityViewFlipper;
import com.android.systemui.R;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
@@ -58,7 +59,15 @@ public interface KeyguardBouncerModule {
/** */
@Provides
@KeyguardBouncerScope
static KeyguardSecurityContainer preovidesKeyguardSecurityContainer(KeyguardHostView hostView) {
static KeyguardSecurityContainer providesKeyguardSecurityContainer(KeyguardHostView hostView) {
return hostView.findViewById(R.id.keyguard_security_container);
}
/** */
@Provides
@KeyguardBouncerScope
static KeyguardSecurityViewFlipper providesKeyguardSecurityViewFlipper(
KeyguardSecurityContainer containerView) {
return containerView.findViewById(R.id.view_flipper);
}
}

View File

@@ -288,6 +288,7 @@ public class DependencyProvider {
/** */
@Provides
@SysUISingleton
public LockPatternUtils provideLockPatternUtils(Context context) {
return new LockPatternUtils(context);
}

View File

@@ -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() {

View File

@@ -23,7 +23,6 @@ import android.view.InflateException;
import android.view.LayoutInflater;
import android.view.View;
import com.android.keyguard.KeyguardMessageArea;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.qs.QSFooterImpl;
import com.android.systemui.qs.QSPanel;
@@ -107,11 +106,6 @@ public class InjectionInflationController {
*/
NotificationStackScrollLayout createNotificationStackScrollLayout();
/**
* Creates the KeyguardMessageArea.
*/
KeyguardMessageArea createKeyguardMessageArea();
/**
* Creates the QSPanel.
*/

View File

@@ -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 <T> View class that this ViewController is for.
*/
@@ -54,10 +67,12 @@ public abstract class ViewController<T extends View> {
}
mInited = true;
if (mView.isAttachedToWindow()) {
mOnAttachStateListener.onViewAttachedToWindow(mView);
if (mView != null) {
if (mView.isAttachedToWindow()) {
mOnAttachStateListener.onViewAttachedToWindow(mView);
}
mView.addOnAttachStateChangeListener(mOnAttachStateListener);
}
mView.addOnAttachStateChangeListener(mOnAttachStateListener);
}
/**

View File

@@ -41,8 +41,6 @@ import android.testing.TestableLooper.RunWithLooper;
import android.testing.ViewUtils;
import android.view.SurfaceControlViewHost;
import android.view.SurfaceView;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.test.filters.SmallTest;
@@ -67,7 +65,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
private ComponentName mComponentName;
private Intent mServiceIntent;
private TestableLooper mTestableLooper;
private ViewGroup mParent;
private KeyguardSecurityContainer mKeyguardSecurityContainer;
@Mock
private Handler mHandler;
@@ -84,8 +82,8 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
public void setUp() {
MockitoAnnotations.initMocks(this);
mParent = spy(new FrameLayout(mContext));
ViewUtils.attachView(mParent);
mKeyguardSecurityContainer = spy(new KeyguardSecurityContainer(mContext));
ViewUtils.attachView(mKeyguardSecurityContainer);
mTestableLooper = TestableLooper.get(this);
mComponentName = new ComponentName(mContext, "FakeKeyguardClient.class");
@@ -96,13 +94,14 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
when(mKeyguardClient.queryLocalInterface(anyString())).thenReturn(mKeyguardClient);
when(mKeyguardClient.asBinder()).thenReturn(mKeyguardClient);
mTestController = new AdminSecondaryLockScreenController(
mContext, mParent, mUpdateMonitor, mKeyguardCallback, mHandler);
mTestController = new AdminSecondaryLockScreenController.Factory(
mContext, mKeyguardSecurityContainer, mUpdateMonitor, mHandler)
.create(mKeyguardCallback);
}
@After
public void tearDown() {
ViewUtils.detachView(mParent);
ViewUtils.detachView(mKeyguardSecurityContainer);
}
@Test
@@ -146,7 +145,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
SurfaceView v = verifySurfaceReady();
mTestController.hide();
verify(mParent).removeView(v);
verify(mKeyguardSecurityContainer).removeView(v);
assertThat(mContext.isBound(mComponentName)).isFalse();
}
@@ -154,7 +153,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
public void testHide_notShown() throws Exception {
mTestController.hide();
// Nothing should happen if trying to hide when the view isn't attached yet.
verify(mParent, never()).removeView(any(SurfaceView.class));
verify(mKeyguardSecurityContainer, never()).removeView(any(SurfaceView.class));
}
@Test
@@ -182,7 +181,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
private SurfaceView verifySurfaceReady() throws Exception {
mTestableLooper.processAllMessages();
ArgumentCaptor<SurfaceView> captor = ArgumentCaptor.forClass(SurfaceView.class);
verify(mParent).addView(captor.capture());
verify(mKeyguardSecurityContainer).addView(captor.capture());
mTestableLooper.processAllMessages();
verify(mKeyguardClient).onCreateKeyguardSurface(any(), any(IKeyguardCallback.class));
@@ -190,7 +189,7 @@ public class AdminSecondaryLockScreenControllerTest extends SysuiTestCase {
}
private void verifyViewDismissed(SurfaceView v) throws Exception {
verify(mParent).removeView(v);
verify(mKeyguardSecurityContainer).removeView(v);
verify(mKeyguardCallback).dismiss(true, TARGET_USER_ID, true);
assertThat(mContext.isBound(mComponentName)).isFalse();
}

View File

@@ -0,0 +1,122 @@
/*
* 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.reset;
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();
reset(mKeyguardMessageAreaController); // Clear out implicit call to init.
}
@Test
public void onKeyDown_clearsSecurityMessage() {
ArgumentCaptor<KeyDownListener> 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<KeyDownListener> onKeyDownListenerArgumentCaptor =
ArgumentCaptor.forClass(KeyDownListener.class);
verify(mAbsKeyInputView).setKeyDownListener(onKeyDownListenerArgumentCaptor.capture());
onKeyDownListenerArgumentCaptor.getValue().onKeyDown(
KeyEvent.KEYCODE_UNKNOWN, mock(KeyEvent.class));
verifyZeroInteractions(mKeyguardSecurityCallback);
verifyZeroInteractions(mKeyguardMessageAreaController);
}
}

View File

@@ -41,11 +41,9 @@ import android.widget.FrameLayout;
import android.widget.TextClock;
import com.android.systemui.R;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.util.InjectionInflationController;
import org.junit.Before;
import org.junit.Test;
@@ -78,12 +76,7 @@ public class KeyguardClockSwitchTest extends SysuiTestCase {
when(mMockKeyguardSliceView.findViewById(R.id.keyguard_status_area))
.thenReturn(mMockKeyguardSliceView);
InjectionInflationController inflationController = new InjectionInflationController(
SystemUIFactory.getInstance()
.getSysUIComponent()
.createViewInstanceCreatorFactory());
LayoutInflater layoutInflater = inflationController
.injectable(LayoutInflater.from(getContext()));
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
layoutInflater.setPrivateFactory(new LayoutInflater.Factory2() {
@Override

View File

@@ -0,0 +1,87 @@
/*
* 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.eq;
import static org.mockito.Mockito.verify;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
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)
public class KeyguardMessageAreaControllerTest extends SysuiTestCase {
@Mock
private ConfigurationController mConfigurationController;
@Mock
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock
private KeyguardMessageArea mKeyguardMessageArea;
private KeyguardMessageAreaController mMessageAreaController;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mMessageAreaController = new KeyguardMessageAreaController.Factory(
mKeyguardUpdateMonitor, mConfigurationController).create(mKeyguardMessageArea);
}
@Test
public void onAttachedToWindow_registersConfigurationCallback() {
ArgumentCaptor<ConfigurationListener> configurationListenerArgumentCaptor =
ArgumentCaptor.forClass(ConfigurationListener.class);
mMessageAreaController.onViewAttached();
verify(mConfigurationController).addCallback(configurationListenerArgumentCaptor.capture());
mMessageAreaController.onViewDetached();
verify(mConfigurationController).removeCallback(
eq(configurationListenerArgumentCaptor.getValue()));
}
@Test
public void onAttachedToWindow_registersKeyguardUpdateMontiorCallback() {
ArgumentCaptor<KeyguardUpdateMonitorCallback> keyguardUpdateMonitorCallbackArgumentCaptor =
ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class);
mMessageAreaController.onViewAttached();
verify(mKeyguardUpdateMonitor).registerCallback(
keyguardUpdateMonitorCallbackArgumentCaptor.capture());
mMessageAreaController.onViewDetached();
verify(mKeyguardUpdateMonitor).removeCallback(
eq(keyguardUpdateMonitorCallbackArgumentCaptor.getValue()));
}
@Test
public void testClearsTextField() {
mMessageAreaController.setMessage("");
verify(mKeyguardMessageArea).setMessage("");
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2016 The Android Open Source Project
* 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.
@@ -11,65 +11,60 @@
* 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
* limitations under the License.
*/
package com.android.keyguard;
import static junit.framework.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static com.google.common.truth.Truth.assertThat;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.view.View;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.policy.ConfigurationController;
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 KeyguardMessageAreaTest extends SysuiTestCase {
@Mock
private ConfigurationController mConfigurationController;
@Mock
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private KeyguardMessageArea mMessageArea;
private KeyguardMessageArea mKeyguardMessageArea;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mMessageArea = new KeyguardMessageArea(mContext, null, mKeyguardUpdateMonitor,
mConfigurationController);
waitForIdleSync();
mKeyguardMessageArea = new KeyguardMessageArea(mContext, null);
mKeyguardMessageArea.setBouncerVisible(true);
}
@Test
public void onAttachedToWindow_registersConfigurationCallback() {
mMessageArea.onAttachedToWindow();
verify(mConfigurationController).addCallback(eq(mMessageArea));
mMessageArea.onDetachedFromWindow();
verify(mConfigurationController).removeCallback(eq(mMessageArea));
public void testShowsTextField() {
mKeyguardMessageArea.setVisibility(View.INVISIBLE);
mKeyguardMessageArea.setMessage("oobleck");
assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(mKeyguardMessageArea.getText()).isEqualTo("oobleck");
}
@Test
public void clearFollowedByMessage_keepsMessage() {
mMessageArea.setMessage("");
mMessageArea.setMessage("test");
CharSequence[] messageText = new CharSequence[1];
messageText[0] = mMessageArea.getText();
assertEquals("test", messageText[0]);
public void testHiddenWhenBouncerHidden() {
mKeyguardMessageArea.setBouncerVisible(false);
mKeyguardMessageArea.setVisibility(View.INVISIBLE);
mKeyguardMessageArea.setMessage("oobleck");
assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.INVISIBLE);
assertThat(mKeyguardMessageArea.getText()).isEqualTo("oobleck");
}
@Test
public void testClearsTextField() {
mKeyguardMessageArea.setVisibility(View.VISIBLE);
mKeyguardMessageArea.setMessage("");
assertThat(mKeyguardMessageArea.getVisibility()).isEqualTo(View.INVISIBLE);
assertThat(mKeyguardMessageArea.getText()).isEqualTo("");
}
}

View File

@@ -0,0 +1,84 @@
/*
* 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.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.internal.util.LatencyTracker
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternView
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.Mockito.`when`
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class KeyguardPatternViewControllerTest : SysuiTestCase() {
@Mock
private lateinit var mKeyguardPatternView: KeyguardPatternView
@Mock
private lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock
private lateinit var mSecurityMode: KeyguardSecurityModel.SecurityMode
@Mock
private lateinit var mLockPatternUtils: LockPatternUtils
@Mock
private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback
@Mock
private lateinit var mLatencyTracker: LatencyTracker
@Mock
private lateinit
var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory
@Mock
private lateinit var mKeyguardMessageArea: KeyguardMessageArea
@Mock
private lateinit var mKeyguardMessageAreaController: KeyguardMessageAreaController
@Mock
private lateinit var mLockPatternView: LockPatternView
private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
`when`(mKeyguardPatternView.isAttachedToWindow).thenReturn(true)
`when`(mKeyguardPatternView.findViewById<KeyguardMessageArea>(R.id.keyguard_message_area))
.thenReturn(mKeyguardMessageArea)
`when`(mKeyguardPatternView.findViewById<LockPatternView>(R.id.lockPatternView))
.thenReturn(mLockPatternView)
`when`(mKeyguardMessageAreaControllerFactory.create(mKeyguardMessageArea))
.thenReturn(mKeyguardMessageAreaController)
mKeyguardPatternViewController = KeyguardPatternViewController(mKeyguardPatternView,
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
mLatencyTracker, mKeyguardMessageAreaControllerFactory)
}
@Test
fun onPause_clearsTextField() {
mKeyguardPatternViewController.init()
mKeyguardPatternViewController.onPause()
verify(mKeyguardMessageAreaController).setMessage("")
}
}

View File

@@ -1,59 +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 androidx.test.filters.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.LayoutInflater
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.policy.ConfigurationController
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class KeyguardPatternViewTest : SysuiTestCase() {
private lateinit var mKeyguardPatternView: KeyguardPatternView
private lateinit var mSecurityMessage: KeyguardMessageArea
@Before
fun setup() {
val inflater = LayoutInflater.from(context)
mDependency.injectMockDependency(KeyguardUpdateMonitor::class.java)
mKeyguardPatternView = inflater.inflate(R.layout.keyguard_pattern_view, null)
as KeyguardPatternView
mSecurityMessage = KeyguardMessageArea(mContext, null,
mock(KeyguardUpdateMonitor::class.java), mock(ConfigurationController::class.java))
mKeyguardPatternView.mSecurityMessageDisplay = mSecurityMessage
}
@Test
fun onPause_clearsTextField() {
mSecurityMessage.setMessage("an old message")
mKeyguardPatternView.onPause()
assertThat(mSecurityMessage.text).isEqualTo("")
}
}

View File

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

View File

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

View File

@@ -31,9 +31,7 @@ import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardDisplayManager.KeyguardPresentation;
import com.android.keyguard.dagger.KeyguardStatusViewComponent;
import com.android.systemui.R;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.InjectionInflationController;
import org.junit.After;
import org.junit.Before;
@@ -65,7 +63,6 @@ public class KeyguardPresentationTest extends SysuiTestCase {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
when(mMockKeyguardClockSwitch.getContext()).thenReturn(mContext);
when(mMockKeyguardSliceView.getContext()).thenReturn(mContext);
when(mMockKeyguardStatusView.getContext()).thenReturn(mContext);
@@ -77,11 +74,7 @@ public class KeyguardPresentationTest extends SysuiTestCase {
allowTestableLooperAsMainThread();
InjectionInflationController inflationController = new InjectionInflationController(
SystemUIFactory.getInstance()
.getSysUIComponent()
.createViewInstanceCreatorFactory());
mLayoutInflater = inflationController.injectable(LayoutInflater.from(mContext));
mLayoutInflater = LayoutInflater.from(mContext);
mLayoutInflater.setPrivateFactory(new LayoutInflater.Factory2() {
@Override

View File

@@ -0,0 +1,130 @@
/*
* 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.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.WindowInsetsController;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper()
public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
@Rule
public MockitoRule mRule = MockitoJUnit.rule();
@Mock
private KeyguardSecurityContainer mView;
@Mock
private AdminSecondaryLockScreenController.Factory mAdminSecondaryLockScreenControllerFactory;
@Mock
private AdminSecondaryLockScreenController mAdminSecondaryLockScreenController;
@Mock
private LockPatternUtils mLockPatternUtils;
@Mock
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock
private KeyguardSecurityModel mKeyguardSecurityModel;
@Mock
private MetricsLogger mMetricsLogger;
@Mock
private UiEventLogger mUiEventLogger;
@Mock
private KeyguardStateController mKeyguardStateController;
@Mock
private KeyguardInputViewController mInputViewController;
@Mock
private KeyguardSecurityContainer.SecurityCallback mSecurityCallback;
@Mock
private WindowInsetsController mWindowInsetsController;
@Mock
private KeyguardSecurityViewFlipper mSecurityViewFlipper;
@Mock
private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController;
private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
@Before
public void setup() {
when(mAdminSecondaryLockScreenControllerFactory.create(any(KeyguardSecurityCallback.class)))
.thenReturn(mAdminSecondaryLockScreenController);
when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController);
mKeyguardSecurityContainerController = new KeyguardSecurityContainerController(
mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
mKeyguardStateController, mKeyguardSecurityViewFlipperController);
mKeyguardSecurityContainerController.setSecurityCallback(mSecurityCallback);
}
@Test
public void showSecurityScreen_canInflateAllModes() {
SecurityMode[] modes = SecurityMode.values();
for (SecurityMode mode : modes) {
when(mInputViewController.getSecurityMode()).thenReturn(mode);
mKeyguardSecurityContainerController.showSecurityScreen(mode);
if (mode == SecurityMode.Invalid) {
verify(mKeyguardSecurityViewFlipperController, never()).getSecurityView(
any(SecurityMode.class), any(KeyguardSecurityCallback.class));
} else {
verify(mKeyguardSecurityViewFlipperController).getSecurityView(
eq(mode), any(KeyguardSecurityCallback.class));
}
}
}
@Test
public void startDisappearAnimation_animatesKeyboard() {
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
SecurityMode.Password);
when(mInputViewController.getSecurityMode()).thenReturn(
SecurityMode.Password);
when(mKeyguardSecurityViewFlipperController.getSecurityView(
eq(SecurityMode.Password), any(KeyguardSecurityCallback.class)))
.thenReturn(mInputViewController);
mKeyguardSecurityContainerController.showPrimarySecurityScreen(false /* turningOff */);
mKeyguardSecurityContainerController.startDisappearAnimation(null);
verify(mInputViewController).startDisappearAnimation(eq(null));
}
}

View File

@@ -19,23 +19,19 @@ package com.android.keyguard;
import static android.view.WindowInsets.Type.ime;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Context;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.LayoutInflater;
import android.view.WindowInsetsController;
import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
import org.junit.Rule;
@@ -50,68 +46,26 @@ import org.mockito.junit.MockitoRule;
@TestableLooper.RunWithLooper()
public class KeyguardSecurityContainerTest extends SysuiTestCase {
@Mock
private KeyguardSecurityModel mKeyguardSecurityModel;
@Mock
private KeyguardStateController mKeyguardStateController;
@Mock
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock
private KeyguardSecurityContainer.SecurityCallback mSecurityCallback;
@Mock
private KeyguardSecurityView mSecurityView;
@Rule
public MockitoRule mRule = MockitoJUnit.rule();
@Mock
private WindowInsetsController mWindowInsetsController;
@Mock
private KeyguardSecurityViewFlipper mSecurityViewFlipper;
@Rule
public MockitoRule mRule = MockitoJUnit.rule();
private KeyguardSecurityContainer mKeyguardSecurityContainer;
@Before
public void setup() {
mDependency.injectTestDependency(KeyguardStateController.class, mKeyguardStateController);
mDependency.injectTestDependency(KeyguardSecurityModel.class, mKeyguardSecurityModel);
mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mKeyguardUpdateMonitor);
mKeyguardSecurityContainer = new KeyguardSecurityContainer(getContext()) {
@Override
protected KeyguardSecurityView getSecurityView(
KeyguardSecurityModel.SecurityMode securityMode) {
return mSecurityView;
}
};
mKeyguardSecurityContainer.mSecurityViewFlipper = mSecurityViewFlipper;
when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController);
mKeyguardSecurityContainer.setSecurityCallback(mSecurityCallback);
}
@Test
public void showSecurityScreen_canInflateAllModes() {
Context context = getContext();
for (int theme : new int[] {R.style.Theme_SystemUI, R.style.Theme_SystemUI_Light}) {
context.setTheme(theme);
final LayoutInflater inflater = LayoutInflater.from(context);
KeyguardSecurityModel.SecurityMode[] modes =
KeyguardSecurityModel.SecurityMode.values();
for (KeyguardSecurityModel.SecurityMode mode : modes) {
final int resId = mKeyguardSecurityContainer.getLayoutIdFor(mode);
if (resId == 0) {
continue;
}
inflater.inflate(resId, null /* root */, false /* attach */);
}
}
mKeyguardSecurityContainer = new KeyguardSecurityContainer(getContext());
mKeyguardSecurityContainer.mSecurityViewFlipper = mSecurityViewFlipper;
}
@Test
public void startDisappearAnimation_animatesKeyboard() {
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
KeyguardSecurityModel.SecurityMode.Password);
mKeyguardSecurityContainer.showPrimarySecurityScreen(false /* turningOff */);
mKeyguardSecurityContainer.startDisappearAnimation(null);
verify(mSecurityView).startDisappearAnimation(eq(null));
mKeyguardSecurityContainer.startDisappearAnimation(SecurityMode.Password);
verify(mWindowInsetsController).controlWindowInsetsAnimation(eq(ime()), anyLong(), any(),
any(), any());
}

View File

@@ -0,0 +1,102 @@
/*
* 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.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.view.WindowInsetsController;
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper()
public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase {
@Rule
public MockitoRule mRule = MockitoJUnit.rule();
@Mock
private KeyguardSecurityViewFlipper mView;
@Mock
private LayoutInflater mLayoutInflater;
@Mock
private KeyguardInputViewController.Factory mKeyguardSecurityViewControllerFactory;
@Mock
private KeyguardInputViewController mKeyguardInputViewController;
@Mock
private KeyguardInputView mInputView;
@Mock
private WindowInsetsController mWindowInsetsController;
@Mock
private KeyguardSecurityCallback mKeyguardSecurityCallback;
private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController;
@Before
public void setup() {
when(mKeyguardSecurityViewControllerFactory.create(
any(KeyguardInputView.class), any(SecurityMode.class),
any(KeyguardSecurityCallback.class)))
.thenReturn(mKeyguardInputViewController);
when(mView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
mKeyguardSecurityViewFlipperController = new KeyguardSecurityViewFlipperController(mView,
mLayoutInflater, mKeyguardSecurityViewControllerFactory);
}
@Test
public void showSecurityScreen_canInflateAllModes() {
SecurityMode[] modes = SecurityMode.values();
// Always return an invalid controller so that we're always making a new one.
when(mKeyguardInputViewController.getSecurityMode()).thenReturn(SecurityMode.Invalid);
for (SecurityMode mode : modes) {
reset(mLayoutInflater);
when(mLayoutInflater.inflate(anyInt(), eq(mView), eq(false)))
.thenReturn(mInputView);
mKeyguardSecurityViewFlipperController.getSecurityView(mode, mKeyguardSecurityCallback);
if (mode == SecurityMode.Invalid || mode == SecurityMode.None) {
verify(mLayoutInflater, never()).inflate(
anyInt(), any(ViewGroup.class), anyBoolean());
} else {
verify(mLayoutInflater).inflate(anyInt(), eq(mView), eq(false));
}
}
}
}

View File

@@ -24,9 +24,7 @@ import android.testing.TestableLooper.RunWithLooper;
import android.view.LayoutInflater;
import com.android.systemui.R;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.util.InjectionInflationController;
import org.junit.Before;
import org.junit.Test;
@@ -50,13 +48,7 @@ public class KeyguardStatusViewTest extends SysuiTestCase {
@Before
public void setUp() {
allowTestableLooperAsMainThread();
mDependency.injectMockDependency(KeyguardUpdateMonitor.class);
InjectionInflationController inflationController = new InjectionInflationController(
SystemUIFactory.getInstance()
.getSysUIComponent()
.createViewInstanceCreatorFactory());
LayoutInflater layoutInflater = inflationController
.injectable(LayoutInflater.from(getContext()));
LayoutInflater layoutInflater = LayoutInflater.from(getContext());
mKeyguardStatusView =
(KeyguardStatusView) layoutInflater.inflate(R.layout.keyguard_status_view, null);
org.mockito.MockitoAnnotations.initMocks(this);