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:
@@ -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>
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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)
|
||||
@@ -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 }
|
||||
@@ -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) } }
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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. */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user