Merge changes I04f6a54e,Ifb505f6d,Ia9ec3ce2,I97b201c6,Ia12e8cd6, ... into udc-dev am: 9c7f11991d am: b1d590e6b0

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23120507

Change-Id: Iee956b942fe9e446ba586154bef73a7787254d4d
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Chandru S
2023-05-13 07:06:56 +00:00
committed by Automerger Merge Worker
52 changed files with 2262 additions and 153 deletions

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2023 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<com.android.keyguard.BouncerKeyguardMessageArea
android:id="@+id/bouncer_primary_message_area"
style="@style/Keyguard.Bouncer.PrimaryMessage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/keyguard_lock_padding"
android:focusable="true"
/>
<com.android.keyguard.BouncerKeyguardMessageArea
android:id="@+id/bouncer_secondary_message_area"
style="@style/Keyguard.Bouncer.SecondaryMessage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/secondary_message_padding"
android:focusable="true" />
</merge>

View File

@@ -29,6 +29,13 @@
>
<include layout="@layout/keyguard_bouncer_message_area"/>
<com.android.systemui.keyguard.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
/>
<Space
android:layout_width="match_parent"
android:layout_height="0dp"

View File

@@ -33,6 +33,12 @@
android:clipToPadding="false">
<include layout="@layout/keyguard_bouncer_message_area"/>
<com.android.systemui.keyguard.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/pattern_container"
android:layout_width="match_parent"

View File

@@ -29,6 +29,12 @@
androidprv:layout_maxWidth="@dimen/keyguard_security_width">
<include layout="@layout/keyguard_bouncer_message_area"/>
<com.android.systemui.keyguard.bouncer.ui.BouncerMessageView
android:id="@+id/bouncer_message_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/pin_container"
android:layout_width="match_parent"

View File

@@ -23,6 +23,26 @@
<style name="Keyguard.TextView" parent="@android:style/Widget.DeviceDefault.TextView">
<item name="android:textSize">@dimen/kg_status_line_font_size</item>
<item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
<item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
</style>
<style name="Keyguard.Bouncer.PrimaryMessage" parent="Theme.SystemUI">
<item name="android:textSize">18sp</item>
<item name="android:lineHeight">24dp</item>
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
<item name="android:textColor">?androidprv:attr/materialColorOnSurface</item>
<item name="android:singleLine">true</item>
<item name="android:textAlignment">center</item>
<item name="android:ellipsize">marquee</item>
</style>
<style name="Keyguard.Bouncer.SecondaryMessage" parent="Theme.SystemUI">
<item name="android:textSize">14sp</item>
<item name="android:lineHeight">20dp</item>
<item name="android:maxLines">@integer/bouncer_secondary_message_lines</item>
<item name="android:lines">@integer/bouncer_secondary_message_lines</item>
<item name="android:textAlignment">center</item>
<item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
<item name="android:ellipsize">end</item>
<item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item>
</style>
<style name="Keyguard.TextView.EmergencyButton" parent="Theme.SystemUI">
<item name="android:textColor">?androidprv:attr/materialColorOnTertiaryFixed</item>

View File

@@ -577,6 +577,9 @@
<!-- Whether to show the side fps hint while on bouncer -->
<bool name="config_show_sidefps_hint_on_bouncer">true</bool>
<!-- Max number of lines we want to show for the bouncer secondary message -->
<integer name="bouncer_secondary_message_lines">2</integer>
<!-- Whether to use the split 2-column notification shade -->
<bool name="config_use_split_notification_shade">false</bool>

View File

@@ -807,6 +807,7 @@
<!-- The width/height of the unlock icon view on keyguard. -->
<dimen name="keyguard_lock_height">42dp</dimen>
<dimen name="keyguard_lock_padding">20dp</dimen>
<dimen name="secondary_message_padding">8dp</dimen>
<dimen name="keyguard_security_container_padding_top">20dp</dimen>

View File

@@ -22,8 +22,6 @@ import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.content.Context
import android.content.res.ColorStateList
import android.content.res.TypedArray
import android.graphics.Color
import android.util.AttributeSet
import android.view.View
import com.android.app.animation.Interpolators
@@ -41,12 +39,28 @@ open class BouncerKeyguardMessageArea(context: Context?, attrs: AttributeSet?) :
protected open val SHOW_DURATION_MILLIS = 150L
protected open val HIDE_DURATION_MILLIS = 200L
override fun onFinishInflate() {
super.onFinishInflate()
mDefaultColorState = getColorInStyle()
}
private fun getColorInStyle(): ColorStateList? {
val styledAttributes =
context.obtainStyledAttributes(styleResId, intArrayOf(android.R.attr.textColor))
var colorStateList: ColorStateList? = null
if (styledAttributes != null) {
colorStateList = styledAttributes.getColorStateList(0)
}
styledAttributes.recycle()
return colorStateList
}
override fun updateTextColor() {
var colorState = mDefaultColorState
mNextMessageColorState?.defaultColor?.let { color ->
if (color != DEFAULT_COLOR) {
colorState = mNextMessageColorState
mNextMessageColorState = ColorStateList.valueOf(DEFAULT_COLOR)
mNextMessageColorState = mDefaultColorState ?: ColorStateList.valueOf(DEFAULT_COLOR)
}
}
setTextColor(colorState)
@@ -57,15 +71,12 @@ open class BouncerKeyguardMessageArea(context: Context?, attrs: AttributeSet?) :
}
override fun onThemeChanged() {
val array: TypedArray = mContext.obtainStyledAttributes(intArrayOf(TITLE))
val newTextColors: ColorStateList = ColorStateList.valueOf(array.getColor(0, Color.RED))
array.recycle()
mDefaultColorState = newTextColors
mDefaultColorState = getColorInStyle() ?: Utils.getColorAttr(context, TITLE)
super.onThemeChanged()
}
override fun reloadColor() {
mDefaultColorState = Utils.getColorAttr(context, TITLE)
mDefaultColorState = getColorInStyle() ?: Utils.getColorAttr(context, TITLE)
super.reloadColor()
}

View File

@@ -22,6 +22,8 @@ import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
import android.view.View;
import androidx.annotation.CallSuper;
import com.android.internal.widget.LockscreenCredential;
import com.android.systemui.R;
@@ -48,7 +50,9 @@ public abstract class KeyguardAbsKeyInputView extends KeyguardInputView {
protected abstract void resetState();
@Override
@CallSuper
protected void onFinishInflate() {
super.onFinishInflate();
mEcaView = findViewById(R.id.keyguard_selector_fade_container);
}

View File

@@ -37,6 +37,7 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingClassifier;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import java.util.HashMap;
import java.util.Map;
@@ -77,9 +78,10 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey
KeyguardSecurityCallback keyguardSecurityCallback,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker, FalsingCollector falsingCollector,
EmergencyButtonController emergencyButtonController) {
EmergencyButtonController emergencyButtonController,
FeatureFlags featureFlags) {
super(view, securityMode, keyguardSecurityCallback, emergencyButtonController,
messageAreaControllerFactory);
messageAreaControllerFactory, featureFlags);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mLatencyTracker = latencyTracker;

View File

@@ -21,11 +21,14 @@ import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.systemui.R;
/**
* A Base class for all Keyguard password/pattern/pin related inputs.
@@ -33,6 +36,9 @@ import com.android.internal.jank.InteractionJankMonitor;
public abstract class KeyguardInputView extends LinearLayout {
private Runnable mOnFinishImeAnimationRunnable;
@Nullable
private View mBouncerMessageView;
public KeyguardInputView(Context context) {
super(context);
}
@@ -87,6 +93,18 @@ public abstract class KeyguardInputView extends LinearLayout {
mOnFinishImeAnimationRunnable = onFinishImeAnimationRunnable;
}
@Override
@CallSuper
protected void onFinishInflate() {
super.onFinishInflate();
mBouncerMessageView = findViewById(R.id.bouncer_message_view);
}
@Nullable
public final View getBouncerMessageView() {
return mBouncerMessageView;
}
public void runOnFinishImeAnimationRunnable() {
if (mOnFinishImeAnimationRunnable != null) {
mOnFinishImeAnimationRunnable.run();

View File

@@ -16,6 +16,7 @@
package com.android.keyguard;
import android.annotation.CallSuper;
import android.annotation.Nullable;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -31,6 +32,7 @@ import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -53,16 +55,19 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
// (e.g. face unlock). This avoids unwanted asynchronous events from messing with the
// state for the current security method.
private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() {};
private final FeatureFlags mFeatureFlags;
protected KeyguardInputViewController(T view, SecurityMode securityMode,
KeyguardSecurityCallback keyguardSecurityCallback,
EmergencyButtonController emergencyButtonController,
@Nullable KeyguardMessageAreaController.Factory messageAreaControllerFactory) {
@Nullable KeyguardMessageAreaController.Factory messageAreaControllerFactory,
FeatureFlags featureFlags) {
super(view);
mSecurityMode = securityMode;
mKeyguardSecurityCallback = keyguardSecurityCallback;
mEmergencyButton = view == null ? null : view.findViewById(R.id.emergency_call_button);
mEmergencyButtonController = emergencyButtonController;
mFeatureFlags = featureFlags;
if (messageAreaControllerFactory != null) {
try {
BouncerKeyguardMessageArea kma = view.requireViewById(R.id.bouncer_message_area);
@@ -82,9 +87,21 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
}
@Override
@CallSuper
protected void onViewAttached() {
updateMessageAreaVisibility();
}
private void updateMessageAreaVisibility() {
if (mMessageAreaController == null) return;
if (mFeatureFlags.isEnabled(Flags.REVAMPED_BOUNCER_MESSAGES)) {
mMessageAreaController.disable();
} else {
mMessageAreaController.setIsVisible(true);
}
}
@Override
protected void onViewDetached() {
}
@@ -208,14 +225,14 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mLatencyTracker, mFalsingCollector,
emergencyButtonController, mMessageAreaControllerFactory,
mDevicePostureController);
mDevicePostureController, mFeatureFlags);
} else if (keyguardInputView instanceof KeyguardPasswordView) {
return new KeyguardPasswordViewController((KeyguardPasswordView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mInputMethodManager, emergencyButtonController, mMainExecutor, mResources,
mFalsingCollector, mKeyguardViewController);
mFalsingCollector, mKeyguardViewController,
mFeatureFlags);
} else if (keyguardInputView instanceof KeyguardPINView) {
return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
@@ -227,13 +244,13 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
emergencyButtonController);
emergencyButtonController, mFeatureFlags);
} else if (keyguardInputView instanceof KeyguardSimPukView) {
return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
emergencyButtonController);
emergencyButtonController, mFeatureFlags);
}
throw new RuntimeException("Unable to find controller for " + keyguardInputView);

View File

@@ -44,11 +44,18 @@ public abstract class KeyguardMessageArea extends TextView implements SecurityMe
private ViewGroup mContainer;
private int mTopMargin;
protected boolean mAnimate;
private final int mStyleResId;
private boolean mIsDisabled = false;
public KeyguardMessageArea(Context context, AttributeSet attrs) {
super(context, attrs);
setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug
if (attrs != null) {
mStyleResId = attrs.getStyleAttribute();
} else {
// Set to default reference style if the component is used without setting "style" attr
mStyleResId = R.style.Keyguard_TextView;
}
onThemeChanged();
}
@@ -82,13 +89,17 @@ public abstract class KeyguardMessageArea extends TextView implements SecurityMe
}
void onDensityOrFontScaleChanged() {
TypedArray array = mContext.obtainStyledAttributes(R.style.Keyguard_TextView, new int[] {
TypedArray array = mContext.obtainStyledAttributes(getStyleResId(), new int[] {
android.R.attr.textSize
});
setTextSize(TypedValue.COMPLEX_UNIT_PX, array.getDimensionPixelSize(0, 0));
array.recycle();
}
protected int getStyleResId() {
return mStyleResId;
}
@Override
public void setMessage(CharSequence msg, boolean animate) {
if (!TextUtils.isEmpty(msg)) {
@@ -118,6 +129,10 @@ public abstract class KeyguardMessageArea extends TextView implements SecurityMe
}
void update() {
if (mIsDisabled) {
setVisibility(GONE);
return;
}
CharSequence status = mMessage;
setVisibility(TextUtils.isEmpty(status) || (!mIsVisible) ? INVISIBLE : VISIBLE);
setText(status);
@@ -136,4 +151,17 @@ public abstract class KeyguardMessageArea extends TextView implements SecurityMe
/** Set the text color */
protected abstract void updateTextColor();
/**
* Mark this view with {@link android.view.View#GONE} visibility to remove this from the layout
* of the view. Any calls to {@link #setIsVisible(boolean)} after this will be a no-op.
*/
public void disable() {
mIsDisabled = true;
update();
}
public boolean isDisabled() {
return mIsDisabled;
}
}

