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"/>
|
<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
|
<Space
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
|
|||||||
@@ -33,6 +33,12 @@
|
|||||||
android:clipToPadding="false">
|
android:clipToPadding="false">
|
||||||
<include layout="@layout/keyguard_bouncer_message_area"/>
|
<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
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/pattern_container"
|
android:id="@+id/pattern_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -29,6 +29,12 @@
|
|||||||
androidprv:layout_maxWidth="@dimen/keyguard_security_width">
|
androidprv:layout_maxWidth="@dimen/keyguard_security_width">
|
||||||
<include layout="@layout/keyguard_bouncer_message_area"/>
|
<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
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
android:id="@+id/pin_container"
|
android:id="@+id/pin_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -23,6 +23,26 @@
|
|||||||
<style name="Keyguard.TextView" parent="@android:style/Widget.DeviceDefault.TextView">
|
<style name="Keyguard.TextView" parent="@android:style/Widget.DeviceDefault.TextView">
|
||||||
<item name="android:textSize">@dimen/kg_status_line_font_size</item>
|
<item name="android:textSize">@dimen/kg_status_line_font_size</item>
|
||||||
<item name="android:fontFamily">@*android:string/config_bodyFontFamily</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>
|
||||||
<style name="Keyguard.TextView.EmergencyButton" parent="Theme.SystemUI">
|
<style name="Keyguard.TextView.EmergencyButton" parent="Theme.SystemUI">
|
||||||
<item name="android:textColor">?androidprv:attr/materialColorOnTertiaryFixed</item>
|
<item name="android:textColor">?androidprv:attr/materialColorOnTertiaryFixed</item>
|
||||||
|
|||||||
@@ -577,6 +577,9 @@
|
|||||||
<!-- Whether to show the side fps hint while on bouncer -->
|
<!-- Whether to show the side fps hint while on bouncer -->
|
||||||
<bool name="config_show_sidefps_hint_on_bouncer">true</bool>
|
<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 -->
|
<!-- Whether to use the split 2-column notification shade -->
|
||||||
<bool name="config_use_split_notification_shade">false</bool>
|
<bool name="config_use_split_notification_shade">false</bool>
|
||||||
|
|
||||||
|
|||||||
@@ -807,6 +807,7 @@
|
|||||||
<!-- The width/height of the unlock icon view on keyguard. -->
|
<!-- The width/height of the unlock icon view on keyguard. -->
|
||||||
<dimen name="keyguard_lock_height">42dp</dimen>
|
<dimen name="keyguard_lock_height">42dp</dimen>
|
||||||
<dimen name="keyguard_lock_padding">20dp</dimen>
|
<dimen name="keyguard_lock_padding">20dp</dimen>
|
||||||
|
<dimen name="secondary_message_padding">8dp</dimen>
|
||||||
|
|
||||||
<dimen name="keyguard_security_container_padding_top">20dp</dimen>
|
<dimen name="keyguard_security_container_padding_top">20dp</dimen>
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ import android.animation.AnimatorSet
|
|||||||
import android.animation.ObjectAnimator
|
import android.animation.ObjectAnimator
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.content.res.TypedArray
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.android.app.animation.Interpolators
|
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 SHOW_DURATION_MILLIS = 150L
|
||||||
protected open val HIDE_DURATION_MILLIS = 200L
|
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() {
|
override fun updateTextColor() {
|
||||||
var colorState = mDefaultColorState
|
var colorState = mDefaultColorState
|
||||||
mNextMessageColorState?.defaultColor?.let { color ->
|
mNextMessageColorState?.defaultColor?.let { color ->
|
||||||
if (color != DEFAULT_COLOR) {
|
if (color != DEFAULT_COLOR) {
|
||||||
colorState = mNextMessageColorState
|
colorState = mNextMessageColorState
|
||||||
mNextMessageColorState = ColorStateList.valueOf(DEFAULT_COLOR)
|
mNextMessageColorState = mDefaultColorState ?: ColorStateList.valueOf(DEFAULT_COLOR)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTextColor(colorState)
|
setTextColor(colorState)
|
||||||
@@ -57,15 +71,12 @@ open class BouncerKeyguardMessageArea(context: Context?, attrs: AttributeSet?) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onThemeChanged() {
|
override fun onThemeChanged() {
|
||||||
val array: TypedArray = mContext.obtainStyledAttributes(intArrayOf(TITLE))
|
mDefaultColorState = getColorInStyle() ?: Utils.getColorAttr(context, TITLE)
|
||||||
val newTextColors: ColorStateList = ColorStateList.valueOf(array.getColor(0, Color.RED))
|
|
||||||
array.recycle()
|
|
||||||
mDefaultColorState = newTextColors
|
|
||||||
super.onThemeChanged()
|
super.onThemeChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun reloadColor() {
|
override fun reloadColor() {
|
||||||
mDefaultColorState = Utils.getColorAttr(context, TITLE)
|
mDefaultColorState = getColorInStyle() ?: Utils.getColorAttr(context, TITLE)
|
||||||
super.reloadColor()
|
super.reloadColor()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import android.view.HapticFeedbackConstants;
|
|||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.CallSuper;
|
||||||
|
|
||||||
import com.android.internal.widget.LockscreenCredential;
|
import com.android.internal.widget.LockscreenCredential;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
|
|
||||||
@@ -48,7 +50,9 @@ public abstract class KeyguardAbsKeyInputView extends KeyguardInputView {
|
|||||||
protected abstract void resetState();
|
protected abstract void resetState();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@CallSuper
|
||||||
protected void onFinishInflate() {
|
protected void onFinishInflate() {
|
||||||
|
super.onFinishInflate();
|
||||||
mEcaView = findViewById(R.id.keyguard_selector_fade_container);
|
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.R;
|
||||||
import com.android.systemui.classifier.FalsingClassifier;
|
import com.android.systemui.classifier.FalsingClassifier;
|
||||||
import com.android.systemui.classifier.FalsingCollector;
|
import com.android.systemui.classifier.FalsingCollector;
|
||||||
|
import com.android.systemui.flags.FeatureFlags;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -77,9 +78,10 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey
|
|||||||
KeyguardSecurityCallback keyguardSecurityCallback,
|
KeyguardSecurityCallback keyguardSecurityCallback,
|
||||||
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
|
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
|
||||||
LatencyTracker latencyTracker, FalsingCollector falsingCollector,
|
LatencyTracker latencyTracker, FalsingCollector falsingCollector,
|
||||||
EmergencyButtonController emergencyButtonController) {
|
EmergencyButtonController emergencyButtonController,
|
||||||
|
FeatureFlags featureFlags) {
|
||||||
super(view, securityMode, keyguardSecurityCallback, emergencyButtonController,
|
super(view, securityMode, keyguardSecurityCallback, emergencyButtonController,
|
||||||
messageAreaControllerFactory);
|
messageAreaControllerFactory, featureFlags);
|
||||||
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
|
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
|
||||||
mLockPatternUtils = lockPatternUtils;
|
mLockPatternUtils = lockPatternUtils;
|
||||||
mLatencyTracker = latencyTracker;
|
mLatencyTracker = latencyTracker;
|
||||||
|
|||||||
@@ -21,11 +21,14 @@ import android.animation.AnimatorListenerAdapter;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import androidx.annotation.CallSuper;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.android.internal.jank.InteractionJankMonitor;
|
import com.android.internal.jank.InteractionJankMonitor;
|
||||||
|
import com.android.systemui.R;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Base class for all Keyguard password/pattern/pin related inputs.
|
* 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 {
|
public abstract class KeyguardInputView extends LinearLayout {
|
||||||
private Runnable mOnFinishImeAnimationRunnable;
|
private Runnable mOnFinishImeAnimationRunnable;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private View mBouncerMessageView;
|
||||||
|
|
||||||
public KeyguardInputView(Context context) {
|
public KeyguardInputView(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
}
|
}
|
||||||
@@ -87,6 +93,18 @@ public abstract class KeyguardInputView extends LinearLayout {
|
|||||||
mOnFinishImeAnimationRunnable = onFinishImeAnimationRunnable;
|
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() {
|
public void runOnFinishImeAnimationRunnable() {
|
||||||
if (mOnFinishImeAnimationRunnable != null) {
|
if (mOnFinishImeAnimationRunnable != null) {
|
||||||
mOnFinishImeAnimationRunnable.run();
|
mOnFinishImeAnimationRunnable.run();
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package com.android.keyguard;
|
package com.android.keyguard;
|
||||||
|
|
||||||
|
import android.annotation.CallSuper;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.content.res.ColorStateList;
|
import android.content.res.ColorStateList;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
@@ -31,6 +32,7 @@ import com.android.systemui.R;
|
|||||||
import com.android.systemui.classifier.FalsingCollector;
|
import com.android.systemui.classifier.FalsingCollector;
|
||||||
import com.android.systemui.dagger.qualifiers.Main;
|
import com.android.systemui.dagger.qualifiers.Main;
|
||||||
import com.android.systemui.flags.FeatureFlags;
|
import com.android.systemui.flags.FeatureFlags;
|
||||||
|
import com.android.systemui.flags.Flags;
|
||||||
import com.android.systemui.statusbar.policy.DevicePostureController;
|
import com.android.systemui.statusbar.policy.DevicePostureController;
|
||||||
import com.android.systemui.util.ViewController;
|
import com.android.systemui.util.ViewController;
|
||||||
import com.android.systemui.util.concurrency.DelayableExecutor;
|
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
|
// (e.g. face unlock). This avoids unwanted asynchronous events from messing with the
|
||||||
// state for the current security method.
|
// state for the current security method.
|
||||||
private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() {};
|
private KeyguardSecurityCallback mNullCallback = new KeyguardSecurityCallback() {};
|
||||||
|
private final FeatureFlags mFeatureFlags;
|
||||||
|
|
||||||
protected KeyguardInputViewController(T view, SecurityMode securityMode,
|
protected KeyguardInputViewController(T view, SecurityMode securityMode,
|
||||||
KeyguardSecurityCallback keyguardSecurityCallback,
|
KeyguardSecurityCallback keyguardSecurityCallback,
|
||||||
EmergencyButtonController emergencyButtonController,
|
EmergencyButtonController emergencyButtonController,
|
||||||
@Nullable KeyguardMessageAreaController.Factory messageAreaControllerFactory) {
|
@Nullable KeyguardMessageAreaController.Factory messageAreaControllerFactory,
|
||||||
|
FeatureFlags featureFlags) {
|
||||||
super(view);
|
super(view);
|
||||||
mSecurityMode = securityMode;
|
mSecurityMode = securityMode;
|
||||||
mKeyguardSecurityCallback = keyguardSecurityCallback;
|
mKeyguardSecurityCallback = keyguardSecurityCallback;
|
||||||
mEmergencyButton = view == null ? null : view.findViewById(R.id.emergency_call_button);
|
mEmergencyButton = view == null ? null : view.findViewById(R.id.emergency_call_button);
|
||||||
mEmergencyButtonController = emergencyButtonController;
|
mEmergencyButtonController = emergencyButtonController;
|
||||||
|
mFeatureFlags = featureFlags;
|
||||||
if (messageAreaControllerFactory != null) {
|
if (messageAreaControllerFactory != null) {
|
||||||
try {
|
try {
|
||||||
BouncerKeyguardMessageArea kma = view.requireViewById(R.id.bouncer_message_area);
|
BouncerKeyguardMessageArea kma = view.requireViewById(R.id.bouncer_message_area);
|
||||||
@@ -82,9 +87,21 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@CallSuper
|
||||||
protected void onViewAttached() {
|
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
|
@Override
|
||||||
protected void onViewDetached() {
|
protected void onViewDetached() {
|
||||||
}
|
}
|
||||||
@@ -208,14 +225,14 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
|
|||||||
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
|
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
|
||||||
keyguardSecurityCallback, mLatencyTracker, mFalsingCollector,
|
keyguardSecurityCallback, mLatencyTracker, mFalsingCollector,
|
||||||
emergencyButtonController, mMessageAreaControllerFactory,
|
emergencyButtonController, mMessageAreaControllerFactory,
|
||||||
mDevicePostureController);
|
mDevicePostureController, mFeatureFlags);
|
||||||
} else if (keyguardInputView instanceof KeyguardPasswordView) {
|
} else if (keyguardInputView instanceof KeyguardPasswordView) {
|
||||||
return new KeyguardPasswordViewController((KeyguardPasswordView) keyguardInputView,
|
return new KeyguardPasswordViewController((KeyguardPasswordView) keyguardInputView,
|
||||||
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
|
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
|
||||||
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
|
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
|
||||||
mInputMethodManager, emergencyButtonController, mMainExecutor, mResources,
|
mInputMethodManager, emergencyButtonController, mMainExecutor, mResources,
|
||||||
mFalsingCollector, mKeyguardViewController);
|
mFalsingCollector, mKeyguardViewController,
|
||||||
|
mFeatureFlags);
|
||||||
} else if (keyguardInputView instanceof KeyguardPINView) {
|
} else if (keyguardInputView instanceof KeyguardPINView) {
|
||||||
return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
|
return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
|
||||||
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
|
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
|
||||||
@@ -227,13 +244,13 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
|
|||||||
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
|
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
|
||||||
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
|
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
|
||||||
mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
|
mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
|
||||||
emergencyButtonController);
|
emergencyButtonController, mFeatureFlags);
|
||||||
} else if (keyguardInputView instanceof KeyguardSimPukView) {
|
} else if (keyguardInputView instanceof KeyguardSimPukView) {
|
||||||
return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
|
return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
|
||||||
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
|
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
|
||||||
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
|
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
|
||||||
mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
|
mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
|
||||||
emergencyButtonController);
|
emergencyButtonController, mFeatureFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new RuntimeException("Unable to find controller for " + keyguardInputView);
|
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 ViewGroup mContainer;
|
||||||
private int mTopMargin;
|
private int mTopMargin;
|
||||||
protected boolean mAnimate;
|
protected boolean mAnimate;
|
||||||
|
private final int mStyleResId;
|
||||||
|
private boolean mIsDisabled = false;
|
||||||
|
|
||||||
public KeyguardMessageArea(Context context, AttributeSet attrs) {
|
public KeyguardMessageArea(Context context, AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug
|
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();
|
onThemeChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,13 +89,17 @@ public abstract class KeyguardMessageArea extends TextView implements SecurityMe
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onDensityOrFontScaleChanged() {
|
void onDensityOrFontScaleChanged() {
|
||||||
TypedArray array = mContext.obtainStyledAttributes(R.style.Keyguard_TextView, new int[] {
|
TypedArray array = mContext.obtainStyledAttributes(getStyleResId(), new int[] {
|
||||||
android.R.attr.textSize
|
android.R.attr.textSize
|
||||||
});
|
});
|
||||||
setTextSize(TypedValue.COMPLEX_UNIT_PX, array.getDimensionPixelSize(0, 0));
|
setTextSize(TypedValue.COMPLEX_UNIT_PX, array.getDimensionPixelSize(0, 0));
|
||||||
array.recycle();
|
array.recycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected int getStyleResId() {
|
||||||
|
return mStyleResId;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setMessage(CharSequence msg, boolean animate) {
|
public void setMessage(CharSequence msg, boolean animate) {
|
||||||
if (!TextUtils.isEmpty(msg)) {
|
if (!TextUtils.isEmpty(msg)) {
|
||||||
@@ -118,6 +129,10 @@ public abstract class KeyguardMessageArea extends TextView implements SecurityMe
|
|||||||
}
|
}
|
||||||
|
|
||||||
void update() {
|
void update() {
|
||||||
|
if (mIsDisabled) {
|
||||||
|
setVisibility(GONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
CharSequence status = mMessage;
|
CharSequence status = mMessage;
|
||||||
setVisibility(TextUtils.isEmpty(status) || (!mIsVisible) ? INVISIBLE : VISIBLE);
|
setVisibility(TextUtils.isEmpty(status) || (!mIsVisible) ? INVISIBLE : VISIBLE);
|
||||||
setText(status);
|
setText(status);
|
||||||
@@ -136,4 +151,17 @@ public abstract class KeyguardMessageArea extends TextView implements SecurityMe
|
|||||||
|
|
||||||
/** Set the text color */
|
/** Set the text color */
|
||||||
protected abstract void updateTextColor();
|
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);
|
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) {
|
public void setMessage(CharSequence s) {
|
||||||
setMessage(s, true);
|
setMessage(s, true);
|
||||||
}
|
}
|
||||||
@@ -141,6 +149,9 @@ public class KeyguardMessageAreaController<T extends KeyguardMessageArea>
|
|||||||
* Sets a message to the underlying text view.
|
* Sets a message to the underlying text view.
|
||||||
*/
|
*/
|
||||||
public void setMessage(CharSequence s, boolean animate) {
|
public void setMessage(CharSequence s, boolean animate) {
|
||||||
|
if (mView.isDisabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
mView.setMessage(s, animate);
|
mView.setMessage(s, animate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
|
|||||||
private View[][] mViews;
|
private View[][] mViews;
|
||||||
private int mYTrans;
|
private int mYTrans;
|
||||||
private int mYTransOffset;
|
private int mYTransOffset;
|
||||||
private View mBouncerMessageView;
|
private View mBouncerMessageArea;
|
||||||
@DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN;
|
@DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN;
|
||||||
public static final long ANIMATION_DURATION = 650;
|
public static final long ANIMATION_DURATION = 650;
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
|
|||||||
super.onFinishInflate();
|
super.onFinishInflate();
|
||||||
|
|
||||||
mContainer = findViewById(R.id.pin_container);
|
mContainer = findViewById(R.id.pin_container);
|
||||||
mBouncerMessageView = findViewById(R.id.bouncer_message_area);
|
mBouncerMessageArea = findViewById(R.id.bouncer_message_area);
|
||||||
mViews = new View[][]{
|
mViews = new View[][]{
|
||||||
new View[]{
|
new View[]{
|
||||||
findViewById(R.id.row0), null, null
|
findViewById(R.id.row0), null, null
|
||||||
@@ -221,9 +221,9 @@ public class KeyguardPINView extends KeyguardPinBasedInputView {
|
|||||||
Interpolator legacyDecelerate = Interpolators.LEGACY_DECELERATE;
|
Interpolator legacyDecelerate = Interpolators.LEGACY_DECELERATE;
|
||||||
float standardProgress = standardDecelerate.getInterpolation(progress);
|
float standardProgress = standardDecelerate.getInterpolation(progress);
|
||||||
|
|
||||||
mBouncerMessageView.setTranslationY(
|
mBouncerMessageArea.setTranslationY(
|
||||||
mYTrans - mYTrans * standardProgress);
|
mYTrans - mYTrans * standardProgress);
|
||||||
mBouncerMessageView.setAlpha(standardProgress);
|
mBouncerMessageArea.setAlpha(standardProgress);
|
||||||
|
|
||||||
for (int i = 0; i < mViews.length; i++) {
|
for (int i = 0; i < mViews.length; i++) {
|
||||||
View[] row = mViews[i];
|
View[] row = mViews[i];
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
|
|||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.classifier.FalsingCollector;
|
import com.android.systemui.classifier.FalsingCollector;
|
||||||
import com.android.systemui.dagger.qualifiers.Main;
|
import com.android.systemui.dagger.qualifiers.Main;
|
||||||
|
import com.android.systemui.flags.FeatureFlags;
|
||||||
import com.android.systemui.util.concurrency.DelayableExecutor;
|
import com.android.systemui.util.concurrency.DelayableExecutor;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -104,10 +105,11 @@ public class KeyguardPasswordViewController
|
|||||||
@Main DelayableExecutor mainExecutor,
|
@Main DelayableExecutor mainExecutor,
|
||||||
@Main Resources resources,
|
@Main Resources resources,
|
||||||
FalsingCollector falsingCollector,
|
FalsingCollector falsingCollector,
|
||||||
KeyguardViewController keyguardViewController) {
|
KeyguardViewController keyguardViewController,
|
||||||
|
FeatureFlags featureFlags) {
|
||||||
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
|
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
|
||||||
messageAreaControllerFactory, latencyTracker, falsingCollector,
|
messageAreaControllerFactory, latencyTracker, falsingCollector,
|
||||||
emergencyButtonController);
|
emergencyButtonController, featureFlags);
|
||||||
mKeyguardSecurityCallback = keyguardSecurityCallback;
|
mKeyguardSecurityCallback = keyguardSecurityCallback;
|
||||||
mInputMethodManager = inputMethodManager;
|
mInputMethodManager = inputMethodManager;
|
||||||
mMainExecutor = mainExecutor;
|
mMainExecutor = mainExecutor;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
|
|||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.classifier.FalsingClassifier;
|
import com.android.systemui.classifier.FalsingClassifier;
|
||||||
import com.android.systemui.classifier.FalsingCollector;
|
import com.android.systemui.classifier.FalsingCollector;
|
||||||
|
import com.android.systemui.flags.FeatureFlags;
|
||||||
import com.android.systemui.statusbar.policy.DevicePostureController;
|
import com.android.systemui.statusbar.policy.DevicePostureController;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -196,9 +197,9 @@ public class KeyguardPatternViewController
|
|||||||
FalsingCollector falsingCollector,
|
FalsingCollector falsingCollector,
|
||||||
EmergencyButtonController emergencyButtonController,
|
EmergencyButtonController emergencyButtonController,
|
||||||
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
|
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
|
||||||
DevicePostureController postureController) {
|
DevicePostureController postureController, FeatureFlags featureFlags) {
|
||||||
super(view, securityMode, keyguardSecurityCallback, emergencyButtonController,
|
super(view, securityMode, keyguardSecurityCallback, emergencyButtonController,
|
||||||
messageAreaControllerFactory);
|
messageAreaControllerFactory, featureFlags);
|
||||||
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
|
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
|
||||||
mLockPatternUtils = lockPatternUtils;
|
mLockPatternUtils = lockPatternUtils;
|
||||||
mLatencyTracker = latencyTracker;
|
mLatencyTracker = latencyTracker;
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ import android.util.AttributeSet;
|
|||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.CallSuper;
|
||||||
|
|
||||||
import com.android.app.animation.Interpolators;
|
import com.android.app.animation.Interpolators;
|
||||||
import com.android.internal.widget.LockscreenCredential;
|
import com.android.internal.widget.LockscreenCredential;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
@@ -147,7 +149,9 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@CallSuper
|
||||||
protected void onFinishInflate() {
|
protected void onFinishInflate() {
|
||||||
|
super.onFinishInflate();
|
||||||
mPasswordEntry = findViewById(getPasswordTextViewId());
|
mPasswordEntry = findViewById(getPasswordTextViewId());
|
||||||
|
|
||||||
// Set selected property on so the view can send accessibility events.
|
// 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.keyguard.KeyguardSecurityModel.SecurityMode;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.classifier.FalsingCollector;
|
import com.android.systemui.classifier.FalsingCollector;
|
||||||
|
import com.android.systemui.flags.FeatureFlags;
|
||||||
|
|
||||||
public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView>
|
public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinBasedInputView>
|
||||||
extends KeyguardAbsKeyInputViewController<T> {
|
extends KeyguardAbsKeyInputViewController<T> {
|
||||||
@@ -58,10 +59,11 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB
|
|||||||
LatencyTracker latencyTracker,
|
LatencyTracker latencyTracker,
|
||||||
LiftToActivateListener liftToActivateListener,
|
LiftToActivateListener liftToActivateListener,
|
||||||
EmergencyButtonController emergencyButtonController,
|
EmergencyButtonController emergencyButtonController,
|
||||||
FalsingCollector falsingCollector) {
|
FalsingCollector falsingCollector,
|
||||||
|
FeatureFlags featureFlags) {
|
||||||
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
|
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
|
||||||
messageAreaControllerFactory, latencyTracker, falsingCollector,
|
messageAreaControllerFactory, latencyTracker, falsingCollector,
|
||||||
emergencyButtonController);
|
emergencyButtonController, featureFlags);
|
||||||
mLiftToActivateListener = liftToActivateListener;
|
mLiftToActivateListener = liftToActivateListener;
|
||||||
mFalsingCollector = falsingCollector;
|
mFalsingCollector = falsingCollector;
|
||||||
mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
|
mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public class KeyguardPinViewController
|
|||||||
FeatureFlags featureFlags) {
|
FeatureFlags featureFlags) {
|
||||||
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
|
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
|
||||||
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
|
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
|
||||||
emergencyButtonController, falsingCollector);
|
emergencyButtonController, falsingCollector, featureFlags);
|
||||||
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
|
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
|
||||||
mPostureController = postureController;
|
mPostureController = postureController;
|
||||||
mLockPatternUtils = lockPatternUtils;
|
mLockPatternUtils = lockPatternUtils;
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ abstract class KeyguardSimInputView(context: Context, attrs: AttributeSet) :
|
|||||||
private var disableESimButton: KeyguardEsimArea? = null
|
private var disableESimButton: KeyguardEsimArea? = null
|
||||||
|
|
||||||
override fun onFinishInflate() {
|
override fun onFinishInflate() {
|
||||||
|
super.onFinishInflate()
|
||||||
simImageView = findViewById(R.id.keyguard_sim)
|
simImageView = findViewById(R.id.keyguard_sim)
|
||||||
disableESimButton = findViewById(R.id.keyguard_esim_area)
|
disableESimButton = findViewById(R.id.keyguard_esim_area)
|
||||||
super.onFinishInflate()
|
super.onFinishInflate()
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import com.android.internal.widget.LockPatternUtils;
|
|||||||
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
|
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.classifier.FalsingCollector;
|
import com.android.systemui.classifier.FalsingCollector;
|
||||||
|
import com.android.systemui.flags.FeatureFlags;
|
||||||
|
|
||||||
public class KeyguardSimPinViewController
|
public class KeyguardSimPinViewController
|
||||||
extends KeyguardPinBasedInputViewController<KeyguardSimPinView> {
|
extends KeyguardPinBasedInputViewController<KeyguardSimPinView> {
|
||||||
@@ -81,10 +82,10 @@ public class KeyguardSimPinViewController
|
|||||||
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
|
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
|
||||||
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
|
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
|
||||||
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
|
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
|
||||||
EmergencyButtonController emergencyButtonController) {
|
EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags) {
|
||||||
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
|
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
|
||||||
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
|
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
|
||||||
emergencyButtonController, falsingCollector);
|
emergencyButtonController, falsingCollector, featureFlags);
|
||||||
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
|
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
|
||||||
mTelephonyManager = telephonyManager;
|
mTelephonyManager = telephonyManager;
|
||||||
mSimImageView = mView.findViewById(R.id.keyguard_sim);
|
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.keyguard.KeyguardSecurityModel.SecurityMode;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
import com.android.systemui.classifier.FalsingCollector;
|
import com.android.systemui.classifier.FalsingCollector;
|
||||||
|
import com.android.systemui.flags.FeatureFlags;
|
||||||
|
|
||||||
public class KeyguardSimPukViewController
|
public class KeyguardSimPukViewController
|
||||||
extends KeyguardPinBasedInputViewController<KeyguardSimPukView> {
|
extends KeyguardPinBasedInputViewController<KeyguardSimPukView> {
|
||||||
@@ -85,10 +86,10 @@ public class KeyguardSimPukViewController
|
|||||||
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
|
KeyguardMessageAreaController.Factory messageAreaControllerFactory,
|
||||||
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
|
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
|
||||||
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
|
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
|
||||||
EmergencyButtonController emergencyButtonController) {
|
EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags) {
|
||||||
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
|
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
|
||||||
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
|
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
|
||||||
emergencyButtonController, falsingCollector);
|
emergencyButtonController, falsingCollector, featureFlags);
|
||||||
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
|
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
|
||||||
mTelephonyManager = telephonyManager;
|
mTelephonyManager = telephonyManager;
|
||||||
mSimImageView = mView.findViewById(R.id.keyguard_sim);
|
mSimImageView = mView.findViewById(R.id.keyguard_sim);
|
||||||
|
|||||||
@@ -2635,6 +2635,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
|
|||||||
return mAuthController.isUdfpsSupported();
|
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.
|
* @return true if there's at least one sfps enrollment for the current user.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -14,9 +14,10 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.android.keyguard
|
package com.android.systemui.keyguard.bouncer.data.factory
|
||||||
|
|
||||||
import android.annotation.IntDef
|
import android.annotation.IntDef
|
||||||
|
import com.android.keyguard.KeyguardSecurityModel
|
||||||
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
|
import com.android.keyguard.KeyguardSecurityModel.SecurityMode
|
||||||
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT
|
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT
|
||||||
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_DEFAULT
|
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_TIMEOUT
|
||||||
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED
|
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED
|
||||||
import com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST
|
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.bouncer_face_not_recognized
|
||||||
import com.android.systemui.R.string.keyguard_enter_password
|
import com.android.systemui.R.string.keyguard_enter_password
|
||||||
import com.android.systemui.R.string.keyguard_enter_pattern
|
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_password_try_again
|
||||||
import com.android.systemui.R.string.kg_wrong_pattern_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.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)
|
@Retention(AnnotationRetention.SOURCE)
|
||||||
@IntDef(
|
@IntDef(
|
||||||
@@ -97,48 +175,7 @@ fun emptyBouncerMessage(): BouncerMessage = Pair(0, 0)
|
|||||||
)
|
)
|
||||||
annotation class BouncerPromptReason
|
annotation class BouncerPromptReason
|
||||||
|
|
||||||
/**
|
private fun defaultMessage(securityMode: SecurityMode): Pair<Int, Int> {
|
||||||
* 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 {
|
|
||||||
return when (securityMode) {
|
return when (securityMode) {
|
||||||
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
|
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, 0)
|
||||||
SecurityMode.Password -> Pair(keyguard_enter_password, 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) {
|
return when (securityMode) {
|
||||||
SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, 0)
|
SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, 0)
|
||||||
SecurityMode.Password -> Pair(kg_unlock_with_password_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) {
|
return when (securityMode) {
|
||||||
SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, 0)
|
SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, 0)
|
||||||
SecurityMode.Password -> Pair(kg_wrong_password_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) {
|
return when (securityMode) {
|
||||||
SecurityMode.Pattern -> Pair(kg_wrong_pattern_try_again, kg_wrong_input_try_fp_suggestion)
|
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)
|
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) {
|
return when (securityMode) {
|
||||||
SecurityMode.Pattern -> Pair(kg_fp_not_recognized, kg_bio_try_again_or_pattern)
|
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)
|
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) {
|
return when (securityMode) {
|
||||||
SecurityMode.Pattern -> Pair(bouncer_face_not_recognized, kg_bio_try_again_or_pattern)
|
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)
|
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) {
|
return when (securityMode) {
|
||||||
SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, bouncer_face_not_recognized)
|
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)
|
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) {
|
return when (securityMode) {
|
||||||
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_bio_too_many_attempts_pattern)
|
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_bio_too_many_attempts_pattern)
|
||||||
SecurityMode.Password -> Pair(keyguard_enter_password, kg_bio_too_many_attempts_password)
|
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) {
|
return when (securityMode) {
|
||||||
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_reason_restart_pattern)
|
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_reason_restart_pattern)
|
||||||
SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_reason_restart_password)
|
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) {
|
return when (securityMode) {
|
||||||
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_dpm_lock)
|
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_dpm_lock)
|
||||||
SecurityMode.Password -> Pair(keyguard_enter_password, 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) {
|
return when (securityMode) {
|
||||||
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_user_lockdown_pattern)
|
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_after_user_lockdown_pattern)
|
||||||
SecurityMode.Password ->
|
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) {
|
return when (securityMode) {
|
||||||
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_unattended_update)
|
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_unattended_update)
|
||||||
SecurityMode.Password -> Pair(keyguard_enter_password, 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) {
|
return when (securityMode) {
|
||||||
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_pattern_auth_timeout)
|
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_pattern_auth_timeout)
|
||||||
SecurityMode.Password -> Pair(keyguard_enter_password, kg_prompt_password_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) {
|
return when (securityMode) {
|
||||||
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_auth_timeout)
|
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_prompt_auth_timeout)
|
||||||
SecurityMode.Password -> Pair(keyguard_enter_password, 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) {
|
return when (securityMode) {
|
||||||
SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_prompt_auth_timeout)
|
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)
|
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) {
|
return when (securityMode) {
|
||||||
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_face_locked_out)
|
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_face_locked_out)
|
||||||
SecurityMode.Password -> Pair(keyguard_enter_password, 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) {
|
return when (securityMode) {
|
||||||
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_fp_locked_out)
|
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_fp_locked_out)
|
||||||
SecurityMode.Password -> Pair(keyguard_enter_password, 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) {
|
return when (securityMode) {
|
||||||
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_trust_agent_disabled)
|
SecurityMode.Pattern -> Pair(keyguard_enter_pattern, kg_trust_agent_disabled)
|
||||||
SecurityMode.Password -> Pair(keyguard_enter_password, 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) {
|
return when (securityMode) {
|
||||||
SecurityMode.Pattern -> Pair(kg_unlock_with_pattern_or_fp, kg_trust_agent_disabled)
|
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)
|
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) {
|
return when (securityMode) {
|
||||||
SecurityMode.Pattern ->
|
SecurityMode.Pattern ->
|
||||||
Pair(kg_too_many_failed_attempts_countdown, kg_primary_auth_locked_out_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.os.UserHandle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.android.internal.widget.LockPatternUtils
|
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.Dumpable
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.biometrics.AuthController
|
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.Application
|
||||||
import com.android.systemui.dagger.qualifiers.Background
|
import com.android.systemui.dagger.qualifiers.Background
|
||||||
import com.android.systemui.dump.DumpManager
|
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.keyguard.shared.model.DevicePosture
|
||||||
import com.android.systemui.user.data.repository.UserRepository
|
import com.android.systemui.user.data.repository.UserRepository
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -108,10 +108,14 @@ interface BiometricSettingsRepository {
|
|||||||
* lockdown.
|
* lockdown.
|
||||||
*/
|
*/
|
||||||
val isCurrentUserInLockdown: Flow<Boolean>
|
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
|
@SysUISingleton
|
||||||
class BiometricSettingsRepositoryImpl
|
class BiometricSettingsRepositoryImpl
|
||||||
@Inject
|
@Inject
|
||||||
@@ -129,6 +133,8 @@ constructor(
|
|||||||
dumpManager: DumpManager,
|
dumpManager: DumpManager,
|
||||||
) : BiometricSettingsRepository, Dumpable {
|
) : BiometricSettingsRepository, Dumpable {
|
||||||
|
|
||||||
|
private val biometricsEnabledForUser = mutableMapOf<Int, Boolean>()
|
||||||
|
|
||||||
override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
|
override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
|
||||||
|
|
||||||
private val strongAuthTracker = StrongAuthTracker(userRepository, context)
|
private val strongAuthTracker = StrongAuthTracker(userRepository, context)
|
||||||
@@ -136,6 +142,9 @@ constructor(
|
|||||||
override val isCurrentUserInLockdown: Flow<Boolean> =
|
override val isCurrentUserInLockdown: Flow<Boolean> =
|
||||||
strongAuthTracker.currentUserAuthFlags.map { it.isInUserLockdown }
|
strongAuthTracker.currentUserAuthFlags.map { it.isInUserLockdown }
|
||||||
|
|
||||||
|
override val authenticationFlags: Flow<AuthenticationFlags> =
|
||||||
|
strongAuthTracker.currentUserAuthFlags
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Log.d(TAG, "Registering StrongAuthTracker")
|
Log.d(TAG, "Registering StrongAuthTracker")
|
||||||
lockPatternUtils.registerStrongAuthTracker(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>
|
override val isFaceAuthenticationEnabled: Flow<Boolean>
|
||||||
get() =
|
get() =
|
||||||
combine(isFaceEnabledByBiometricsManager, isFaceEnabledByDevicePolicy) {
|
combine(isFaceEnabledByBiometricsManagerForCurrentUser, isFaceEnabledByDevicePolicy) {
|
||||||
biometricsManagerSetting,
|
biometricsManagerSetting,
|
||||||
devicePolicySetting ->
|
devicePolicySetting ->
|
||||||
biometricsManagerSetting && devicePolicySetting
|
biometricsManagerSetting && devicePolicySetting
|
||||||
@@ -249,13 +263,13 @@ constructor(
|
|||||||
.flowOn(backgroundDispatcher)
|
.flowOn(backgroundDispatcher)
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
|
|
||||||
private val isFaceEnabledByBiometricsManager =
|
private val isFaceEnabledByBiometricsManager: Flow<Pair<Int, Boolean>> =
|
||||||
conflatedCallbackFlow {
|
conflatedCallbackFlow {
|
||||||
val callback =
|
val callback =
|
||||||
object : IBiometricEnabledOnKeyguardCallback.Stub() {
|
object : IBiometricEnabledOnKeyguardCallback.Stub() {
|
||||||
override fun onChanged(enabled: Boolean, userId: Int) {
|
override fun onChanged(enabled: Boolean, userId: Int) {
|
||||||
trySendWithFailureLogging(
|
trySendWithFailureLogging(
|
||||||
enabled,
|
Pair(userId, enabled),
|
||||||
TAG,
|
TAG,
|
||||||
"biometricsEnabled state changed"
|
"biometricsEnabled state changed"
|
||||||
)
|
)
|
||||||
@@ -264,9 +278,10 @@ constructor(
|
|||||||
biometricManager?.registerEnabledOnKeyguardCallback(callback)
|
biometricManager?.registerEnabledOnKeyguardCallback(callback)
|
||||||
awaitClose {}
|
awaitClose {}
|
||||||
}
|
}
|
||||||
|
.onEach { biometricsEnabledForUser[it.first] = it.second }
|
||||||
// This is because the callback is binder-based and we want to avoid multiple callbacks
|
// This is because the callback is binder-based and we want to avoid multiple callbacks
|
||||||
// being registered.
|
// being registered.
|
||||||
.stateIn(scope, SharingStarted.Eagerly, false)
|
.stateIn(scope, SharingStarted.Eagerly, Pair(0, false))
|
||||||
|
|
||||||
override val isStrongBiometricAllowed: StateFlow<Boolean> =
|
override val isStrongBiometricAllowed: StateFlow<Boolean> =
|
||||||
strongAuthTracker.isStrongBiometricAllowed.stateIn(
|
strongAuthTracker.isStrongBiometricAllowed.stateIn(
|
||||||
@@ -306,14 +321,13 @@ constructor(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
private class StrongAuthTracker(private val userRepository: UserRepository, context: Context?) :
|
private class StrongAuthTracker(private val userRepository: UserRepository, context: Context?) :
|
||||||
LockPatternUtils.StrongAuthTracker(context) {
|
LockPatternUtils.StrongAuthTracker(context) {
|
||||||
|
|
||||||
// Backing field for onStrongAuthRequiredChanged
|
// Backing field for onStrongAuthRequiredChanged
|
||||||
private val _strongAuthFlags =
|
private val _authFlags =
|
||||||
MutableStateFlow(
|
MutableStateFlow(AuthenticationFlags(currentUserId, getStrongAuthForUser(currentUserId)))
|
||||||
StrongAuthenticationFlags(currentUserId, getStrongAuthForUser(currentUserId))
|
|
||||||
)
|
|
||||||
|
|
||||||
// Backing field for onIsNonStrongBiometricAllowedChanged
|
// Backing field for onIsNonStrongBiometricAllowedChanged
|
||||||
private val _nonStrongBiometricAllowed =
|
private val _nonStrongBiometricAllowed =
|
||||||
@@ -321,17 +335,15 @@ private class StrongAuthTracker(private val userRepository: UserRepository, cont
|
|||||||
Pair(currentUserId, isNonStrongBiometricAllowedAfterIdleTimeout(currentUserId))
|
Pair(currentUserId, isNonStrongBiometricAllowedAfterIdleTimeout(currentUserId))
|
||||||
)
|
)
|
||||||
|
|
||||||
val currentUserAuthFlags: Flow<StrongAuthenticationFlags> =
|
val currentUserAuthFlags: Flow<AuthenticationFlags> =
|
||||||
userRepository.selectedUserInfo
|
userRepository.selectedUserInfo
|
||||||
.map { it.id }
|
.map { it.id }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.flatMapLatest { userId ->
|
.flatMapLatest { userId ->
|
||||||
_strongAuthFlags
|
_authFlags
|
||||||
.filter { it.userId == userId }
|
.map { AuthenticationFlags(userId, getStrongAuthForUser(userId)) }
|
||||||
.onEach { Log.d(TAG, "currentUser authFlags changed, new value: $it") }
|
.onEach { Log.d(TAG, "currentUser authFlags changed, new value: $it") }
|
||||||
.onStart {
|
.onStart { emit(AuthenticationFlags(userId, getStrongAuthForUser(userId))) }
|
||||||
emit(StrongAuthenticationFlags(userId, getStrongAuthForUser(userId)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** isStrongBiometricAllowed for the current user. */
|
/** isStrongBiometricAllowed for the current user. */
|
||||||
@@ -356,7 +368,7 @@ private class StrongAuthTracker(private val userRepository: UserRepository, cont
|
|||||||
|
|
||||||
override fun onStrongAuthRequiredChanged(userId: Int) {
|
override fun onStrongAuthRequiredChanged(userId: Int) {
|
||||||
val newFlags = getStrongAuthForUser(userId)
|
val newFlags = getStrongAuthForUser(userId)
|
||||||
_strongAuthFlags.value = StrongAuthenticationFlags(userId, newFlags)
|
_authFlags.value = AuthenticationFlags(userId, newFlags)
|
||||||
Log.d(TAG, "onStrongAuthRequiredChanged for userId: $userId, flag value: $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 =
|
private fun DevicePolicyManager.isNotActive(userId: Int, policy: Int): Boolean =
|
||||||
(getKeyguardDisabledFeatures(null, userId) and policy) == 0
|
(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.dagger.qualifiers.Application
|
||||||
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
|
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
|
||||||
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
|
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.TableLogBuffer
|
||||||
import com.android.systemui.log.table.logDiffsForTable
|
import com.android.systemui.log.table.logDiffsForTable
|
||||||
import com.android.systemui.util.time.SystemClock
|
import com.android.systemui.util.time.SystemClock
|
||||||
@@ -105,7 +105,7 @@ class KeyguardBouncerRepositoryImpl
|
|||||||
constructor(
|
constructor(
|
||||||
private val clock: SystemClock,
|
private val clock: SystemClock,
|
||||||
@Application private val applicationScope: CoroutineScope,
|
@Application private val applicationScope: CoroutineScope,
|
||||||
@BouncerLog private val buffer: TableLogBuffer,
|
@BouncerTableLog private val buffer: TableLogBuffer,
|
||||||
) : KeyguardBouncerRepository {
|
) : KeyguardBouncerRepository {
|
||||||
/** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
|
/** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
|
||||||
private val _primaryBouncerShow = MutableStateFlow(false)
|
private val _primaryBouncerShow = MutableStateFlow(false)
|
||||||
|
|||||||
@@ -16,8 +16,14 @@
|
|||||||
|
|
||||||
package com.android.systemui.keyguard.data.repository
|
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.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
|
import dagger.multibindings.ClassKey
|
||||||
|
import dagger.multibindings.IntoMap
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
interface KeyguardRepositoryModule {
|
interface KeyguardRepositoryModule {
|
||||||
@@ -46,5 +52,13 @@ interface KeyguardRepositoryModule {
|
|||||||
@Binds
|
@Binds
|
||||||
fun keyguardBouncerRepository(impl: KeyguardBouncerRepositoryImpl): KeyguardBouncerRepository
|
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
|
@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()
|
object FailedAuthenticationStatus : AuthenticationStatus()
|
||||||
|
|
||||||
/** Face authentication error message */
|
/** 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
|
* Method that checks if [msgId] is a lockout error. A lockout error means that face
|
||||||
* authentication is locked out.
|
* 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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@@ -16,10 +16,7 @@
|
|||||||
|
|
||||||
package com.android.systemui.log.dagger
|
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
|
import javax.inject.Qualifier
|
||||||
|
|
||||||
/** Logger for the primary and alternative bouncers. */
|
/** A [com.android.systemui.log.LogBuffer] for bouncer and its child views. */
|
||||||
@Qualifier @Documented @Retention(RetentionPolicy.RUNTIME) annotation class BouncerLog
|
@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);
|
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.
|
* 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 a logging buffer for the primary bouncer. */
|
||||||
@Provides
|
@Provides
|
||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
@BouncerLog
|
@BouncerTableLog
|
||||||
public static TableLogBuffer provideBouncerLogBuffer(TableLogBufferFactory factory) {
|
public static TableLogBuffer provideBouncerLogBuffer(TableLogBufferFactory factory) {
|
||||||
return factory.create("BouncerLog", 250);
|
return factory.create("BouncerTableLog", 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Provides a table logging buffer for the Monitor. */
|
/** 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.SysuiTestCase;
|
||||||
import com.android.systemui.classifier.FalsingCollector;
|
import com.android.systemui.classifier.FalsingCollector;
|
||||||
import com.android.systemui.classifier.FalsingCollectorFake;
|
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.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -77,6 +79,7 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase {
|
|||||||
@Mock
|
@Mock
|
||||||
private EmergencyButtonController mEmergencyButtonController;
|
private EmergencyButtonController mEmergencyButtonController;
|
||||||
|
|
||||||
|
private FakeFeatureFlags mFeatureFlags;
|
||||||
private KeyguardAbsKeyInputViewController mKeyguardAbsKeyInputViewController;
|
private KeyguardAbsKeyInputViewController mKeyguardAbsKeyInputViewController;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -90,10 +93,18 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase {
|
|||||||
when(mAbsKeyInputView.requireViewById(R.id.bouncer_message_area))
|
when(mAbsKeyInputView.requireViewById(R.id.bouncer_message_area))
|
||||||
.thenReturn(mKeyguardMessageArea);
|
.thenReturn(mKeyguardMessageArea);
|
||||||
when(mAbsKeyInputView.getResources()).thenReturn(getContext().getResources());
|
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,
|
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
|
||||||
mKeyguardMessageAreaControllerFactory, mLatencyTracker, mFalsingCollector,
|
mKeyguardMessageAreaControllerFactory, mLatencyTracker, mFalsingCollector,
|
||||||
mEmergencyButtonController) {
|
mEmergencyButtonController, mFeatureFlags) {
|
||||||
@Override
|
@Override
|
||||||
void resetState() {
|
void resetState() {
|
||||||
}
|
}
|
||||||
@@ -108,8 +119,16 @@ public class KeyguardAbsKeyInputViewControllerTest extends SysuiTestCase {
|
|||||||
return 0;
|
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
|
@Test
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import com.android.internal.widget.LockPatternUtils
|
|||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import com.android.systemui.classifier.FalsingCollector
|
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 com.android.systemui.util.concurrency.DelayableExecutor
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -76,6 +78,8 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() {
|
|||||||
Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry))
|
Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry))
|
||||||
.thenReturn(passwordEntry)
|
.thenReturn(passwordEntry)
|
||||||
`when`(keyguardPasswordView.resources).thenReturn(context.resources)
|
`when`(keyguardPasswordView.resources).thenReturn(context.resources)
|
||||||
|
val fakeFeatureFlags = FakeFeatureFlags()
|
||||||
|
fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
|
||||||
keyguardPasswordViewController =
|
keyguardPasswordViewController =
|
||||||
KeyguardPasswordViewController(
|
KeyguardPasswordViewController(
|
||||||
keyguardPasswordView,
|
keyguardPasswordView,
|
||||||
@@ -90,7 +94,8 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() {
|
|||||||
mainExecutor,
|
mainExecutor,
|
||||||
mContext.resources,
|
mContext.resources,
|
||||||
falsingCollector,
|
falsingCollector,
|
||||||
keyguardViewController)
|
keyguardViewController,
|
||||||
|
fakeFeatureFlags)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import com.android.systemui.R
|
|||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import com.android.systemui.classifier.FalsingCollector
|
import com.android.systemui.classifier.FalsingCollector
|
||||||
import com.android.systemui.classifier.FalsingCollectorFake
|
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 com.android.systemui.statusbar.policy.DevicePostureController
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -72,6 +74,7 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
|
|||||||
@Mock private lateinit var mPostureController: DevicePostureController
|
@Mock private lateinit var mPostureController: DevicePostureController
|
||||||
|
|
||||||
private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
|
private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
|
||||||
|
private lateinit var fakeFeatureFlags: FakeFeatureFlags
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
@@ -86,6 +89,8 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
|
|||||||
`when`(mKeyguardMessageAreaControllerFactory.create(mKeyguardMessageArea))
|
`when`(mKeyguardMessageAreaControllerFactory.create(mKeyguardMessageArea))
|
||||||
.thenReturn(mKeyguardMessageAreaController)
|
.thenReturn(mKeyguardMessageAreaController)
|
||||||
`when`(mKeyguardPatternView.resources).thenReturn(context.resources)
|
`when`(mKeyguardPatternView.resources).thenReturn(context.resources)
|
||||||
|
fakeFeatureFlags = FakeFeatureFlags()
|
||||||
|
fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, false)
|
||||||
mKeyguardPatternViewController =
|
mKeyguardPatternViewController =
|
||||||
KeyguardPatternViewController(
|
KeyguardPatternViewController(
|
||||||
mKeyguardPatternView,
|
mKeyguardPatternView,
|
||||||
@@ -97,7 +102,17 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
|
|||||||
mFalsingCollector,
|
mFalsingCollector,
|
||||||
mEmergencyButtonController,
|
mEmergencyButtonController,
|
||||||
mKeyguardMessageAreaControllerFactory,
|
mKeyguardMessageAreaControllerFactory,
|
||||||
mPostureController)
|
mPostureController,
|
||||||
|
fakeFeatureFlags)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun withFeatureFlagOn_oldMessage_isHidden() {
|
||||||
|
fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
|
||||||
|
|
||||||
|
mKeyguardPatternViewController.init()
|
||||||
|
|
||||||
|
verify<KeyguardMessageAreaController<*>>(mKeyguardMessageAreaController).disable()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ import com.android.systemui.SysuiTestCase;
|
|||||||
import com.android.systemui.classifier.FalsingCollector;
|
import com.android.systemui.classifier.FalsingCollector;
|
||||||
import com.android.systemui.classifier.FalsingCollectorFake;
|
import com.android.systemui.classifier.FalsingCollectorFake;
|
||||||
import com.android.systemui.classifier.SingleTapClassifier;
|
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.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -98,10 +100,13 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase {
|
|||||||
.thenReturn(mDeleteButton);
|
.thenReturn(mDeleteButton);
|
||||||
when(mPinBasedInputView.findViewById(R.id.key_enter))
|
when(mPinBasedInputView.findViewById(R.id.key_enter))
|
||||||
.thenReturn(mOkButton);
|
.thenReturn(mOkButton);
|
||||||
|
FakeFeatureFlags featureFlags = new FakeFeatureFlags();
|
||||||
|
featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true);
|
||||||
|
|
||||||
mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView,
|
mKeyguardPinViewController = new KeyguardPinBasedInputViewController(mPinBasedInputView,
|
||||||
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
|
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
|
||||||
mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener,
|
mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener,
|
||||||
mEmergencyButtonController, mFalsingCollector) {
|
mEmergencyButtonController, mFalsingCollector, featureFlags) {
|
||||||
@Override
|
@Override
|
||||||
public void onResume(int reason) {
|
public void onResume(int reason) {
|
||||||
super.onResume(reason);
|
super.onResume(reason);
|
||||||
|
|||||||
@@ -63,7 +63,9 @@ import com.android.systemui.biometrics.SideFpsController;
|
|||||||
import com.android.systemui.biometrics.SideFpsUiRequestSource;
|
import com.android.systemui.biometrics.SideFpsUiRequestSource;
|
||||||
import com.android.systemui.classifier.FalsingA11yDelegate;
|
import com.android.systemui.classifier.FalsingA11yDelegate;
|
||||||
import com.android.systemui.classifier.FalsingCollector;
|
import com.android.systemui.classifier.FalsingCollector;
|
||||||
|
import com.android.systemui.flags.FakeFeatureFlags;
|
||||||
import com.android.systemui.flags.FeatureFlags;
|
import com.android.systemui.flags.FeatureFlags;
|
||||||
|
import com.android.systemui.flags.Flags;
|
||||||
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
|
import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
|
||||||
import com.android.systemui.log.SessionTracker;
|
import com.android.systemui.log.SessionTracker;
|
||||||
import com.android.systemui.plugins.ActivityStarter;
|
import com.android.systemui.plugins.ActivityStarter;
|
||||||
@@ -195,11 +197,15 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
|
|||||||
when(mKeyguardPasswordView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
|
when(mKeyguardPasswordView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
|
||||||
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(SecurityMode.PIN);
|
when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(SecurityMode.PIN);
|
||||||
when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
|
when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true);
|
||||||
|
FakeFeatureFlags featureFlags = new FakeFeatureFlags();
|
||||||
|
featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true);
|
||||||
|
|
||||||
mKeyguardPasswordViewController = new KeyguardPasswordViewController(
|
mKeyguardPasswordViewController = new KeyguardPasswordViewController(
|
||||||
(KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor,
|
(KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor,
|
||||||
SecurityMode.Password, mLockPatternUtils, null,
|
SecurityMode.Password, mLockPatternUtils, null,
|
||||||
mKeyguardMessageAreaControllerFactory, null, null, mEmergencyButtonController,
|
mKeyguardMessageAreaControllerFactory, null, null, mEmergencyButtonController,
|
||||||
null, mock(Resources.class), null, mKeyguardViewController);
|
null, mock(Resources.class), null, mKeyguardViewController,
|
||||||
|
featureFlags);
|
||||||
|
|
||||||
mKeyguardSecurityContainerController = new KeyguardSecurityContainerController(
|
mKeyguardSecurityContainerController = new KeyguardSecurityContainerController(
|
||||||
mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
|
mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils,
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ import com.android.internal.widget.LockPatternUtils
|
|||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import com.android.systemui.classifier.FalsingCollector
|
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 com.android.systemui.util.mockito.any
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -71,6 +73,9 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() {
|
|||||||
simPinView =
|
simPinView =
|
||||||
LayoutInflater.from(context).inflate(R.layout.keyguard_sim_pin_view, null)
|
LayoutInflater.from(context).inflate(R.layout.keyguard_sim_pin_view, null)
|
||||||
as KeyguardSimPinView
|
as KeyguardSimPinView
|
||||||
|
val fakeFeatureFlags = FakeFeatureFlags()
|
||||||
|
fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
|
||||||
|
|
||||||
underTest =
|
underTest =
|
||||||
KeyguardSimPinViewController(
|
KeyguardSimPinViewController(
|
||||||
simPinView,
|
simPinView,
|
||||||
@@ -83,7 +88,8 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() {
|
|||||||
liftToActivateListener,
|
liftToActivateListener,
|
||||||
telephonyManager,
|
telephonyManager,
|
||||||
falsingCollector,
|
falsingCollector,
|
||||||
emergencyButtonController
|
emergencyButtonController,
|
||||||
|
fakeFeatureFlags,
|
||||||
)
|
)
|
||||||
underTest.init()
|
underTest.init()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ import com.android.internal.widget.LockPatternUtils
|
|||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import com.android.systemui.classifier.FalsingCollector
|
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 com.android.systemui.util.mockito.any
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -70,6 +72,9 @@ class KeyguardSimPukViewControllerTest : SysuiTestCase() {
|
|||||||
simPukView =
|
simPukView =
|
||||||
LayoutInflater.from(context).inflate(R.layout.keyguard_sim_puk_view, null)
|
LayoutInflater.from(context).inflate(R.layout.keyguard_sim_puk_view, null)
|
||||||
as KeyguardSimPukView
|
as KeyguardSimPukView
|
||||||
|
val fakeFeatureFlags = FakeFeatureFlags()
|
||||||
|
fakeFeatureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
|
||||||
|
|
||||||
underTest =
|
underTest =
|
||||||
KeyguardSimPukViewController(
|
KeyguardSimPukViewController(
|
||||||
simPukView,
|
simPukView,
|
||||||
@@ -82,7 +87,8 @@ class KeyguardSimPukViewControllerTest : SysuiTestCase() {
|
|||||||
liftToActivateListener,
|
liftToActivateListener,
|
||||||
telephonyManager,
|
telephonyManager,
|
||||||
falsingCollector,
|
falsingCollector,
|
||||||
emergencyButtonController
|
emergencyButtonController,
|
||||||
|
fakeFeatureFlags,
|
||||||
)
|
)
|
||||||
underTest.init()
|
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()
|
createBiometricSettingsRepository()
|
||||||
verify(biometricManager)
|
verify(biometricManager)
|
||||||
.registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
|
.registerEnabledOnKeyguardCallback(biometricManagerCallback.capture())
|
||||||
|
|
||||||
whenever(devicePolicyManager.getKeyguardDisabledFeatures(isNull(), eq(PRIMARY_USER_ID)))
|
whenever(devicePolicyManager.getKeyguardDisabledFeatures(isNull(), eq(PRIMARY_USER_ID)))
|
||||||
.thenReturn(0)
|
.thenReturn(0)
|
||||||
broadcastDPMStateChange()
|
broadcastDPMStateChange()
|
||||||
|
|
||||||
biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID)
|
|
||||||
val isFaceAuthEnabled = collectLastValue(underTest.isFaceAuthenticationEnabled)
|
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()
|
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
|
@Test
|
||||||
@@ -382,7 +401,7 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun userInLockdownUsesStrongAuthFlagsToDetermineValue() =
|
fun userInLockdownUsesAuthFlagsToDetermineValue() =
|
||||||
testScope.runTest {
|
testScope.runTest {
|
||||||
createBiometricSettingsRepository()
|
createBiometricSettingsRepository()
|
||||||
|
|
||||||
@@ -405,6 +424,38 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() {
|
|||||||
assertThat(isUserInLockdown()).isTrue()
|
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) {
|
private fun enrollmentChange(biometricType: BiometricType, userId: Int, enabled: Boolean) {
|
||||||
authControllerCallback.value.onEnrollmentsChanged(biometricType, userId, enabled)
|
authControllerCallback.value.onEnrollmentsChanged(biometricType, userId, enabled)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,10 +17,13 @@
|
|||||||
|
|
||||||
package com.android.systemui.keyguard.data.repository
|
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.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
class FakeBiometricSettingsRepository : BiometricSettingsRepository {
|
class FakeBiometricSettingsRepository : BiometricSettingsRepository {
|
||||||
|
|
||||||
@@ -50,9 +53,12 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository {
|
|||||||
override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
|
override val isFaceAuthSupportedInCurrentPosture: Flow<Boolean>
|
||||||
get() = _isFaceAuthSupportedInCurrentPosture
|
get() = _isFaceAuthSupportedInCurrentPosture
|
||||||
|
|
||||||
private val _isCurrentUserInLockdown = MutableStateFlow(false)
|
|
||||||
override val isCurrentUserInLockdown: Flow<Boolean>
|
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) {
|
fun setFingerprintEnrolled(isFingerprintEnrolled: Boolean) {
|
||||||
_isFingerprintEnrolled.value = isFingerprintEnrolled
|
_isFingerprintEnrolled.value = isFingerprintEnrolled
|
||||||
@@ -66,6 +72,10 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository {
|
|||||||
_isFingerprintEnabledByDevicePolicy.value = isFingerprintEnabledByDevicePolicy
|
_isFingerprintEnabledByDevicePolicy.value = isFingerprintEnabledByDevicePolicy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setAuthenticationFlags(value: AuthenticationFlags) {
|
||||||
|
_authFlags.value = value
|
||||||
|
}
|
||||||
|
|
||||||
fun setFaceEnrolled(isFaceEnrolled: Boolean) {
|
fun setFaceEnrolled(isFaceEnrolled: Boolean) {
|
||||||
_isFaceEnrolled.value = isFaceEnrolled
|
_isFaceEnrolled.value = isFaceEnrolled
|
||||||
}
|
}
|
||||||
@@ -79,7 +89,22 @@ class FakeBiometricSettingsRepository : BiometricSettingsRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setIsUserInLockdown(value: Boolean) {
|
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) {
|
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