Merge changes I04f6a54e,Ifb505f6d,Ia9ec3ce2,I97b201c6,Ia12e8cd6, ... into udc-dev
* changes: Add an audit logger for bouncer messages Add BouncerMessageRepository and BouncerMessageInteractor Make current user strong auth flags available in BiometricSettingsRepository Hide existing bouncer message area if the feature flag is enabled. Create BouncerMessageFactory to easily create BouncerMessage data model. Add views for displaying the new bouncer messages
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