View File

@@ -133,6 +133,14 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea>
mView.setIsVisible(isVisible);
}
/**
* Mark this view with {@link View#GONE} visibility to remove this from the layout of the view.
* Any calls to {@link #setIsVisible(boolean)} after this will be a no-op.
*/
public void disable() {
mView.disable();
}
public void setMessage(CharSequence s) {
setMessage(s, true);
}
@@ -141,6 +149,9 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea>
* Sets a message to the underlying text view.
*/
public void setMessage(CharSequence s, boolean animate) {
if (mView.isDisabled()) {
return;
}
mView.setMessage(s, animate);
}

View File

@@ -51,7 +51,7 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
private View[][] mViews;
private int mYTrans;
private int mYTransOffset;
private View mBouncerMessageView;
private View mBouncerMessageArea;
@DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN;
public static final long ANIMATION_DURATION = 650;
@@ -145,7 +145,7 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
super.onFinishInflate();
mContainer = findViewById(R.id.pin_container);
mBouncerMessageView = findViewById(R.id.bouncer_message_area);
mBouncerMessageArea = findViewById(R.id.bouncer_message_area);
mViews = new View[][]{
new View[]{
findViewById(R.id.row0), null, null
@@ -221,9 +221,9 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
Interpolator legacyDecelerate = Interpolators.LEGACY_DECELERATE;
float standardProgress = standardDecelerate.getInterpolation(progress);
mBouncerMessageView.setTranslationY(
mBouncerMessageArea.setTranslationY(
mYTrans - mYTrans * standardProgress);
mBouncerMessageView.setAlpha(standardProgress);
mBouncerMessageArea.setAlpha(standardProgress);
for (int i = 0; i < mViews.length; i++) {
View[] row = mViews[i];

View File

@@ -40,6 +40,7 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.util.concurrency.DelayableExecutor;
import java.util.List;
@@ -104,10 +105,11 @@ public class KeyguardPasswordViewController
@Main DelayableExecutor mainExecutor,
@Main Resources resources,
FalsingCollector falsingCollector,
KeyguardViewController keyguardViewController) {
KeyguardViewController keyguardViewController,
FeatureFlags featureFlags) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, falsingCollector,
emergencyButtonController);
emergencyButtonController, featureFlags);
mKeyguardSecurityCallback = keyguardSecurityCallback;
mInputMethodManager = inputMethodManager;
mMainExecutor = mainExecutor;

View File

@@ -38,6 +38,7 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingClassifier;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.statusbar.policy.DevicePostureController;
import java.util.HashMap;
@@ -196,9 +197,9 @@ public class KeyguardPatternViewController
FalsingCollector falsingCollector,
EmergencyButtonController emergencyButtonController,
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
DevicePostureController postureController) {
DevicePostureController postureController, FeatureFlags featureFlags) {
super(view, securityMode, keyguardSecurityCallback, emergencyButtonController,
messageAreaControllerFactory);
messageAreaControllerFactory, featureFlags);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mLatencyTracker = latencyTracker;

View File

@@ -34,6 +34,8 @@ import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.View;
import androidx.annotation.CallSuper;
import com.android.app.animation.Interpolators;
import com.android.internal.widget.LockscreenCredential;
import com.android.systemui.R;
@@ -147,7 +149,9 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView
}
@Override
@CallSuper
protected void onFinishInflate() {
super.onFinishInflate();
mPasswordEntry = findViewById(getPasswordTextViewId());
// Set selected property on so the view can send accessibility events.

View File

@@ -27,6 +27,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView>
extends KeyguardAbsKeyInputViewController<T> {
@@ -58,10 +59,11 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB
LatencyTracker latencyTracker,
LiftToActivateListener liftToActivateListener,
EmergencyButtonController emergencyButtonController,
FalsingCollector falsingCollector) {
FalsingCollector falsingCollector,
FeatureFlags featureFlags) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, falsingCollector,
emergencyButtonController);
emergencyButtonController, featureFlags);
mLiftToActivateListener = liftToActivateListener;
mFalsingCollector = falsingCollector;
mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());

View File

@@ -56,7 +56,7 @@ public class KeyguardPinViewController
FeatureFlags featureFlags) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
emergencyButtonController, falsingCollector);
emergencyButtonController, falsingCollector, featureFlags);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mPostureController = postureController;
mLockPatternUtils = lockPatternUtils;

View File

@@ -30,6 +30,7 @@ abstract class KeyguardSimInputView(context: Context, attrs: AttributeSet) :
private var disableESimButton: KeyguardEsimArea? = null
override fun onFinishInflate() {
super.onFinishInflate()
simImageView = findViewById(R.id.keyguard_sim)
disableESimButton = findViewById(R.id.keyguard_esim_area)
super.onFinishInflate()

View File

@@ -41,6 +41,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
public class KeyguardSimPinViewController
extends KeyguardPinBasedInputViewController<KeyguardSimPinView> {
@@ -81,10 +82,10 @@ public class KeyguardSimPinViewController
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
EmergencyButtonController emergencyButtonController) {
EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
emergencyButtonController, falsingCollector);
emergencyButtonController, falsingCollector, featureFlags);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mTelephonyManager = telephonyManager;
mSimImageView = mView.findViewById(R.id.keyguard_sim);

View File

@@ -38,6 +38,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
public class KeyguardSimPukViewController
extends KeyguardPinBasedInputViewController<KeyguardSimPukView> {
@@ -85,10 +86,10 @@ public class KeyguardSimPukViewController
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
EmergencyButtonController emergencyButtonController) {
EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
emergencyButtonController, falsingCollector);
emergencyButtonController, falsingCollector, featureFlags);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mTelephonyManager = telephonyManager;
mSimImageView = mView.findViewById(R.id.keyguard_sim);

View File

@@ -2635,6 +2635,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
return mAuthController.isUdfpsSupported();
}
/**
* @return true if the FP sensor is non-UDFPS and the device can be unlocked using fingerprint
* at this moment.
*/
public boolean isFingerprintAllowedInBouncer() {
return !isUdfpsSupported() && isUnlockingWithFingerprintAllowed();
}
/**
* @return true if there's at least one sfps enrollment for the current user.
*/

View File

@@ -14,9 +14,10 @@
* limitations under the License.
*/
package com.android.keyguard
package com.android.systemui.keyguard.bouncer.data.factory
import android.annotation.IntDef
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEFAULT
@@ -34,6 +35,7 @@ import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R.string.bouncer_face_not_recognized
import com.android.systemui.R.string.keyguard_enter_password
import com.android.systemui.R.string.keyguard_enter_pattern
@@ -71,10 +73,86 @@ import com.android.systemui.R.string.kg_wrong_input_try_fp_suggestion
import com.android.systemui.R.string.kg_wrong_password_try_again
import com.android.systemui.R.string.kg_wrong_pattern_try_again
import com.android.systemui.R.string.kg_wrong_pin_try_again
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
import com.android.systemui.keyguard.bouncer.shared.model.Message
import javax.inject.Inject
typealias BouncerMessage = Pair<Int, Int>
@SysUISingleton
class BouncerMessageFactory
@Inject
constructor(
private val updateMonitor: KeyguardUpdateMonitor,
private val securityModel: KeyguardSecurityModel,
) {
fun emptyBouncerMessage(): BouncerMessage = Pair(0, 0)
fun createFromPromptReason(
@BouncerPromptReason reason: Int,
userId: Int,
): BouncerMessageModel? {
val pair =
getBouncerMessage(
reason,
securityModel.getSecurityMode(userId),
updateMonitor.isFingerprintAllowedInBouncer
)
return pair?.let {
BouncerMessageModel(
message = Message(messageResId = pair.first),
secondaryMessage = Message(messageResId = pair.second)
)
}
}
fun createFromString(
primaryMsg: String? = null,
secondaryMsg: String? = null
): BouncerMessageModel =
BouncerMessageModel(
message = primaryMsg?.let { Message(message = it) },
secondaryMessage = secondaryMsg?.let { Message(message = it) },
)
/**
* Helper method that provides the relevant bouncer message that should be shown for different
* scenarios indicated by [reason]. [securityMode] & [fpAllowedInBouncer] parameters are used to
* provide a more specific message.
*/
private fun getBouncerMessage(
@BouncerPromptReason reason: Int,
securityMode: SecurityMode,
fpAllowedInBouncer: Boolean = false
): Pair<Int, Int>? {
return when (reason) {
PROMPT_REASON_RESTART -> authRequiredAfterReboot(securityMode)
PROMPT_REASON_TIMEOUT -> authRequiredAfterPrimaryAuthTimeout(securityMode)
PROMPT_REASON_DEVICE_ADMIN -> authRequiredAfterAdminLockdown(securityMode)
PROMPT_REASON_USER_REQUEST -> authRequiredAfterUserLockdown(securityMode)
PROMPT_REASON_AFTER_LOCKOUT -> biometricLockout(securityMode)
PROMPT_REASON_PREPARE_FOR_UPDATE -> authRequiredForUnattendedUpdate(securityMode)
PROMPT_REASON_FINGERPRINT_LOCKED_OUT -> fingerprintUnlockUnavailable(securityMode)
PROMPT_REASON_FACE_LOCKED_OUT -> faceUnlockUnavailable(securityMode)
PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT ->
if (fpAllowedInBouncer) incorrectSecurityInputWithFingerprint(securityMode)
else incorrectSecurityInput(securityMode)
PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT ->
if (fpAllowedInBouncer) nonStrongAuthTimeoutWithFingerprintAllowed(securityMode)
else nonStrongAuthTimeout(securityMode)
PROMPT_REASON_TRUSTAGENT_EXPIRED ->
if (fpAllowedInBouncer) trustAgentDisabledWithFingerprintAllowed(securityMode)
else trustAgentDisabled(securityMode)
PROMPT_REASON_INCORRECT_FACE_INPUT ->
if (fpAllowedInBouncer) incorrectFaceInputWithFingerprintAllowed(securityMode)
else incorrectFaceInput(securityMode)
PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT -> incorrectFingerprintInput(securityMode)
PROMPT_REASON_DEFAULT ->
if (fpAllowedInBouncer) defaultMessageWithFingerprint(securityMode)
else defaultMessage(securityMode)
PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT -> primaryAuthLockedOut(securityMode)
else -> null
}
}
}
@Retention(AnnotationRetention.SOURCE)
@IntDef(
@@ -97,48 +175,7 @@ fun emptyBouncerMessage(): BouncerMessage = Pair(0, 0)
)
annotation class BouncerPromptReason
/**
* Helper method that provides the relevant bouncer message that should be shown for different
* scenarios indicated by [reason]. [securityMode] & [fpAllowedInBouncer] parameters are used to
* provide a more specific message.
*/
@JvmOverloads
fun getBouncerMessage(
@BouncerPromptReason reason: Int,
securityMode: SecurityMode,
fpAllowedInBouncer: Boolean = false
): BouncerMessage {
return when (reason) {
PROMPT_REASON_RESTART -> authRequiredAfterReboot(securityMode)
PROMPT_REASON_TIMEOUT -> authRequiredAfterPrimaryAuthTimeout(securityMode)
PROMPT_REASON_DEVICE_ADMIN -> authRequiredAfterAdminLockdown(securityMode)
PROMPT_REASON_USER_REQUEST -> authRequiredAfterUserLockdown(securityMode)
PROMPT_REASON_AFTER_LOCKOUT -> biometricLockout(securityMode)
PROMPT_REASON_PREPARE_FOR_UPDATE -> authRequiredForUnattendedUpdate(securityMode)
PROMPT_REASON_FINGERPRINT_LOCKED_OUT -> fingerprintUnlockUnavailable(securityMode)
PROMPT_REASON_FACE_LOCKED_OUT -> faceUnlockUnavailable(securityMode)
PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT ->
if (fpAllowedInBouncer) incorrectSecurityInputWithFingerprint(securityMode)
else incorrectSecurityInput(securityMode)
PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT ->
if (fpAllowedInBouncer) nonStrongAuthTimeoutWithFingerprintAllowed(securityMode)
else nonStrongAuthTimeout(securityMode)
PROMPT_REASON_TRUSTAGENT_EXPIRED ->
if (fpAllowedInBouncer) trustAgentDisabledWithFingerprintAllowed(securityMode)
else trustAgentDisabled(securityMode)
PROMPT_REASON_INCORRECT_FACE_INPUT ->
if (fpAllowedInBouncer) incorrectFaceInputWithFingerprintAllowed(securityMode)
else incorrectFaceInput(securityMode)
PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT -> incorrectFingerprintInput(securityMode)
PROMPT_REASON_DEFAULT ->
if (fpAllowedInBouncer) defaultMessageWithFingerprint(securityMode)
else defaultMessage(securityMode)
PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT -> primaryAuthLockedOut(securityMode)
else -> emptyBouncerMessage()
}
}
fun defaultMessage(securityMode: SecurityMode): BouncerMessage {
private fun defaultMessage(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
SecurityMode.Password -> Pair(keyguard_enter_password, 0)
@@ -147,7 +184,7 @@ fun defaultMessage(securityMode: SecurityMode): BouncerMessage {
}
}
fun defaultMessageWithFingerprint(securityMode: SecurityMode): BouncerMessage {
private fun defaultMessageWithFingerprint(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, 0)
SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, 0)
@@ -156,7 +193,7 @@ fun defaultMessageWithFingerprint(securityMode: SecurityMode): BouncerMessage {
}
}
fun incorrectSecurityInput(securityMode: SecurityMode): BouncerMessage {
private fun incorrectSecurityInput(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, 0)
SecurityMode.Password -> Pair(kg_wrong_password_try_again, 0)
@@ -165,7 +202,7 @@ fun incorrectSecurityInput(securityMode: SecurityMode): BouncerMessage {
}
}
fun incorrectSecurityInputWithFingerprint(securityMode: SecurityMode): BouncerMessage {
private fun incorrectSecurityInputWithFingerprint(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, kg_wrong_input_try_fp_suggestion)
SecurityMode.Password -> Pair(kg_wrong_password_try_again, kg_wrong_input_try_fp_suggestion)
@@ -174,7 +211,7 @@ fun incorrectSecurityInputWithFingerprint(securityMode: SecurityMode): BouncerMe
}
}
fun incorrectFingerprintInput(securityMode: SecurityMode): BouncerMessage {
private fun incorrectFingerprintInput(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pattern)
SecurityMode.Password -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_password)
@@ -183,7 +220,7 @@ fun incorrectFingerprintInput(securityMode: SecurityMode): BouncerMessage {
}
}
fun incorrectFaceInput(securityMode: SecurityMode): BouncerMessage {
private fun incorrectFaceInput(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pattern)
SecurityMode.Password -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_password)
@@ -192,7 +229,7 @@ fun incorrectFaceInput(securityMode: SecurityMode): BouncerMessage {
}
}
fun incorrectFaceInputWithFingerprintAllowed(securityMode: SecurityMode): BouncerMessage {
private fun incorrectFaceInputWithFingerprintAllowed(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, bouncer_face_not_recognized)
SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, bouncer_face_not_recognized)
@@ -201,7 +238,7 @@ fun incorrectFaceInputWithFingerprintAllowed(securityMode: SecurityMode): Bounce
}
}
fun biometricLockout(securityMode: SecurityMode): BouncerMessage {
private fun biometricLockout(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_bio_too_many_attempts_pattern)
SecurityMode.Password -> Pair(keyguard_enter_password, kg_bio_too_many_attempts_password)
@@ -210,7 +247,7 @@ fun biometricLockout(securityMode: SecurityMode): BouncerMessage {
}
}
fun authRequiredAfterReboot(securityMode: SecurityMode): BouncerMessage {
private fun authRequiredAfterReboot(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_reason_restart_pattern)
SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_reason_restart_password)
@@ -219,7 +256,7 @@ fun authRequiredAfterReboot(securityMode: SecurityMode): BouncerMessage {
}
}
fun authRequiredAfterAdminLockdown(securityMode: SecurityMode): BouncerMessage {
private fun authRequiredAfterAdminLockdown(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_dpm_lock)
SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_after_dpm_lock)
@@ -228,7 +265,7 @@ fun authRequiredAfterAdminLockdown(securityMode: SecurityMode): BouncerMessage {
}
}
fun authRequiredAfterUserLockdown(securityMode: SecurityMode): BouncerMessage {
private fun authRequiredAfterUserLockdown(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_user_lockdown_pattern)
SecurityMode.Password ->
@@ -238,7 +275,7 @@ fun authRequiredAfterUserLockdown(securityMode: SecurityMode): BouncerMessage {
}
}
fun authRequiredForUnattendedUpdate(securityMode: SecurityMode): BouncerMessage {
private fun authRequiredForUnattendedUpdate(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_unattended_update)
SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_unattended_update)
@@ -247,7 +284,7 @@ fun authRequiredForUnattendedUpdate(securityMode: SecurityMode): BouncerMessage
}
}
fun authRequiredAfterPrimaryAuthTimeout(securityMode: SecurityMode): BouncerMessage {
private fun authRequiredAfterPrimaryAuthTimeout(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_pattern_auth_timeout)
SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_password_auth_timeout)
@@ -256,7 +293,7 @@ fun authRequiredAfterPrimaryAuthTimeout(securityMode: SecurityMode): BouncerMess
}
}
fun nonStrongAuthTimeout(securityMode: SecurityMode): BouncerMessage {
private fun nonStrongAuthTimeout(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_auth_timeout)
SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_auth_timeout)
@@ -265,7 +302,7 @@ fun nonStrongAuthTimeout(securityMode: SecurityMode): BouncerMessage {
}
}
fun nonStrongAuthTimeoutWithFingerprintAllowed(securityMode: SecurityMode): BouncerMessage {
private fun nonStrongAuthTimeoutWithFingerprintAllowed(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_prompt_auth_timeout)
SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_prompt_auth_timeout)
@@ -274,7 +311,7 @@ fun nonStrongAuthTimeoutWithFingerprintAllowed(securityMode: SecurityMode): Boun
}
}
fun faceUnlockUnavailable(securityMode: SecurityMode): BouncerMessage {
private fun faceUnlockUnavailable(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_face_locked_out)
SecurityMode.Password -> Pair(keyguard_enter_password, kg_face_locked_out)
@@ -283,7 +320,7 @@ fun faceUnlockUnavailable(securityMode: SecurityMode): BouncerMessage {
}
}
fun fingerprintUnlockUnavailable(securityMode: SecurityMode): BouncerMessage {
private fun fingerprintUnlockUnavailable(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_fp_locked_out)
SecurityMode.Password -> Pair(keyguard_enter_password, kg_fp_locked_out)
@@ -292,7 +329,7 @@ fun fingerprintUnlockUnavailable(securityMode: SecurityMode): BouncerMessage {
}
}
fun trustAgentDisabled(securityMode: SecurityMode): BouncerMessage {
private fun trustAgentDisabled(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_trust_agent_disabled)
SecurityMode.Password -> Pair(keyguard_enter_password, kg_trust_agent_disabled)
@@ -301,7 +338,7 @@ fun trustAgentDisabled(securityMode: SecurityMode): BouncerMessage {
}
}
fun trustAgentDisabledWithFingerprintAllowed(securityMode: SecurityMode): BouncerMessage {
private fun trustAgentDisabledWithFingerprintAllowed(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_trust_agent_disabled)
SecurityMode.Password -> Pair(kg_unlock_with_password_or_fp, kg_trust_agent_disabled)
@@ -310,7 +347,7 @@ fun trustAgentDisabledWithFingerprintAllowed(securityMode: SecurityMode): Bounce
}
}
fun primaryAuthLockedOut(securityMode: SecurityMode): BouncerMessage {
private fun primaryAuthLockedOut(securityMode: SecurityMode): Pair<Int, Int> {
return when (securityMode) {
SecurityMode.Pattern ->
Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_pattern)

View File

@@ -0,0 +1,332 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.keyguard.bouncer.data.repository
import android.hardware.biometrics.BiometricSourceType
import android.hardware.biometrics.BiometricSourceType.FACE
import android.hardware.biometrics.BiometricSourceType.FINGERPRINT
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FACE_LOCKED_OUT
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_FINGERPRINT_LOCKED_OUT
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FACE_INPUT
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NONE
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.bouncer.data.factory.BouncerMessageFactory
import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.TrustRepository
import com.android.systemui.user.data.repository.UserRepository
import javax.inject.Inject
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
/** Provide different sources of messages that needs to be shown on the bouncer. */
interface BouncerMessageRepository {
/**
* Messages that are shown in response to the incorrect security attempts on the bouncer and
* primary authentication method being locked out, along with countdown messages before primary
* auth is active again.
*/
val primaryAuthMessage: Flow<BouncerMessageModel?>
/**
* Help messages that are shown to the user on how to successfully perform authentication using
* face.
*/
val faceAcquisitionMessage: Flow<BouncerMessageModel?>
/**
* Help messages that are shown to the user on how to successfully perform authentication using
* fingerprint.
*/
val fingerprintAcquisitionMessage: Flow<BouncerMessageModel?>
/** Custom message that is displayed when the bouncer is being shown to launch an app. */
val customMessage: Flow<BouncerMessageModel?>
/**
* Messages that are shown in response to biometric authentication attempts through face or
* fingerprint.
*/
val biometricAuthMessage: Flow<BouncerMessageModel?>
/** Messages that are shown when certain auth flags are set. */
val authFlagsMessage: Flow<BouncerMessageModel?>
/** Messages that are show after biometrics are locked out temporarily or permanently */
val biometricLockedOutMessage: Flow<BouncerMessageModel?>
/** Set the value for [primaryAuthMessage] */
fun setPrimaryAuthMessage(value: BouncerMessageModel?)
/** Set the value for [faceAcquisitionMessage] */
fun setFaceAcquisitionMessage(value: BouncerMessageModel?)
/** Set the value for [fingerprintAcquisitionMessage] */
fun setFingerprintAcquisitionMessage(value: BouncerMessageModel?)
/** Set the value for [customMessage] */
fun setCustomMessage(value: BouncerMessageModel?)
/**
* Clear any previously set messages for [primaryAuthMessage], [faceAcquisitionMessage],
* [fingerprintAcquisitionMessage] & [customMessage]
*/
fun clearMessage()
}
@SysUISingleton
class BouncerMessageRepositoryImpl
@Inject
constructor(
trustRepository: TrustRepository,
biometricSettingsRepository: BiometricSettingsRepository,
updateMonitor: KeyguardUpdateMonitor,
private val bouncerMessageFactory: BouncerMessageFactory,
private val userRepository: UserRepository,
fingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
) : BouncerMessageRepository {
private val isFaceEnrolledAndEnabled =
and(
biometricSettingsRepository.isFaceAuthenticationEnabled,
biometricSettingsRepository.isFaceEnrolled
)
private val isFingerprintEnrolledAndEnabled =
and(
biometricSettingsRepository.isFingerprintEnabledByDevicePolicy,
biometricSettingsRepository.isFingerprintEnrolled
)
private val isAnyBiometricsEnabledAndEnrolled =
or(isFaceEnrolledAndEnabled, isFingerprintEnrolledAndEnabled)
private val authFlagsBasedPromptReason: Flow<Int> =
combine(
biometricSettingsRepository.authenticationFlags,
trustRepository.isCurrentUserTrustManaged,
isAnyBiometricsEnabledAndEnrolled,
::Triple
)
.map { (flags, isTrustManaged, biometricsEnrolledAndEnabled) ->
val trustOrBiometricsAvailable = (isTrustManaged || biometricsEnrolledAndEnabled)
return@map if (
trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterReboot
) {
PROMPT_REASON_RESTART
} else if (trustOrBiometricsAvailable && flags.isPrimaryAuthRequiredAfterTimeout) {
PROMPT_REASON_TIMEOUT
} else if (flags.isPrimaryAuthRequiredAfterDpmLockdown) {
PROMPT_REASON_DEVICE_ADMIN
} else if (isTrustManaged && flags.someAuthRequiredAfterUserRequest) {
PROMPT_REASON_TRUSTAGENT_EXPIRED
} else if (isTrustManaged && flags.someAuthRequiredAfterTrustAgentExpired) {
PROMPT_REASON_TRUSTAGENT_EXPIRED
} else if (trustOrBiometricsAvailable && flags.isInUserLockdown) {
PROMPT_REASON_USER_REQUEST
} else if (
trustOrBiometricsAvailable && flags.primaryAuthRequiredForUnattendedUpdate
) {
PROMPT_REASON_PREPARE_FOR_UPDATE
} else if (
trustOrBiometricsAvailable &&
flags.strongerAuthRequiredAfterNonStrongBiometricsTimeout
) {
PROMPT_REASON_NON_STRONG_BIOMETRIC_TIMEOUT
} else {
PROMPT_REASON_NONE
}
}
private val biometricAuthReason: Flow<Int> =
conflatedCallbackFlow {
val callback =
object : KeyguardUpdateMonitorCallback() {
override fun onBiometricAuthFailed(
biometricSourceType: BiometricSourceType?
) {
val promptReason =
if (biometricSourceType == FINGERPRINT)
PROMPT_REASON_INCORRECT_FINGERPRINT_INPUT
else if (
biometricSourceType == FACE && !updateMonitor.isFaceLockedOut
) {
PROMPT_REASON_INCORRECT_FACE_INPUT
} else PROMPT_REASON_NONE
trySendWithFailureLogging(promptReason, TAG, "onBiometricAuthFailed")
}
override fun onBiometricsCleared() {
trySendWithFailureLogging(
PROMPT_REASON_NONE,
TAG,
"onBiometricsCleared"
)
}
override fun onBiometricAcquired(
biometricSourceType: BiometricSourceType?,
acquireInfo: Int
) {
trySendWithFailureLogging(
PROMPT_REASON_NONE,
TAG,
"clearBiometricPrompt for new auth session."
)
}
override fun onBiometricAuthenticated(
userId: Int,
biometricSourceType: BiometricSourceType?,
isStrongBiometric: Boolean
) {
trySendWithFailureLogging(
PROMPT_REASON_NONE,
TAG,
"onBiometricAuthenticated"
)
}
}
updateMonitor.registerCallback(callback)
awaitClose { updateMonitor.removeCallback(callback) }
}
.distinctUntilChanged()
private val _primaryAuthMessage = MutableStateFlow<BouncerMessageModel?>(null)
override val primaryAuthMessage: Flow<BouncerMessageModel?> = _primaryAuthMessage
private val _faceAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
override val faceAcquisitionMessage: Flow<BouncerMessageModel?> = _faceAcquisitionMessage
private val _fingerprintAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
override val fingerprintAcquisitionMessage: Flow<BouncerMessageModel?> =
_fingerprintAcquisitionMessage
private val _customMessage = MutableStateFlow<BouncerMessageModel?>(null)
override val customMessage: Flow<BouncerMessageModel?> = _customMessage
override val biometricAuthMessage: Flow<BouncerMessageModel?> =
biometricAuthReason
.map {
if (it == PROMPT_REASON_NONE) null
else
bouncerMessageFactory.createFromPromptReason(
it,
userRepository.getSelectedUserInfo().id
)
}
.onStart { emit(null) }
.distinctUntilChanged()
override val authFlagsMessage: Flow<BouncerMessageModel?> =
authFlagsBasedPromptReason
.map {
if (it == PROMPT_REASON_NONE) null
else
bouncerMessageFactory.createFromPromptReason(
it,
userRepository.getSelectedUserInfo().id
)
}
.onStart { emit(null) }
.distinctUntilChanged()
// TODO (b/262838215): Replace with DeviceEntryFaceAuthRepository when the new face auth system
// has been launched.
private val faceLockedOut: Flow<Boolean> = conflatedCallbackFlow {
val callback =
object : KeyguardUpdateMonitorCallback() {
override fun onLockedOutStateChanged(biometricSourceType: BiometricSourceType?) {
if (biometricSourceType == FACE) {
trySendWithFailureLogging(
updateMonitor.isFaceLockedOut,
TAG,
"face lock out state changed."
)
}
}
}
updateMonitor.registerCallback(callback)
trySendWithFailureLogging(updateMonitor.isFaceLockedOut, TAG, "face lockout initial value")
awaitClose { updateMonitor.removeCallback(callback) }
}
override val biometricLockedOutMessage: Flow<BouncerMessageModel?> =
combine(fingerprintAuthRepository.isLockedOut, faceLockedOut) { fp, face ->
return@combine if (fp) {
bouncerMessageFactory.createFromPromptReason(
PROMPT_REASON_FINGERPRINT_LOCKED_OUT,
userRepository.getSelectedUserInfo().id
)
} else if (face) {
bouncerMessageFactory.createFromPromptReason(
PROMPT_REASON_FACE_LOCKED_OUT,
userRepository.getSelectedUserInfo().id
)
} else null
}
override fun setPrimaryAuthMessage(value: BouncerMessageModel?) {
_primaryAuthMessage.value = value
}
override fun setFaceAcquisitionMessage(value: BouncerMessageModel?) {
_faceAcquisitionMessage.value = value
}
override fun setFingerprintAcquisitionMessage(value: BouncerMessageModel?) {
_fingerprintAcquisitionMessage.value = value
}
override fun setCustomMessage(value: BouncerMessageModel?) {
_customMessage.value = value
}
override fun clearMessage() {
_fingerprintAcquisitionMessage.value = null
_faceAcquisitionMessage.value = null
_primaryAuthMessage.value = null
_customMessage.value = null
}
companion object {
const val TAG = "BouncerDetailedMessageRepository"
}
}
private fun and(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
flow.combine(anotherFlow) { a, b -> a && b }
private fun or(flow: Flow<Boolean>, anotherFlow: Flow<Boolean>) =
flow.combine(anotherFlow) { a, b -> a || b }

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.keyguard.bouncer.domain.interactor
import android.os.Build
import android.util.Log
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.bouncer.data.repository.BouncerMessageRepository
import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
private val TAG = BouncerMessageAuditLogger::class.simpleName!!
/** Logger that echoes bouncer messages state to logcat in debuggable builds. */
@SysUISingleton
class BouncerMessageAuditLogger
@Inject
constructor(
@Application private val scope: CoroutineScope,
private val repository: BouncerMessageRepository,
private val interactor: BouncerMessageInteractor,
) : CoreStartable {
override fun start() {
if (Build.isDebuggable()) {
collectAndLog(repository.biometricAuthMessage, "biometricMessage: ")
collectAndLog(repository.primaryAuthMessage, "primaryAuthMessage: ")
collectAndLog(repository.customMessage, "customMessage: ")
collectAndLog(repository.faceAcquisitionMessage, "faceAcquisitionMessage: ")
collectAndLog(
repository.fingerprintAcquisitionMessage,
"fingerprintAcquisitionMessage: "
)
collectAndLog(repository.authFlagsMessage, "authFlagsMessage: ")
collectAndLog(interactor.bouncerMessage, "interactor.bouncerMessage: ")
}
}
private fun collectAndLog(flow: Flow<BouncerMessageModel?>, context: String) {
scope.launch { flow.collect { Log.d(TAG, context + it) } }
}
}

View File

@@ -0,0 +1,190 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.keyguard.bouncer.domain.interactor
import android.os.CountDownTimer
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEFAULT
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES
import com.android.systemui.keyguard.bouncer.data.factory.BouncerMessageFactory
import com.android.systemui.keyguard.bouncer.data.repository.BouncerMessageRepository
import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
import com.android.systemui.user.data.repository.UserRepository
import javax.inject.Inject
import kotlin.math.roundToInt
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
@SysUISingleton
class BouncerMessageInteractor
@Inject
constructor(
private val repository: BouncerMessageRepository,
private val factory: BouncerMessageFactory,
private val userRepository: UserRepository,
private val countDownTimerUtil: CountDownTimerUtil,
private val featureFlags: FeatureFlags,
) {
fun onPrimaryAuthLockedOut(secondsBeforeLockoutReset: Long) {
if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
val callback =
object : CountDownTimerCallback {
override fun onFinish() {
repository.clearMessage()
}
override fun onTick(millisUntilFinished: Long) {
val secondsRemaining = (millisUntilFinished / 1000.0).roundToInt()
val message =
factory.createFromPromptReason(
reason = PROMPT_REASON_PRIMARY_AUTH_LOCKED_OUT,
userId = userRepository.getSelectedUserInfo().id
)
message?.message?.animate = false
message?.message?.formatterArgs =
mutableMapOf<String, Any>(Pair("count", secondsRemaining))
repository.setPrimaryAuthMessage(message)
}
}
countDownTimerUtil.startNewTimer(secondsBeforeLockoutReset * 1000, 1000, callback)
}
fun onPrimaryAuthIncorrectAttempt() {
if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
repository.setPrimaryAuthMessage(
factory.createFromPromptReason(
PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
userRepository.getSelectedUserInfo().id
)
)
}
fun setFingerprintAcquisitionMessage(value: String?) {
if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
repository.setFingerprintAcquisitionMessage(
if (value != null) {
factory.createFromString(secondaryMsg = value)
} else {
null
}
)
}
fun setFaceAcquisitionMessage(value: String?) {
if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
repository.setFaceAcquisitionMessage(
if (value != null) {
factory.createFromString(secondaryMsg = value)
} else {
null
}
)
}
fun setCustomMessage(value: String?) {
if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
repository.setCustomMessage(
if (value != null) {
factory.createFromString(secondaryMsg = value)
} else {
null
}
)
}
fun onPrimaryBouncerUserInput() {
if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
repository.clearMessage()
}
fun onBouncerBeingHidden() {
if (!featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) return
repository.clearMessage()
}
private fun firstNonNullMessage(
oneMessageModel: Flow<BouncerMessageModel?>,
anotherMessageModel: Flow<BouncerMessageModel?>
): Flow<BouncerMessageModel?> {
return oneMessageModel.combine(anotherMessageModel) { a, b -> a ?: b }
}
// Null if feature flag is enabled which gets ignored always or empty bouncer message model that
// always maps to an empty string.
private fun nullOrEmptyMessage() =
flowOf(
if (featureFlags.isEnabled(REVAMPED_BOUNCER_MESSAGES)) null
else factory.createFromString("", "")
)
val bouncerMessage =
listOf(
nullOrEmptyMessage(),
repository.primaryAuthMessage,
repository.biometricAuthMessage,
repository.fingerprintAcquisitionMessage,
repository.faceAcquisitionMessage,
repository.customMessage,
repository.authFlagsMessage,
repository.biometricLockedOutMessage,
userRepository.selectedUserInfo.map {
factory.createFromPromptReason(PROMPT_REASON_DEFAULT, it.id)
},
)
.reduce(::firstNonNullMessage)
.distinctUntilChanged()
}
interface CountDownTimerCallback {
fun onFinish()
fun onTick(millisUntilFinished: Long)
}
@SysUISingleton
open class CountDownTimerUtil @Inject constructor() {
/**
* Start a new count down timer that runs for [millisInFuture] with a tick every
* [millisInterval]
*/
fun startNewTimer(
millisInFuture: Long,
millisInterval: Long,
callback: CountDownTimerCallback,
): CountDownTimer {
return object : CountDownTimer(millisInFuture, millisInterval) {
override fun onFinish() = callback.onFinish()
override fun onTick(millisUntilFinished: Long) =
callback.onTick(millisUntilFinished)
}
.start()
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.keyguard.bouncer.shared.model
import android.content.res.ColorStateList
/**
* Represents the message displayed on the bouncer. It has two parts, primary and a secondary
* message
*/
data class BouncerMessageModel(val message: Message? = null, val secondaryMessage: Message? = null)
/**
* Representation of a single message on the bouncer. It can be either a string or a string resource
* ID
*/
data class Message(
val message: String? = null,
val messageResId: Int? = null,
val colorState: ColorStateList? = null,
/** Any plural formatter arguments that can used to format the [messageResId] */
var formatterArgs: Map<String, Any>? = null,
/** Specifies whether this text should be animated when it is shown. */
var animate: Boolean = true,
)

View File

@@ -0,0 +1,52 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.keyguard.bouncer.ui
import android.content.Context
import android.util.AttributeSet
import android.widget.LinearLayout
import com.android.keyguard.BouncerKeyguardMessageArea
import com.android.keyguard.KeyguardMessageArea
import com.android.keyguard.KeyguardMessageAreaController
import com.android.systemui.R
class BouncerMessageView : LinearLayout {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
init {
inflate(context, R.layout.bouncer_message_view, this)
}
var primaryMessageView: BouncerKeyguardMessageArea? = null
var secondaryMessageView: BouncerKeyguardMessageArea? = null
var primaryMessage: KeyguardMessageAreaController<KeyguardMessageArea>? = null
var secondaryMessage: KeyguardMessageAreaController<KeyguardMessageArea>? = null
override fun onFinishInflate() {
super.onFinishInflate()
primaryMessageView = findViewById(R.id.bouncer_primary_message_area)
secondaryMessageView = findViewById(R.id.bouncer_secondary_message_area)
}
fun init(factory: KeyguardMessageAreaController.Factory) {
primaryMessage = factory.create(primaryMessageView)
primaryMessage?.init()
secondaryMessage = factory.create(secondaryMessageView)
secondaryMessage?.init()
}
}

View File

@@ -25,7 +25,6 @@ import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback
import android.os.UserHandle
import android.util.Log
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.biometrics.AuthController
@@ -36,13 +35,14 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.TAG
import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import com.android.systemui.keyguard.shared.model.DevicePosture
import com.android.systemui.user.data.repository.UserRepository
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@@ -108,10 +108,14 @@ interface BiometricSettingsRepository {
* lockdown.
*/
val isCurrentUserInLockdown: Flow<Boolean>
/** Authentication flags set for the current user. */
val authenticationFlags: Flow<AuthenticationFlags>
}
const val TAG = "BiometricsRepositoryImpl"
private const val TAG = "BiometricsRepositoryImpl"
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class BiometricSettingsRepositoryImpl
@Inject
@@ -129,6 +133,8 @@ constructor(
dumpManager: DumpManager,
) : BiometricSettingsRepository, Dumpable {
private val biometricsEnabledForUser = mutableMapOf<Int, Boolean>()
override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
private val strongAuthTracker = StrongAuthTracker(userRepository, context)
@@ -136,6 +142,9 @@ constructor(
override val isCurrentUserInLockdown: Flow<Boolean> =
strongAuthTracker.currentUserAuthFlags.map { it.isInUserLockdown }
override val authenticationFlags: Flow<AuthenticationFlags> =
strongAuthTracker.currentUserAuthFlags
init {
Log.d(TAG, "Registering StrongAuthTracker")
lockPatternUtils.registerStrongAuthTracker(strongAuthTracker)
@@ -231,9 +240,14 @@ constructor(
}
}
private val isFaceEnabledByBiometricsManagerForCurrentUser: Flow<Boolean> =
userRepository.selectedUserInfo.flatMapLatest { userInfo ->
isFaceEnabledByBiometricsManager.map { biometricsEnabledForUser[userInfo.id] ?: false }
}
override val isFaceAuthenticationEnabled: Flow<Boolean>
get() =
combine(isFaceEnabledByBiometricsManager, isFaceEnabledByDevicePolicy) {
combine(isFaceEnabledByBiometricsManagerForCurrentUser, isFaceEnabledByDevicePolicy) {
biometricsManagerSetting,
devicePolicySetting ->
biometricsManagerSetting && devicePolicySetting
@@ -249,13 +263,13 @@ constructor(
.flowOn(backgroundDispatcher)
.distinctUntilChanged()
private val isFaceEnabledByBiometricsManager =
private val isFaceEnabledByBiometricsManager: Flow<Pair<Int, Boolean>> =
conflatedCallbackFlow {
val callback =
object : IBiometricEnabledOnKeyguardCallback.Stub() {
override fun onChanged(enabled: Boolean, userId: Int) {
trySendWithFailureLogging(
enabled,
Pair(userId, enabled),
TAG,
"biometricsEnabled state changed"
)
@@ -264,9 +278,10 @@ constructor(
biometricManager?.registerEnabledOnKeyguardCallback(callback)
awaitClose {}
}
.onEach { biometricsEnabledForUser[it.first] = it.second }
// This is because the callback is binder-based and we want to avoid multiple callbacks
// being registered.
.stateIn(scope, SharingStarted.Eagerly, false)
.stateIn(scope, SharingStarted.Eagerly, Pair(0, false))
override val isStrongBiometricAllowed: StateFlow<Boolean> =
strongAuthTracker.isStrongBiometricAllowed.stateIn(
@@ -306,14 +321,13 @@ constructor(
)
}
@OptIn(ExperimentalCoroutinesApi::class)
private class StrongAuthTracker(private val userRepository: UserRepository, context: Context?) :
LockPatternUtils.StrongAuthTracker(context) {
// Backing field for onStrongAuthRequiredChanged
private val _strongAuthFlags =
MutableStateFlow(
StrongAuthenticationFlags(currentUserId, getStrongAuthForUser(currentUserId))
)
private val _authFlags =
MutableStateFlow(AuthenticationFlags(currentUserId, getStrongAuthForUser(currentUserId)))
// Backing field for onIsNonStrongBiometricAllowedChanged
private val _nonStrongBiometricAllowed =
@@ -321,17 +335,15 @@ private class StrongAuthTracker(private val userRepository: UserRepository, cont
Pair(currentUserId, isNonStrongBiometricAllowedAfterIdleTimeout(currentUserId))
)
val currentUserAuthFlags: Flow<StrongAuthenticationFlags> =
val currentUserAuthFlags: Flow<AuthenticationFlags> =
userRepository.selectedUserInfo
.map { it.id }
.distinctUntilChanged()
.flatMapLatest { userId ->
_strongAuthFlags
.filter { it.userId == userId }
_authFlags
.map { AuthenticationFlags(userId, getStrongAuthForUser(userId)) }
.onEach { Log.d(TAG, "currentUser authFlags changed, new value: $it") }
.onStart {
emit(StrongAuthenticationFlags(userId, getStrongAuthForUser(userId)))
}
.onStart { emit(AuthenticationFlags(userId, getStrongAuthForUser(userId))) }
}
/** isStrongBiometricAllowed for the current user. */
@@ -356,7 +368,7 @@ private class StrongAuthTracker(private val userRepository: UserRepository, cont
override fun onStrongAuthRequiredChanged(userId: Int) {
val newFlags = getStrongAuthForUser(userId)
_strongAuthFlags.value = StrongAuthenticationFlags(userId, newFlags)
_authFlags.value = AuthenticationFlags(userId, newFlags)
Log.d(TAG, "onStrongAuthRequiredChanged for userId: $userId, flag value: $newFlags")
}
@@ -375,11 +387,3 @@ private fun DevicePolicyManager.isFingerprintDisabled(userId: Int): Boolean =
private fun DevicePolicyManager.isNotActive(userId: Int, policy: Int): Boolean =
(getKeyguardDisabledFeatures(null, userId) and policy) == 0
private data class StrongAuthenticationFlags(val userId: Int, val flag: Int) {
val isInUserLockdown = containsFlag(flag, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN)
}
private fun containsFlag(haystack: Int, needle: Int): Boolean {
return haystack and needle != 0
}

View File

@@ -22,7 +22,7 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.log.dagger.BouncerLog
import com.android.systemui.log.dagger.BouncerTableLog
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.util.time.SystemClock
@@ -105,7 +105,7 @@ class KeyguardBouncerRepositoryImpl
constructor(
private val clock: SystemClock,
@Application private val applicationScope: CoroutineScope,
@BouncerLog private val buffer: TableLogBuffer,
@BouncerTableLog private val buffer: TableLogBuffer,
) : KeyguardBouncerRepository {
/** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
private val _primaryBouncerShow = MutableStateFlow(false)

View File

@@ -16,8 +16,14 @@
package com.android.systemui.keyguard.data.repository
import com.android.systemui.CoreStartable
import com.android.systemui.keyguard.bouncer.data.repository.BouncerMessageRepository
import com.android.systemui.keyguard.bouncer.data.repository.BouncerMessageRepositoryImpl
import com.android.systemui.keyguard.bouncer.domain.interactor.BouncerMessageAuditLogger
import dagger.Binds
import dagger.Module
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
interface KeyguardRepositoryModule {
@@ -46,5 +52,13 @@ interface KeyguardRepositoryModule {
@Binds
fun keyguardBouncerRepository(impl: KeyguardBouncerRepositoryImpl): KeyguardBouncerRepository
@Binds
fun bouncerMessageRepository(impl: BouncerMessageRepositoryImpl): BouncerMessageRepository
@Binds
@IntoMap
@ClassKey(BouncerMessageAuditLogger::class)
fun bind(impl: BouncerMessageAuditLogger): CoreStartable
@Binds fun trustRepository(impl: TrustRepositoryImpl): TrustRepository
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.keyguard.shared.model
import com.android.internal.widget.LockPatternUtils
/** Authentication flags corresponding to a user. */
data class AuthenticationFlags(val userId: Int, val flag: Int) {
val isInUserLockdown =
containsFlag(
flag,
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
)
val isPrimaryAuthRequiredAfterReboot =
containsFlag(flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT)
val isPrimaryAuthRequiredAfterTimeout =
containsFlag(flag, LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT)
val isPrimaryAuthRequiredAfterDpmLockdown =
containsFlag(
flag,
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW
)
val someAuthRequiredAfterUserRequest =
containsFlag(flag, LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST)
val someAuthRequiredAfterTrustAgentExpired =
containsFlag(
flag,
LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED
)
val primaryAuthRequiredForUnattendedUpdate =
containsFlag(
flag,
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
)
/** Either Class 3 biometrics or primary auth can be used to unlock the device. */
val strongerAuthRequiredAfterNonStrongBiometricsTimeout =
containsFlag(
flag,
LockPatternUtils.StrongAuthTracker
.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT
)
}
private fun containsFlag(haystack: Int, needle: Int): Boolean {
return haystack and needle != 0
}

View File

@@ -38,7 +38,8 @@ data class AcquiredAuthenticationStatus(val acquiredInfo: Int) : AuthenticationS
object FailedAuthenticationStatus : AuthenticationStatus()
/** Face authentication error message */
data class ErrorAuthenticationStatus(val msgId: Int, val msg: String?) : AuthenticationStatus() {
data class ErrorAuthenticationStatus(val msgId: Int, val msg: String? = null) :
AuthenticationStatus() {
/**
* Method that checks if [msgId] is a lockout error. A lockout error means that face
* authentication is locked out.

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.log
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
import com.android.systemui.log.dagger.BouncerLog
import javax.inject.Inject
private const val TAG = "BouncerLog"
/**
* Helper class for logging for classes in the [com.android.systemui.keyguard.bouncer] package.
*
* To enable logcat echoing for an entire buffer:
* ```
* adb shell settings put global systemui/buffer/BouncerLog <logLevel>
*
* ```
*/
@SysUISingleton
class BouncerLogger @Inject constructor(@BouncerLog private val buffer: LogBuffer) {
fun startBouncerMessageInteractor() {
buffer.log(
TAG,
LogLevel.DEBUG,
"Starting BouncerMessageInteractor.bouncerMessage collector"
)
}
fun bouncerMessageUpdated(bouncerMsg: BouncerMessageModel?) {
buffer.log(
TAG,
LogLevel.DEBUG,
{
int1 = bouncerMsg?.message?.messageResId ?: -1
str1 = bouncerMsg?.message?.message
int2 = bouncerMsg?.secondaryMessage?.messageResId ?: -1
str2 = bouncerMsg?.secondaryMessage?.message
},
{ "Bouncer message update received: $int1, $str1, $int2, $str2" }
)
}
fun bindingBouncerMessageView() {
buffer.log(TAG, LogLevel.DEBUG, "Binding BouncerMessageView")
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2022 The Android Open Source Project
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,10 +16,7 @@
package com.android.systemui.log.dagger
import java.lang.annotation.Documented
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import javax.inject.Qualifier
/** Logger for the primary and alternative bouncers. */
@Qualifier @Documented @Retention(RetentionPolicy.RUNTIME) annotation class BouncerLog
/** A [com.android.systemui.log.LogBuffer] for bouncer and its child views. */
@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class BouncerLog()

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2022 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.systemui.log.dagger
import java.lang.annotation.Documented
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import javax.inject.Qualifier
/** Logger for the primary and alternative bouncers. */
@Qualifier @Documented @Retention(RetentionPolicy.RUNTIME) annotation class BouncerTableLog

View File

@@ -403,6 +403,17 @@ public class LogModule {
return factory.create("DeviceEntryFaceAuthRepositoryLog", 300);
}
/**
* Provides a {@link LogBuffer} for use by classes in the
* {@link com.android.systemui.keyguard.bouncer} package.
*/
@Provides
@SysUISingleton
@BouncerLog
public static LogBuffer provideBouncerLog(LogBufferFactory factory) {
return factory.create("BouncerLog", 100);
}
/**
* Provides a {@link LogBuffer} for Device State Auto-Rotation logs.
*/
@@ -426,9 +437,9 @@ public class LogModule {
/** Provides a logging buffer for the primary bouncer. */
@Provides
@SysUISingleton
@BouncerLog
@BouncerTableLog
public static TableLogBuffer provideBouncerLogBuffer(TableLogBufferFactory factory) {
return factory.create("BouncerLog", 250);
return factory.create("BouncerTableLog", 250);
}
/** Provides a table logging buffer for the Monitor. */

View File

@@ -40,6 +40,8 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
import org.junit.Before;
import org.junit.Test;
@@ -77,6 +79,7 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase {
@Mock
private EmergencyButtonController mEmergencyButtonController;
private FakeFeatureFlags mFeatureFlags;
private KeyguardAbsKeyInputViewController mKeyguardAbsKeyInputViewController;
@Before
@@ -90,10 +93,18 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase {
when(mAbsKeyInputView.requireViewById(R.id.bouncer_message_area))
.thenReturn(mKeyguardMessageArea);
when(mAbsKeyInputView.getResources()).thenReturn(getContext().getResources());
mKeyguardAbsKeyInputViewController = new KeyguardAbsKeyInputViewController(mAbsKeyInputView,
mFeatureFlags = new FakeFeatureFlags();
mFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, false);
mKeyguardAbsKeyInputViewController = createTestObject();
mKeyguardAbsKeyInputViewController.init();
reset(mKeyguardMessageAreaController); // Clear out implicit call to init.
}
private KeyguardAbsKeyInputViewController createTestObject() {
return new KeyguardAbsKeyInputViewController(mAbsKeyInputView,
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
mKeyguardMessageAreaControllerFactory, mLatencyTracker, mFalsingCollector,
mEmergencyButtonController) {
mEmergencyButtonController, mFeatureFlags) {
@Override
void resetState() {
}
@@ -108,8 +119,16 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase {
return 0;
}
};
mKeyguardAbsKeyInputViewController.init();
reset(mKeyguardMessageAreaController); // Clear out implicit call to init.
}
@Test
public void withFeatureFlagOn_oldMessage_isHidden() {
mFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true);
KeyguardAbsKeyInputViewController underTest = createTestObject();
underTest.init();
verify(mKeyguardMessageAreaController).disable();
}
@Test

View File

@@ -26,6 +26,8 @@ import com.android.internal.widget.LockPatternUtils
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.util.concurrency.DelayableExecutor
import org.junit.Before
import org.junit.Test
@@ -76,6 +78,8 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() {
Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry))
.thenReturn(passwordEntry)
`when`(keyguardPasswordView.resources).thenReturn(context.resources)
val fakeFeatureFlags = FakeFeatureFlags()
fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
keyguardPasswordViewController =
KeyguardPasswordViewController(
keyguardPasswordView,
@@ -90,7 +94,8 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() {
mainExecutor,
mContext.resources,
falsingCollector,
keyguardViewController)
keyguardViewController,
fakeFeatureFlags)
}
@Test

View File

@@ -26,6 +26,8 @@ import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.policy.DevicePostureController
import org.junit.Before
import org.junit.Test
@@ -72,6 +74,7 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
@Mock private lateinit var mPostureController: DevicePostureController
private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
private lateinit var fakeFeatureFlags: FakeFeatureFlags
@Before
fun setup() {
@@ -86,6 +89,8 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
`when`(mKeyguardMessageAreaControllerFactory.create(mKeyguardMessageArea))
.thenReturn(mKeyguardMessageAreaController)
`when`(mKeyguardPatternView.resources).thenReturn(context.resources)
fakeFeatureFlags = FakeFeatureFlags()
fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, false)
mKeyguardPatternViewController =
KeyguardPatternViewController(
mKeyguardPatternView,
@@ -97,7 +102,17 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
mFalsingCollector,
mEmergencyButtonController,
mKeyguardMessageAreaControllerFactory,
mPostureController)
mPostureController,
fakeFeatureFlags)
}
@Test
fun withFeatureFlagOn_oldMessage_isHidden() {
fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
mKeyguardPatternViewController.init()
verify<KeyguardMessageAreaController<*>>(mKeyguardMessageAreaController).disable()
}
@Test

View File

@@ -36,6 +36,8 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.SingleTapClassifier;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
import org.junit.Before;
import org.junit.Test;
@@ -98,10 +100,13 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase {
.thenReturn(mDeleteButton);
when(mPinBasedInputView.findViewById(R.id.key_enter))
.thenReturn(mOkButton);
FakeFeatureFlags featureFlags = new FakeFeatureFlags();
featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true);
mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView,
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener,
mEmergencyButtonController, mFalsingCollector) {
mEmergencyButtonController, mFalsingCollector, featureFlags) {
@Override
public void onResume(int reason) {
super.onResume(reason);

View File

@@ -63,7 +63,9 @@ import com.android.systemui.biometrics.SideFpsController;
import com.android.systemui.biometrics.SideFpsUiRequestSource;
import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.ActivityStarter;
@@ -195,11 +197,15 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
when(mKeyguardPasswordView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(SecurityMode.PIN);
when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
FakeFeatureFlags featureFlags = new FakeFeatureFlags();
featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true);
mKeyguardPasswordViewController = new KeyguardPasswordViewController(
(KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor,
SecurityMode.Password, mLockPatternUtils, null,
mKeyguardMessageAreaControllerFactory, null, null, mEmergencyButtonController,
null, mock(Resources.class), null, mKeyguardViewController);
null, mock(Resources.class), null, mKeyguardViewController,
featureFlags);
mKeyguardSecurityContainerController = new KeyguardSecurityContainerController(
mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,

View File

@@ -27,6 +27,8 @@ import com.android.internal.widget.LockPatternUtils
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.util.mockito.any
import org.junit.Before
import org.junit.Test
@@ -71,6 +73,9 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() {
simPinView =
LayoutInflater.from(context).inflate(R.layout.keyguard_sim_pin_view, null)
as KeyguardSimPinView
val fakeFeatureFlags = FakeFeatureFlags()
fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
underTest =
KeyguardSimPinViewController(
simPinView,
@@ -83,7 +88,8 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() {
liftToActivateListener,
telephonyManager,
falsingCollector,
emergencyButtonController
emergencyButtonController,
fakeFeatureFlags,
)
underTest.init()
}

View File

@@ -27,6 +27,8 @@ import com.android.internal.widget.LockPatternUtils
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.util.mockito.any
import org.junit.Before
import org.junit.Test
@@ -70,6 +72,9 @@ class KeyguardSimPukViewControllerTest : SysuiTestCase() {
simPukView =
LayoutInflater.from(context).inflate(R.layout.keyguard_sim_puk_view, null)
as KeyguardSimPukView
val fakeFeatureFlags = FakeFeatureFlags()
fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
underTest =
KeyguardSimPukViewController(
simPukView,
@@ -82,7 +87,8 @@ class KeyguardSimPukViewControllerTest : SysuiTestCase() {
liftToActivateListener,
telephonyManager,
falsingCollector,
emergencyButtonController
emergencyButtonController,
fakeFeatureFlags,
)
underTest.init()
}

View File

@@ -0,0 +1,159 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.keyguard.bouncer.data.factory
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Password
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Pattern
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEFAULT
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.StringSubject
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class BouncerMessageFactoryTest : SysuiTestCase() {
private lateinit var underTest: BouncerMessageFactory
@Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var securityModel: KeyguardSecurityModel
private lateinit var testScope: TestScope
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
testScope = TestScope()
underTest = BouncerMessageFactory(updateMonitor, securityModel)
}
@Test
fun bouncerMessages_choosesTheRightMessage_basedOnSecurityModeAndFpAllowedInBouncer() =
testScope.runTest {
primaryMessage(PROMPT_REASON_DEFAULT, mode = PIN, fpAllowedInBouncer = false)
.isEqualTo("Enter PIN")
primaryMessage(PROMPT_REASON_DEFAULT, mode = PIN, fpAllowedInBouncer = true)
.isEqualTo("Unlock with PIN or fingerprint")
primaryMessage(PROMPT_REASON_DEFAULT, mode = Password, fpAllowedInBouncer = false)
.isEqualTo("Enter password")
primaryMessage(PROMPT_REASON_DEFAULT, mode = Password, fpAllowedInBouncer = true)
.isEqualTo("Unlock with password or fingerprint")
primaryMessage(PROMPT_REASON_DEFAULT, mode = Pattern, fpAllowedInBouncer = false)
.isEqualTo("Draw pattern")
primaryMessage(PROMPT_REASON_DEFAULT, mode = Pattern, fpAllowedInBouncer = true)
.isEqualTo("Unlock with pattern or fingerprint")
}
@Test
fun bouncerMessages_setsPrimaryAndSecondaryMessage_basedOnSecurityModeAndFpAllowedInBouncer() =
testScope.runTest {
primaryMessage(
PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
mode = PIN,
fpAllowedInBouncer = true
)
.isEqualTo("Wrong PIN. Try again.")
secondaryMessage(
PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
mode = PIN,
fpAllowedInBouncer = true
)
.isEqualTo("Or unlock with fingerprint")
primaryMessage(
PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
mode = Password,
fpAllowedInBouncer = true
)
.isEqualTo("Wrong password. Try again.")
secondaryMessage(
PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
mode = Password,
fpAllowedInBouncer = true
)
.isEqualTo("Or unlock with fingerprint")
primaryMessage(
PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
mode = Pattern,
fpAllowedInBouncer = true
)
.isEqualTo("Wrong pattern. Try again.")
secondaryMessage(
PROMPT_REASON_INCORRECT_PRIMARY_AUTH_INPUT,
mode = Pattern,
fpAllowedInBouncer = true
)
.isEqualTo("Or unlock with fingerprint")
}
private fun primaryMessage(
reason: Int,
mode: KeyguardSecurityModel.SecurityMode,
fpAllowedInBouncer: Boolean
): StringSubject {
return assertThat(
context.resources.getString(
bouncerMessageModel(mode, fpAllowedInBouncer, reason)!!.message!!.messageResId!!
)
)!!
}
private fun secondaryMessage(
reason: Int,
mode: KeyguardSecurityModel.SecurityMode,
fpAllowedInBouncer: Boolean
): StringSubject {
return assertThat(
context.resources.getString(
bouncerMessageModel(mode, fpAllowedInBouncer, reason)!!
.secondaryMessage!!
.messageResId!!
)
)!!
}
private fun bouncerMessageModel(
mode: KeyguardSecurityModel.SecurityMode,
fpAllowedInBouncer: Boolean,
reason: Int
): BouncerMessageModel? {
whenever(securityModel.getSecurityMode(0)).thenReturn(mode)
whenever(updateMonitor.isFingerprintAllowedInBouncer).thenReturn(fpAllowedInBouncer)
return underTest.createFromPromptReason(reason, 0)
}
}

View File

@@ -0,0 +1,363 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.keyguard.bouncer.data.repository
import android.content.pm.UserInfo
import android.hardware.biometrics.BiometricSourceType
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.R
import com.android.systemui.R.string.keyguard_enter_pin
import com.android.systemui.R.string.kg_prompt_after_dpm_lock
import com.android.systemui.R.string.kg_prompt_after_user_lockdown_pin
import com.android.systemui.R.string.kg_prompt_auth_timeout
import com.android.systemui.R.string.kg_prompt_pin_auth_timeout
import com.android.systemui.R.string.kg_prompt_reason_restart_pin
import com.android.systemui.R.string.kg_prompt_unattended_update
import com.android.systemui.R.string.kg_trust_agent_disabled
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.bouncer.data.factory.BouncerMessageFactory
import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
import com.android.systemui.keyguard.bouncer.shared.model.Message
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidJUnit4::class)
class BouncerMessageRepositoryTest : SysuiTestCase() {
@Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var securityModel: KeyguardSecurityModel
@Captor
private lateinit var updateMonitorCallback: ArgumentCaptor<KeyguardUpdateMonitorCallback>
private lateinit var underTest: BouncerMessageRepository
private lateinit var trustRepository: FakeTrustRepository
private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
private lateinit var userRepository: FakeUserRepository
private lateinit var fingerprintRepository: FakeDeviceEntryFingerprintAuthRepository
private lateinit var testScope: TestScope
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
trustRepository = FakeTrustRepository()
biometricSettingsRepository = FakeBiometricSettingsRepository()
userRepository = FakeUserRepository()
userRepository.setUserInfos(listOf(PRIMARY_USER))
fingerprintRepository = FakeDeviceEntryFingerprintAuthRepository()
testScope = TestScope()
whenever(updateMonitor.isFingerprintAllowedInBouncer).thenReturn(false)
whenever(securityModel.getSecurityMode(PRIMARY_USER_ID)).thenReturn(PIN)
underTest =
BouncerMessageRepositoryImpl(
trustRepository = trustRepository,
biometricSettingsRepository = biometricSettingsRepository,
updateMonitor = updateMonitor,
bouncerMessageFactory = BouncerMessageFactory(updateMonitor, securityModel),
userRepository = userRepository,
fingerprintAuthRepository = fingerprintRepository
)
}
@Test
fun setCustomMessage_propagatesState() =
testScope.runTest {
underTest.setCustomMessage(message("not empty"))
val customMessage = collectLastValue(underTest.customMessage)
assertThat(customMessage()).isEqualTo(message("not empty"))
}
@Test
fun setFaceMessage_propagatesState() =
testScope.runTest {
underTest.setFaceAcquisitionMessage(message("not empty"))
val faceAcquisitionMessage = collectLastValue(underTest.faceAcquisitionMessage)
assertThat(faceAcquisitionMessage()).isEqualTo(message("not empty"))
}
@Test
fun setFpMessage_propagatesState() =
testScope.runTest {
underTest.setFingerprintAcquisitionMessage(message("not empty"))
val fpAcquisitionMsg = collectLastValue(underTest.fingerprintAcquisitionMessage)
assertThat(fpAcquisitionMsg()).isEqualTo(message("not empty"))
}
@Test
fun setPrimaryAuthMessage_propagatesState() =
testScope.runTest {
underTest.setPrimaryAuthMessage(message("not empty"))
val primaryAuthMessage = collectLastValue(underTest.primaryAuthMessage)
assertThat(primaryAuthMessage()).isEqualTo(message("not empty"))
}
@Test
fun biometricAuthMessage_propagatesBiometricAuthMessages() =
testScope.runTest {
userRepository.setSelectedUserInfo(PRIMARY_USER)
val biometricAuthMessage = collectLastValue(underTest.biometricAuthMessage)
runCurrent()
verify(updateMonitor).registerCallback(updateMonitorCallback.capture())
updateMonitorCallback.value.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT)
assertThat(biometricAuthMessage())
.isEqualTo(message(R.string.kg_fp_not_recognized, R.string.kg_bio_try_again_or_pin))
updateMonitorCallback.value.onBiometricAuthFailed(BiometricSourceType.FACE)
assertThat(biometricAuthMessage())
.isEqualTo(
message(R.string.bouncer_face_not_recognized, R.string.kg_bio_try_again_or_pin)
)
updateMonitorCallback.value.onBiometricAcquired(BiometricSourceType.FACE, 0)
assertThat(biometricAuthMessage()).isNull()
}
@Test
fun onFaceLockout_propagatesState() =
testScope.runTest {
userRepository.setSelectedUserInfo(PRIMARY_USER)
val lockoutMessage = collectLastValue(underTest.biometricLockedOutMessage)
runCurrent()
verify(updateMonitor).registerCallback(updateMonitorCallback.capture())
whenever(updateMonitor.isFaceLockedOut).thenReturn(true)
updateMonitorCallback.value.onLockedOutStateChanged(BiometricSourceType.FACE)
assertThat(lockoutMessage())
.isEqualTo(message(keyguard_enter_pin, R.string.kg_face_locked_out))
whenever(updateMonitor.isFaceLockedOut).thenReturn(false)
updateMonitorCallback.value.onLockedOutStateChanged(BiometricSourceType.FACE)
assertThat(lockoutMessage()).isNull()
}
@Test
fun onFingerprintLockout_propagatesState() =
testScope.runTest {
userRepository.setSelectedUserInfo(PRIMARY_USER)
val lockedOutMessage = collectLastValue(underTest.biometricLockedOutMessage)
runCurrent()
fingerprintRepository.setLockedOut(true)
assertThat(lockedOutMessage())
.isEqualTo(message(keyguard_enter_pin, R.string.kg_fp_locked_out))
fingerprintRepository.setLockedOut(false)
assertThat(lockedOutMessage()).isNull()
}
@Test
fun onAuthFlagsChanged_withTrustNotManagedAndNoBiometrics_isANoop() =
testScope.runTest {
userRepository.setSelectedUserInfo(PRIMARY_USER)
trustRepository.setCurrentUserTrustManaged(false)
biometricSettingsRepository.setFaceEnrolled(false)
biometricSettingsRepository.setFingerprintEnrolled(false)
verifyMessagesForAuthFlag(
STRONG_AUTH_NOT_REQUIRED to null,
STRONG_AUTH_REQUIRED_AFTER_BOOT to null,
SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to null,
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to null,
STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to null,
SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to null,
STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to null,
STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
)
}
@Test
fun authFlagsChanges_withTrustManaged_providesDifferentMessages() =
testScope.runTest {
userRepository.setSelectedUserInfo(PRIMARY_USER)
biometricSettingsRepository.setFaceEnrolled(false)
biometricSettingsRepository.setFingerprintEnrolled(false)
trustRepository.setCurrentUserTrustManaged(true)
verifyMessagesForAuthFlag(
STRONG_AUTH_NOT_REQUIRED to null,
STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
STRONG_AUTH_REQUIRED_AFTER_BOOT to
Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin),
STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout),
STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to
Pair(keyguard_enter_pin, kg_trust_agent_disabled),
SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to
Pair(keyguard_enter_pin, kg_trust_agent_disabled),
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin),
STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
Pair(keyguard_enter_pin, kg_prompt_unattended_update),
STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
Pair(keyguard_enter_pin, kg_prompt_auth_timeout),
)
}
@Test
fun authFlagsChanges_withFaceEnrolled_providesDifferentMessages() =
testScope.runTest {
userRepository.setSelectedUserInfo(PRIMARY_USER)
trustRepository.setCurrentUserTrustManaged(false)
biometricSettingsRepository.setFingerprintEnrolled(false)
biometricSettingsRepository.setIsFaceAuthEnabled(true)
biometricSettingsRepository.setFaceEnrolled(true)
verifyMessagesForAuthFlag(
STRONG_AUTH_NOT_REQUIRED to null,
STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to null,
STRONG_AUTH_REQUIRED_AFTER_BOOT to
Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin),
STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout),
STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin),
STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
Pair(keyguard_enter_pin, kg_prompt_unattended_update),
STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
Pair(keyguard_enter_pin, kg_prompt_auth_timeout),
)
}
@Test
fun authFlagsChanges_withFingerprintEnrolled_providesDifferentMessages() =
testScope.runTest {
userRepository.setSelectedUserInfo(PRIMARY_USER)
trustRepository.setCurrentUserTrustManaged(false)
biometricSettingsRepository.setIsFaceAuthEnabled(false)
biometricSettingsRepository.setFaceEnrolled(false)
biometricSettingsRepository.setFingerprintEnrolled(true)
biometricSettingsRepository.setFingerprintEnabledByDevicePolicy(true)
verifyMessagesForAuthFlag(
STRONG_AUTH_NOT_REQUIRED to null,
STRONG_AUTH_REQUIRED_AFTER_LOCKOUT to null,
SOME_AUTH_REQUIRED_AFTER_USER_REQUEST to null,
SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED to null,
STRONG_AUTH_REQUIRED_AFTER_BOOT to
Pair(keyguard_enter_pin, kg_prompt_reason_restart_pin),
STRONG_AUTH_REQUIRED_AFTER_TIMEOUT to
Pair(keyguard_enter_pin, kg_prompt_pin_auth_timeout),
STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW to
Pair(keyguard_enter_pin, kg_prompt_after_dpm_lock),
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN to
Pair(keyguard_enter_pin, kg_prompt_after_user_lockdown_pin),
STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE to
Pair(keyguard_enter_pin, kg_prompt_unattended_update),
STRONG_AUTH_REQUIRED_AFTER_NON_STRONG_BIOMETRICS_TIMEOUT to
Pair(keyguard_enter_pin, kg_prompt_auth_timeout),
)
}
private fun TestScope.verifyMessagesForAuthFlag(
vararg authFlagToExpectedMessages: Pair<Int, Pair<Int, Int>?>
) {
val authFlagsMessage = collectLastValue(underTest.authFlagsMessage)
authFlagToExpectedMessages.forEach { (flag, messagePair) ->
biometricSettingsRepository.setAuthenticationFlags(
AuthenticationFlags(PRIMARY_USER_ID, flag)
)
assertThat(authFlagsMessage())
.isEqualTo(messagePair?.let { message(it.first, it.second) })
}
}
private fun message(primaryResId: Int, secondaryResId: Int): BouncerMessageModel {
return BouncerMessageModel(
message = Message(messageResId = primaryResId),
secondaryMessage = Message(messageResId = secondaryResId)
)
}
private fun message(value: String): BouncerMessageModel {
return BouncerMessageModel(message = Message(message = value))
}
companion object {
private const val PRIMARY_USER_ID = 0
private val PRIMARY_USER =
UserInfo(
/* id= */ PRIMARY_USER_ID,
/* name= */ "primary user",
/* flags= */ UserInfo.FLAG_PRIMARY
)
}
}

View File

@@ -0,0 +1,285 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.keyguard.bouncer.domain.interactor
import android.content.pm.UserInfo
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardSecurityModel.SecurityMode.PIN
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.R.string.keyguard_enter_pin
import com.android.systemui.R.string.kg_too_many_failed_attempts_countdown
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.FlowValue
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.bouncer.data.factory.BouncerMessageFactory
import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
import com.android.systemui.keyguard.bouncer.shared.model.Message
import com.android.systemui.keyguard.data.repository.FakeBouncerMessageRepository
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.mockito.KotlinArgumentCaptor
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidJUnit4::class)
class BouncerMessageInteractorTest : SysuiTestCase() {
@Mock private lateinit var securityModel: KeyguardSecurityModel
@Mock private lateinit var updateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var countDownTimerUtil: CountDownTimerUtil
private lateinit var countDownTimerCallback: KotlinArgumentCaptor<CountDownTimerCallback>
private lateinit var underTest: BouncerMessageInteractor
private lateinit var repository: FakeBouncerMessageRepository
private lateinit var userRepository: FakeUserRepository
private lateinit var testScope: TestScope
private lateinit var bouncerMessage: FlowValue<BouncerMessageModel?>
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
repository = FakeBouncerMessageRepository()
userRepository = FakeUserRepository()
userRepository.setUserInfos(listOf(PRIMARY_USER))
testScope = TestScope()
countDownTimerCallback = KotlinArgumentCaptor(CountDownTimerCallback::class.java)
allowTestableLooperAsMainThread()
whenever(securityModel.getSecurityMode(PRIMARY_USER_ID)).thenReturn(PIN)
whenever(updateMonitor.isFingerprintAllowedInBouncer).thenReturn(false)
}
suspend fun TestScope.init() {
userRepository.setSelectedUserInfo(PRIMARY_USER)
val featureFlags = FakeFeatureFlags()
featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
underTest =
BouncerMessageInteractor(
repository = repository,
factory = BouncerMessageFactory(updateMonitor, securityModel),
userRepository = userRepository,
countDownTimerUtil = countDownTimerUtil,
featureFlags = featureFlags
)
bouncerMessage = collectLastValue(underTest.bouncerMessage)
}
@Test
fun onIncorrectSecurityInput_setsTheBouncerModelInTheRepository() =
testScope.runTest {
init()
underTest.onPrimaryAuthIncorrectAttempt()
assertThat(repository.primaryAuthMessage).isNotNull()
assertThat(
context.resources.getString(
repository.primaryAuthMessage.value!!.message!!.messageResId!!
)
)
.isEqualTo("Wrong PIN. Try again.")
}
@Test
fun onUserStartsPrimaryAuthInput_clearsAllSetBouncerMessages() =
testScope.runTest {
init()
repository.setCustomMessage(message("not empty"))
repository.setFaceAcquisitionMessage(message("not empty"))
repository.setFingerprintAcquisitionMessage(message("not empty"))
repository.setPrimaryAuthMessage(message("not empty"))
underTest.onPrimaryBouncerUserInput()
assertThat(repository.customMessage.value).isNull()
assertThat(repository.faceAcquisitionMessage.value).isNull()
assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
assertThat(repository.primaryAuthMessage.value).isNull()
}
@Test
fun onBouncerBeingHidden_clearsAllSetBouncerMessages() =
testScope.runTest {
init()
repository.setCustomMessage(message("not empty"))
repository.setFaceAcquisitionMessage(message("not empty"))
repository.setFingerprintAcquisitionMessage(message("not empty"))
repository.setPrimaryAuthMessage(message("not empty"))
underTest.onBouncerBeingHidden()
assertThat(repository.customMessage.value).isNull()
assertThat(repository.faceAcquisitionMessage.value).isNull()
assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
assertThat(repository.primaryAuthMessage.value).isNull()
}
@Test
fun setCustomMessage_setsRepositoryValue() =
testScope.runTest {
init()
underTest.setCustomMessage("not empty")
assertThat(repository.customMessage.value)
.isEqualTo(BouncerMessageModel(secondaryMessage = Message(message = "not empty")))
underTest.setCustomMessage(null)
assertThat(repository.customMessage.value).isNull()
}
@Test
fun setFaceMessage_setsRepositoryValue() =
testScope.runTest {
init()
underTest.setFaceAcquisitionMessage("not empty")
assertThat(repository.faceAcquisitionMessage.value)
.isEqualTo(BouncerMessageModel(secondaryMessage = Message(message = "not empty")))
underTest.setFaceAcquisitionMessage(null)
assertThat(repository.faceAcquisitionMessage.value).isNull()
}
@Test
fun setFingerprintMessage_setsRepositoryValue() =
testScope.runTest {
init()
underTest.setFingerprintAcquisitionMessage("not empty")
assertThat(repository.fingerprintAcquisitionMessage.value)
.isEqualTo(BouncerMessageModel(secondaryMessage = Message(message = "not empty")))
underTest.setFingerprintAcquisitionMessage(null)
assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
}
@Test
fun onPrimaryAuthLockout_startsTimerForSpecifiedNumberOfSeconds() =
testScope.runTest {
init()
underTest.onPrimaryAuthLockedOut(3)
verify(countDownTimerUtil)
.startNewTimer(eq(3000L), eq(1000L), countDownTimerCallback.capture())
countDownTimerCallback.value.onTick(2000L)
val primaryMessage = repository.primaryAuthMessage.value!!.message!!
assertThat(primaryMessage.messageResId!!)
.isEqualTo(kg_too_many_failed_attempts_countdown)
assertThat(primaryMessage.formatterArgs).isEqualTo(mapOf(Pair("count", 2)))
}
@Test
fun onPrimaryAuthLockout_timerComplete_resetsRepositoryMessages() =
testScope.runTest {
init()
repository.setCustomMessage(message("not empty"))
repository.setFaceAcquisitionMessage(message("not empty"))
repository.setFingerprintAcquisitionMessage(message("not empty"))
repository.setPrimaryAuthMessage(message("not empty"))
underTest.onPrimaryAuthLockedOut(3)
verify(countDownTimerUtil)
.startNewTimer(eq(3000L), eq(1000L), countDownTimerCallback.capture())
countDownTimerCallback.value.onFinish()
assertThat(repository.customMessage.value).isNull()
assertThat(repository.faceAcquisitionMessage.value).isNull()
assertThat(repository.fingerprintAcquisitionMessage.value).isNull()
assertThat(repository.primaryAuthMessage.value).isNull()
}
@Test
fun bouncerMessage_hasPriorityOrderOfMessages() =
testScope.runTest {
init()
repository.setBiometricAuthMessage(message("biometric message"))
repository.setFaceAcquisitionMessage(message("face acquisition message"))
repository.setFingerprintAcquisitionMessage(message("fingerprint acquisition message"))
repository.setPrimaryAuthMessage(message("primary auth message"))
repository.setAuthFlagsMessage(message("auth flags message"))
repository.setBiometricLockedOutMessage(message("biometrics locked out"))
repository.setCustomMessage(message("custom message"))
assertThat(bouncerMessage()).isEqualTo(message("primary auth message"))
repository.setPrimaryAuthMessage(null)
assertThat(bouncerMessage()).isEqualTo(message("biometric message"))
repository.setBiometricAuthMessage(null)
assertThat(bouncerMessage()).isEqualTo(message("fingerprint acquisition message"))
repository.setFingerprintAcquisitionMessage(null)
assertThat(bouncerMessage()).isEqualTo(message("face acquisition message"))
repository.setFaceAcquisitionMessage(null)
assertThat(bouncerMessage()).isEqualTo(message("custom message"))
repository.setCustomMessage(null)
assertThat(bouncerMessage()).isEqualTo(message("auth flags message"))
repository.setAuthFlagsMessage(null)
assertThat(bouncerMessage()).isEqualTo(message("biometrics locked out"))
repository.setBiometricLockedOutMessage(null)
// sets the default message if everything else is null
assertThat(bouncerMessage()!!.message!!.messageResId).isEqualTo(keyguard_enter_pin)
}
private fun message(value: String): BouncerMessageModel {
return BouncerMessageModel(message = Message(message = value))
}
companion object {
private const val PRIMARY_USER_ID = 0
private val PRIMARY_USER =
UserInfo(
/* id= */ PRIMARY_USER_ID,
/* name= */ "primary user",
/* flags= */ UserInfo.FLAG_PRIMARY
)
}
}

View File

@@ -310,19 +310,38 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
createBiometricSettingsRepository()
verify(biometricManager)
.registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
whenever(devicePolicyManager.getKeyguardDisabledFeatures(isNull(), eq(PRIMARY_USER_ID)))
.thenReturn(0)
broadcastDPMStateChange()
biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID)
val isFaceAuthEnabled = collectLastValue(underTest.isFaceAuthenticationEnabled)
assertThat(isFaceAuthEnabled()).isTrue()
assertThat(isFaceAuthEnabled()).isFalse()
biometricManagerCallback.value.onChanged(false, PRIMARY_USER_ID)
// Value changes for another user
biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
assertThat(isFaceAuthEnabled()).isFalse()
// Value changes for current user.
biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID)
assertThat(isFaceAuthEnabled()).isTrue()
}
@Test
fun userChange_biometricEnabledChange_handlesRaceCondition() =
testScope.runTest {
createBiometricSettingsRepository()
verify(biometricManager)
.registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
val isFaceAuthEnabled = collectLastValue(underTest.isFaceAuthenticationEnabled)
biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID)
runCurrent()
userRepository.setSelectedUserInfo(ANOTHER_USER)
runCurrent()
assertThat(isFaceAuthEnabled()).isTrue()
}
@Test
@@ -382,7 +401,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
}
@Test
fun userInLockdownUsesStrongAuthFlagsToDetermineValue() =
fun userInLockdownUsesAuthFlagsToDetermineValue() =
testScope.runTest {
createBiometricSettingsRepository()
@@ -405,6 +424,38 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
assertThat(isUserInLockdown()).isTrue()
}
@Test
fun authFlagChangesForCurrentUserArePropagated() =
testScope.runTest {
createBiometricSettingsRepository()
val authFlags = collectLastValue(underTest.authenticationFlags)
// has default value.
val defaultStrongAuthValue = STRONG_AUTH_REQUIRED_AFTER_BOOT
assertThat(authFlags()!!.flag).isEqualTo(defaultStrongAuthValue)
// change strong auth flags for another user.
// Combine with one more flag to check if we do the bitwise and
val inLockdown =
STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN or STRONG_AUTH_REQUIRED_AFTER_TIMEOUT
onStrongAuthChanged(inLockdown, ANOTHER_USER_ID)
// Still false.
assertThat(authFlags()!!.flag).isEqualTo(defaultStrongAuthValue)
// change strong auth flags for current user.
onStrongAuthChanged(inLockdown, PRIMARY_USER_ID)
assertThat(authFlags()!!.flag).isEqualTo(inLockdown)
onStrongAuthChanged(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT, ANOTHER_USER_ID)
assertThat(authFlags()!!.flag).isEqualTo(inLockdown)
userRepository.setSelectedUserInfo(ANOTHER_USER)
assertThat(authFlags()!!.flag).isEqualTo(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT)
}
private fun enrollmentChange(biometricType: BiometricType, userId: Int, enabled: Boolean) {
authControllerCallback.value.onEnrollmentsChanged(biometricType, userId, enabled)
}

View File

@@ -17,10 +17,13 @@
package com.android.systemui.keyguard.data.repository
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.keyguard.shared.model.AuthenticationFlags
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
class FakeBiometricSettingsRepository : BiometricSettingsRepository {
@@ -50,9 +53,12 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository {
override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
get() = _isFaceAuthSupportedInCurrentPosture
private val _isCurrentUserInLockdown = MutableStateFlow(false)
override val isCurrentUserInLockdown: Flow<Boolean>
get() = _isCurrentUserInLockdown
get() = _authFlags.map { it.isInUserLockdown }
private val _authFlags = MutableStateFlow(AuthenticationFlags(0, 0))
override val authenticationFlags: Flow<AuthenticationFlags>
get() = _authFlags
fun setFingerprintEnrolled(isFingerprintEnrolled: Boolean) {
_isFingerprintEnrolled.value = isFingerprintEnrolled
@@ -66,6 +72,10 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository {
_isFingerprintEnabledByDevicePolicy.value = isFingerprintEnabledByDevicePolicy
}
fun setAuthenticationFlags(value: AuthenticationFlags) {
_authFlags.value = value
}
fun setFaceEnrolled(isFaceEnrolled: Boolean) {
_isFaceEnrolled.value = isFaceEnrolled
}
@@ -79,7 +89,22 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository {
}
fun setIsUserInLockdown(value: Boolean) {
_isCurrentUserInLockdown.value = value
if (value) {
setAuthenticationFlags(
AuthenticationFlags(
_authFlags.value.userId,
_authFlags.value.flag or
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
)
)
} else {
setAuthenticationFlags(
AuthenticationFlags(
_authFlags.value.userId,
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
)
)
}
}
fun setIsNonStrongBiometricAllowed(value: Boolean) {

View File

@@ -0,0 +1,84 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.keyguard.data.repository
import com.android.systemui.keyguard.bouncer.data.repository.BouncerMessageRepository
import com.android.systemui.keyguard.bouncer.shared.model.BouncerMessageModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class FakeBouncerMessageRepository : BouncerMessageRepository {
private val _primaryAuthMessage = MutableStateFlow<BouncerMessageModel?>(null)
override val primaryAuthMessage: StateFlow<BouncerMessageModel?>
get() = _primaryAuthMessage
private val _faceAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
override val faceAcquisitionMessage: StateFlow<BouncerMessageModel?>
get() = _faceAcquisitionMessage
private val _fingerprintAcquisitionMessage = MutableStateFlow<BouncerMessageModel?>(null)
override val fingerprintAcquisitionMessage: StateFlow<BouncerMessageModel?>
get() = _fingerprintAcquisitionMessage
private val _customMessage = MutableStateFlow<BouncerMessageModel?>(null)
override val customMessage: StateFlow<BouncerMessageModel?>
get() = _customMessage
private val _biometricAuthMessage = MutableStateFlow<BouncerMessageModel?>(null)
override val biometricAuthMessage: StateFlow<BouncerMessageModel?>
get() = _biometricAuthMessage
private val _authFlagsMessage = MutableStateFlow<BouncerMessageModel?>(null)
override val authFlagsMessage: StateFlow<BouncerMessageModel?>
get() = _authFlagsMessage
private val _biometricLockedOutMessage = MutableStateFlow<BouncerMessageModel?>(null)
override val biometricLockedOutMessage: Flow<BouncerMessageModel?>
get() = _biometricLockedOutMessage
override fun setPrimaryAuthMessage(value: BouncerMessageModel?) {
_primaryAuthMessage.value = value
}
override fun setFaceAcquisitionMessage(value: BouncerMessageModel?) {
_faceAcquisitionMessage.value = value
}
override fun setFingerprintAcquisitionMessage(value: BouncerMessageModel?) {
_fingerprintAcquisitionMessage.value = value
}
override fun setCustomMessage(value: BouncerMessageModel?) {
_customMessage.value = value
}
fun setBiometricAuthMessage(value: BouncerMessageModel?) {
_biometricAuthMessage.value = value
}
fun setAuthFlagsMessage(value: BouncerMessageModel?) {
_authFlagsMessage.value = value
}
fun setBiometricLockedOutMessage(value: BouncerMessageModel?) {
_biometricLockedOutMessage.value = value
}
override fun clearMessage() {
_primaryAuthMessage.value = null
_faceAcquisitionMessage.value = null
_fingerprintAcquisitionMessage.value = null
_customMessage.value = null
}
}