Merge "Add assets and rear display state handling in SideFpsController and BiometricPrompt" into udc-dev

This commit is contained in:
Grace Cheng
2023-04-27 23:03:41 +00:00
committed by Android (Google) Code Review
21 changed files with 770 additions and 64 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -19,7 +19,6 @@ package com.android.systemui.biometrics
import android.annotation.RawRes
import android.content.Context
import android.content.Context.FINGERPRINT_SERVICE
import android.content.res.Configuration
import android.hardware.fingerprint.FingerprintManager
import android.view.DisplayInfo
import android.view.Surface
@@ -35,21 +34,18 @@ import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR
import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
import com.android.systemui.biometrics.AuthBiometricView.STATE_IDLE
import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
import com.android.systemui.unfold.updates.FoldProvider
/** Fingerprint only icon animator for BiometricPrompt. */
open class AuthBiometricFingerprintIconController(
context: Context,
iconView: LottieAnimationView,
protected val iconViewOverlay: LottieAnimationView
) : AuthIconController(context, iconView), FoldProvider.FoldCallback {
) : AuthIconController(context, iconView) {
private var isDeviceFolded: Boolean = false
private val isSideFps: Boolean
private val isReverseDefaultRotation =
context.resources.getBoolean(com.android.internal.R.bool.config_reverseDefaultRotation)
private val screenSizeFoldProvider: ScreenSizeFoldProvider = ScreenSizeFoldProvider(context)
var iconLayoutParamSize: Pair<Int, Int> = Pair(1, 1)
set(value) {
if (field == value) {
@@ -77,20 +73,16 @@ open class AuthBiometricFingerprintIconController(
if (isSideFps && getRotationFromDefault(displayInfo.rotation) == Surface.ROTATION_180) {
iconView.rotation = 180f
}
screenSizeFoldProvider.registerCallback(this, context.mainExecutor)
screenSizeFoldProvider.onConfigurationChange(context.resources.configuration)
}
private fun updateIconSideFps(@BiometricState lastState: Int, @BiometricState newState: Int) {
val displayInfo = DisplayInfo()
context.display?.getDisplayInfo(displayInfo)
val rotation = getRotationFromDefault(displayInfo.rotation)
val iconAnimation = getSideFpsAnimationForTransition(rotation)
val iconViewOverlayAnimation =
getSideFpsOverlayAnimationForTransition(lastState, newState, rotation) ?: return
if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) {
iconView.setAnimation(iconAnimation)
iconViewOverlay.setAnimation(iconViewOverlayAnimation)
}
@@ -132,10 +124,6 @@ open class AuthBiometricFingerprintIconController(
LottieColorUtils.applyDynamicColors(context, iconView)
}
override fun onConfigurationChanged(newConfig: Configuration) {
screenSizeFoldProvider.onConfigurationChange(newConfig)
}
override fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) {
if (isSideFps) {
updateIconSideFps(lastState, newState)
@@ -229,25 +217,6 @@ open class AuthBiometricFingerprintIconController(
private fun getRotationFromDefault(rotation: Int): Int =
if (isReverseDefaultRotation) (rotation + 1) % 4 else rotation
@RawRes
private fun getSideFpsAnimationForTransition(rotation: Int): Int = when (rotation) {
Surface.ROTATION_90 -> if (isDeviceFolded) {
R.raw.biometricprompt_folded_base_topleft
} else {
R.raw.biometricprompt_portrait_base_topleft
}
Surface.ROTATION_270 -> if (isDeviceFolded) {
R.raw.biometricprompt_folded_base_bottomright
} else {
R.raw.biometricprompt_portrait_base_bottomright
}
else -> if (isDeviceFolded) {
R.raw.biometricprompt_folded_base_default
} else {
R.raw.biometricprompt_landscape_base
}
}
@RawRes
private fun getSideFpsOverlayAnimationForTransition(
@BiometricState oldState: Int,
@@ -357,8 +326,4 @@ open class AuthBiometricFingerprintIconController(
)
}
}
override fun onFoldUpdated(isFolded: Boolean) {
isDeviceFolded = isFolded
}
}

View File

@@ -124,6 +124,7 @@ public abstract class AuthBiometricView extends LinearLayout {
protected final int mTextColorHint;
private AuthPanelController mPanelController;
private PromptInfo mPromptInfo;
private boolean mRequireConfirmation;
private int mUserId;
@@ -266,11 +267,9 @@ public abstract class AuthBiometricView extends LinearLayout {
/** Create the controller for managing the icons transitions during the prompt.*/
@NonNull
protected abstract AuthIconController createIconController();
void setPanelController(AuthPanelController panelController) {
mPanelController = panelController;
}
void setPromptInfo(PromptInfo promptInfo) {
mPromptInfo = promptInfo;
}
@@ -463,9 +462,16 @@ public abstract class AuthBiometricView extends LinearLayout {
return false;
}
/**
* Updates mIconView animation on updates to fold state, device rotation, or rear display mode
* @param animation new asset to use for iconw
*/
public void updateIconViewAnimation(int animation) {
mIconView.setAnimation(animation);
}
public void updateState(@BiometricState int newState) {
Log.v(TAG, "newState: " + newState);
mIconController.updateState(mState, newState);
switch (newState) {
@@ -625,7 +631,6 @@ public abstract class AuthBiometricView extends LinearLayout {
public void restoreState(@Nullable Bundle savedState) {
mSavedState = savedState;
}
private void setTextOrHide(TextView view, CharSequence charSequence) {
if (TextUtils.isEmpty(charSequence)) {
view.setVisibility(View.GONE);
@@ -658,7 +663,6 @@ public abstract class AuthBiometricView extends LinearLayout {
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
mIconController.onConfigurationChanged(newConfig);
if (mSavedState != null) {
updateState(mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_STATE));
}

View File

@@ -67,6 +67,8 @@ import com.android.systemui.animation.Interpolators;
import com.android.systemui.biometrics.AuthController.ScaleFactorProvider;
import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
import com.android.systemui.biometrics.ui.CredentialView;
import com.android.systemui.biometrics.ui.binder.AuthBiometricFingerprintViewBinder;
import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel;
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -126,6 +128,8 @@ public class AuthContainerView extends LinearLayout
// TODO: these should be migrated out once ready
private final Provider<BiometricPromptCredentialInteractor> mBiometricPromptInteractor;
private final Provider<AuthBiometricFingerprintViewModel>
mAuthBiometricFingerprintViewModelProvider;
private final Provider<CredentialViewModel> mCredentialViewModelProvider;
@VisibleForTesting final BiometricCallback mBiometricCallback;
@@ -241,12 +245,14 @@ public class AuthContainerView extends LinearLayout
@NonNull LockPatternUtils lockPatternUtils,
@NonNull InteractionJankMonitor jankMonitor,
@NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor,
@NonNull Provider<AuthBiometricFingerprintViewModel>
authBiometricFingerprintViewModelProvider,
@NonNull Provider<CredentialViewModel> credentialViewModelProvider) {
mConfig.mSensorIds = sensorIds;
return new AuthContainerView(mConfig, fpProps, faceProps, wakefulnessLifecycle,
panelInteractionDetector, userManager, lockPatternUtils, jankMonitor,
biometricPromptInteractor, credentialViewModelProvider,
new Handler(Looper.getMainLooper()), bgExecutor);
biometricPromptInteractor, authBiometricFingerprintViewModelProvider,
credentialViewModelProvider, new Handler(Looper.getMainLooper()), bgExecutor);
}
}
@@ -339,6 +345,8 @@ public class AuthContainerView extends LinearLayout
@NonNull LockPatternUtils lockPatternUtils,
@NonNull InteractionJankMonitor jankMonitor,
@NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor,
@NonNull Provider<AuthBiometricFingerprintViewModel>
authBiometricFingerprintViewModelProvider,
@NonNull Provider<CredentialViewModel> credentialViewModelProvider,
@NonNull Handler mainHandler,
@NonNull @Background DelayableExecutor bgExecutor) {
@@ -368,6 +376,7 @@ public class AuthContainerView extends LinearLayout
mBackgroundExecutor = bgExecutor;
mInteractionJankMonitor = jankMonitor;
mBiometricPromptInteractor = biometricPromptInteractor;
mAuthBiometricFingerprintViewModelProvider = authBiometricFingerprintViewModelProvider;
mCredentialViewModelProvider = credentialViewModelProvider;
// Inflate biometric view only if necessary.
@@ -386,6 +395,9 @@ public class AuthContainerView extends LinearLayout
fingerprintAndFaceView.updateOverrideIconLayoutParamsSize();
fingerprintAndFaceView.setFaceClass3(
faceProperties.sensorStrength == STRENGTH_STRONG);
final AuthBiometricFingerprintViewModel fpAndFaceViewModel =
mAuthBiometricFingerprintViewModelProvider.get();
AuthBiometricFingerprintViewBinder.bind(fingerprintAndFaceView, fpAndFaceViewModel);
mBiometricView = fingerprintAndFaceView;
} else if (fpProperties != null) {
final AuthBiometricFingerprintView fpView =
@@ -394,6 +406,9 @@ public class AuthContainerView extends LinearLayout
fpView.setSensorProperties(fpProperties);
fpView.setScaleFactorProvider(config.mScaleProvider);
fpView.updateOverrideIconLayoutParamsSize();
final AuthBiometricFingerprintViewModel fpViewModel =
mAuthBiometricFingerprintViewModelProvider.get();
AuthBiometricFingerprintViewBinder.bind(fpView, fpViewModel);
mBiometricView = fpView;
} else if (faceProperties != null) {
mBiometricView = (AuthBiometricFaceView) layoutInflater.inflate(

View File

@@ -73,6 +73,7 @@ import com.android.settingslib.udfps.UdfpsUtils;
import com.android.systemui.CoreStartable;
import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
import com.android.systemui.biometrics.domain.interactor.LogContextInteractor;
import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel;
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -127,6 +128,9 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
// TODO: these should be migrated out once ready
@NonNull private final Provider<BiometricPromptCredentialInteractor> mBiometricPromptInteractor;
@NonNull private final Provider<AuthBiometricFingerprintViewModel>
mAuthBiometricFingerprintViewModelProvider;
@NonNull private final Provider<CredentialViewModel> mCredentialViewModelProvider;
@NonNull private final LogContextInteractor mLogContextInteractor;
@@ -722,7 +726,6 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
}
onDialogDismissed(reason);
}
@Inject
public AuthController(Context context,
Execution execution,
@@ -741,6 +744,8 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
@NonNull UdfpsLogger udfpsLogger,
@NonNull LogContextInteractor logContextInteractor,
@NonNull Provider<BiometricPromptCredentialInteractor> biometricPromptInteractor,
@NonNull Provider<AuthBiometricFingerprintViewModel>
authBiometricFingerprintViewModelProvider,
@NonNull Provider<CredentialViewModel> credentialViewModelProvider,
@NonNull InteractionJankMonitor jankMonitor,
@Main Handler handler,
@@ -771,6 +776,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
mLogContextInteractor = logContextInteractor;
mBiometricPromptInteractor = biometricPromptInteractor;
mAuthBiometricFingerprintViewModelProvider = authBiometricFingerprintViewModelProvider;
mCredentialViewModelProvider = credentialViewModelProvider;
mOrientationListener = new BiometricDisplayListener(
@@ -1299,7 +1305,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
.build(bgExecutor, sensorIds, mFpProps, mFaceProps, wakefulnessLifecycle,
panelInteractionDetector, userManager, lockPatternUtils,
mInteractionJankMonitor, mBiometricPromptInteractor,
mCredentialViewModelProvider);
mAuthBiometricFingerprintViewModelProvider, mCredentialViewModelProvider);
}
@Override

View File

@@ -18,7 +18,6 @@ package com.android.systemui.biometrics
import android.annotation.DrawableRes
import android.content.Context
import android.content.res.Configuration
import android.graphics.drawable.Animatable2
import android.graphics.drawable.AnimatedVectorDrawable
import android.graphics.drawable.Drawable
@@ -94,8 +93,6 @@ abstract class AuthIconController(
/** Called during [onAnimationEnd] if the controller is not [deactivated]. */
open fun handleAnimationEnd(drawable: Drawable) {}
open fun onConfigurationChanged(newConfig: Configuration) {}
// TODO(b/251476085): Migrate this to an extension at the appropriate level?
/** Load the given [rawResources] immediately so they are cached for use in the [context]. */
protected fun cacheLottieAssetsInContext(context: Context, vararg rawResources: Int) {

View File

@@ -55,6 +55,7 @@ import com.airbnb.lottie.model.KeyPath
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
@@ -84,6 +85,7 @@ constructor(
private val activityTaskManager: ActivityTaskManager,
overviewProxyService: OverviewProxyService,
displayManager: DisplayManager,
private val displayStateInteractor: DisplayStateInteractor,
@Main private val mainExecutor: DelayableExecutor,
@Main private val handler: Handler,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
@@ -203,14 +205,16 @@ constructor(
request: SideFpsUiRequestSource,
@BiometricOverlayConstants.ShowReason reason: Int = BiometricOverlayConstants.REASON_UNKNOWN
) {
requests.add(request)
mainExecutor.execute {
if (overlayView == null) {
traceSection("SideFpsController#show(request=${request.name}, reason=$reason") {
createOverlayForDisplay(reason)
if (!displayStateInteractor.isInRearDisplayMode.value) {
requests.add(request)
mainExecutor.execute {
if (overlayView == null) {
traceSection("SideFpsController#show(request=${request.name}, reason=$reason") {
createOverlayForDisplay(reason)
}
} else {
Log.v(TAG, "overlay already shown")
}
} else {
Log.v(TAG, "overlay already shown")
}
}
}

View File

@@ -21,8 +21,12 @@ import com.android.systemui.biometrics.data.repository.FingerprintPropertyReposi
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepositoryImpl
import com.android.systemui.biometrics.data.repository.PromptRepository
import com.android.systemui.biometrics.data.repository.PromptRepositoryImpl
import com.android.systemui.biometrics.data.repository.RearDisplayStateRepository
import com.android.systemui.biometrics.data.repository.RearDisplayStateRepositoryImpl
import com.android.systemui.biometrics.domain.interactor.CredentialInteractor
import com.android.systemui.biometrics.domain.interactor.CredentialInteractorImpl
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
import com.android.systemui.biometrics.domain.interactor.LogContextInteractor
import com.android.systemui.biometrics.domain.interactor.LogContextInteractorImpl
import com.android.systemui.dagger.SysUISingleton
@@ -45,11 +49,18 @@ interface BiometricsModule {
@SysUISingleton
fun fingerprintRepository(impl: FingerprintPropertyRepositoryImpl):
FingerprintPropertyRepository
@Binds
@SysUISingleton
fun rearDisplayStateRepository(impl: RearDisplayStateRepositoryImpl): RearDisplayStateRepository
@Binds
@SysUISingleton
fun providesCredentialInteractor(impl: CredentialInteractorImpl): CredentialInteractor
@Binds
@SysUISingleton
fun providesDisplayStateInteractor(impl: DisplayStateInteractorImpl): DisplayStateInteractor
@Binds
@SysUISingleton
fun bindsLogContextInteractor(impl: LogContextInteractorImpl): LogContextInteractor

View File

@@ -0,0 +1,85 @@
/*
* 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.biometrics.data.repository
import android.content.Context
import android.hardware.devicestate.DeviceStateManager
import com.android.internal.util.ArrayUtils
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.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn
/** Provide current rear display state. */
interface RearDisplayStateRepository {
/** Provides the current rear display state. */
val isInRearDisplayMode: StateFlow<Boolean>
}
@SysUISingleton
class RearDisplayStateRepositoryImpl
@Inject
constructor(
@Application applicationScope: CoroutineScope,
@Application context: Context,
deviceStateManager: DeviceStateManager,
@Main mainExecutor: Executor
) : RearDisplayStateRepository {
override val isInRearDisplayMode: StateFlow<Boolean> =
conflatedCallbackFlow {
val sendRearDisplayStateUpdate = { state: Boolean ->
trySendWithFailureLogging(
state,
TAG,
"Error sending rear display state update to $state"
)
}
val callback =
DeviceStateManager.DeviceStateCallback { state ->
val isInRearDisplayMode =
ArrayUtils.contains(
context.resources.getIntArray(
com.android.internal.R.array.config_rearDisplayDeviceStates
),
state
)
sendRearDisplayStateUpdate(isInRearDisplayMode)
}
sendRearDisplayStateUpdate(false)
deviceStateManager.registerCallback(mainExecutor, callback)
awaitClose { deviceStateManager.unregisterCallback(callback) }
}
.stateIn(
applicationScope,
started = SharingStarted.Eagerly,
initialValue = false,
)
companion object {
const val TAG = "RearDisplayStateRepositoryImpl"
}
}

View File

@@ -0,0 +1,101 @@
/*
* 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.biometrics.domain.interactor
import android.content.Context
import android.content.res.Configuration
import com.android.systemui.biometrics.data.repository.RearDisplayStateRepository
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
import com.android.systemui.unfold.updates.FoldProvider
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn
/** Aggregates display state information. */
interface DisplayStateInteractor {
/** Whether the device is currently in rear display mode. */
val isInRearDisplayMode: StateFlow<Boolean>
/** Whether the device is currently folded. */
val isFolded: Flow<Boolean>
/** Called on configuration changes, used to keep the display state in sync */
fun onConfigurationChanged(newConfig: Configuration)
}
/** Encapsulates logic for interacting with the display state. */
class DisplayStateInteractorImpl
@Inject
constructor(
@Application applicationScope: CoroutineScope,
@Application context: Context,
@Main mainExecutor: Executor,
rearDisplayStateRepository: RearDisplayStateRepository,
) : DisplayStateInteractor {
private var screenSizeFoldProvider: ScreenSizeFoldProvider = ScreenSizeFoldProvider(context)
fun setScreenSizeFoldProvider(foldProvider: ScreenSizeFoldProvider) {
screenSizeFoldProvider = foldProvider
}
override val isFolded: Flow<Boolean> =
conflatedCallbackFlow {
val sendFoldStateUpdate = { state: Boolean ->
trySendWithFailureLogging(
state,
TAG,
"Error sending fold state update to $state"
)
}
val callback =
object : FoldProvider.FoldCallback {
override fun onFoldUpdated(isFolded: Boolean) {
sendFoldStateUpdate(isFolded)
}
}
sendFoldStateUpdate(false)
screenSizeFoldProvider.registerCallback(callback, mainExecutor)
awaitClose { screenSizeFoldProvider.unregisterCallback(callback) }
}
.stateIn(
applicationScope,
started = SharingStarted.Eagerly,
initialValue = false,
)
override val isInRearDisplayMode: StateFlow<Boolean> =
rearDisplayStateRepository.isInRearDisplayMode
override fun onConfigurationChanged(newConfig: Configuration) {
screenSizeFoldProvider.onConfigurationChange(newConfig)
}
companion object {
private const val TAG = "DisplayStateInteractor"
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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.biometrics.ui.binder
import android.view.Surface
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.biometrics.AuthBiometricFingerprintView
import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import kotlinx.coroutines.launch
object AuthBiometricFingerprintViewBinder {
/** Binds a [AuthBiometricFingerprintView] to a [AuthBiometricFingerprintViewModel]. */
@JvmStatic
fun bind(view: AuthBiometricFingerprintView, viewModel: AuthBiometricFingerprintViewModel) {
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.onConfigurationChanged(view.context.resources.configuration)
viewModel.setRotation(view.context.display?.orientation ?: Surface.ROTATION_0)
launch {
viewModel.iconAsset.collect { iconAsset ->
view.updateIconViewAnimation(iconAsset)
}
}
}
}
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.android.systemui.biometrics.ui.viewmodel
import android.annotation.RawRes
import android.content.res.Configuration
import android.view.Surface
import com.android.systemui.R
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
/** Models UI of AuthBiometricFingerprintView to support rear display state changes. */
class AuthBiometricFingerprintViewModel
@Inject
constructor(private val interactor: DisplayStateInteractor) {
/** Current device rotation. */
private var rotation: Int = Surface.ROTATION_0
/** Current AuthBiometricFingerprintView asset. */
val iconAsset: Flow<Int> =
combine(interactor.isFolded, interactor.isInRearDisplayMode) {
isFolded: Boolean,
isInRearDisplayMode: Boolean ->
getSideFpsAnimationAsset(isFolded, isInRearDisplayMode)
}
@RawRes
private fun getSideFpsAnimationAsset(
isDeviceFolded: Boolean,
isInRearDisplayMode: Boolean,
): Int =
when (rotation) {
Surface.ROTATION_90 ->
if (isInRearDisplayMode) {
R.raw.biometricprompt_rear_portrait_reverse_base
} else if (isDeviceFolded) {
R.raw.biometricprompt_folded_base_topleft
} else {
R.raw.biometricprompt_portrait_base_topleft
}
Surface.ROTATION_270 ->
if (isInRearDisplayMode) {
R.raw.biometricprompt_rear_portrait_base
} else if (isDeviceFolded) {
R.raw.biometricprompt_folded_base_bottomright
} else {
R.raw.biometricprompt_portrait_base_bottomright
}
else ->
if (isInRearDisplayMode) {
R.raw.biometricprompt_rear_landscape_base
} else if (isDeviceFolded) {
R.raw.biometricprompt_folded_base_default
} else {
R.raw.biometricprompt_landscape_base
}
}
/** Called on configuration changes */
fun onConfigurationChanged(newConfig: Configuration) {
interactor.onConfigurationChanged(newConfig)
}
fun setRotation(newRotation: Int) {
rotation = newRotation
}
}

View File

@@ -40,14 +40,19 @@ import com.android.internal.widget.LockPatternUtils
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakePromptRepository
import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor
import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import org.junit.After
import org.junit.Ignore
import org.junit.Rule
@@ -87,13 +92,26 @@ class AuthContainerViewTest : SysuiTestCase() {
@Mock
lateinit var interactionJankMonitor: InteractionJankMonitor
private val testScope = TestScope(StandardTestDispatcher())
private val fakeExecutor = FakeExecutor(FakeSystemClock())
private val biometricPromptRepository = FakePromptRepository()
private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
private val credentialInteractor = FakeCredentialInteractor()
private val bpCredentialInteractor = BiometricPromptCredentialInteractor(
Dispatchers.Main.immediate,
biometricPromptRepository,
credentialInteractor
)
private val displayStateInteractor = DisplayStateInteractorImpl(
testScope.backgroundScope,
mContext,
fakeExecutor,
rearDisplayStateRepository
)
private val authBiometricFingerprintViewModel = AuthBiometricFingerprintViewModel(
displayStateInteractor
)
private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)
private var authContainer: TestAuthContainerView? = null
@@ -469,9 +487,10 @@ class AuthContainerViewTest : SysuiTestCase() {
lockPatternUtils,
interactionJankMonitor,
{ bpCredentialInteractor },
{ authBiometricFingerprintViewModel },
{ credentialViewModel },
Handler(TestableLooper.get(this).looper),
FakeExecutor(FakeSystemClock())
fakeExecutor
) {
override fun postOnAnimation(runnable: Runnable) {
runnable.run()

View File

@@ -93,6 +93,7 @@ import com.android.systemui.RoboPilotTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.domain.interactor.BiometricPromptCredentialInteractor;
import com.android.systemui.biometrics.domain.interactor.LogContextInteractor;
import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel;
import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.statusbar.CommandQueue;
@@ -172,6 +173,8 @@ public class AuthControllerTest extends SysuiTestCase {
@Mock
private BiometricPromptCredentialInteractor mBiometricPromptCredentialInteractor;
@Mock
private AuthBiometricFingerprintViewModel mAuthBiometricFingerprintViewModel;
@Mock
private CredentialViewModel mCredentialViewModel;
@Mock
private UdfpsUtils mUdfpsUtils;
@@ -995,8 +998,9 @@ public class AuthControllerTest extends SysuiTestCase {
() -> mSideFpsController, mDisplayManager, mWakefulnessLifecycle,
mPanelInteractionDetector, mUserManager, mLockPatternUtils, mUdfpsLogger,
mLogContextInteractor, () -> mBiometricPromptCredentialInteractor,
() -> mCredentialViewModel, mInteractionJankMonitor, mHandler,
mBackgroundExecutor, mVibratorHelper, mUdfpsUtils);
() -> mAuthBiometricFingerprintViewModel, () -> mCredentialViewModel,
mInteractionJankMonitor, mHandler, mBackgroundExecutor, mVibratorHelper,
mUdfpsUtils);
}
@Override

View File

@@ -55,6 +55,9 @@ import com.android.systemui.R
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestableContext
import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
@@ -66,7 +69,9 @@ import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestCoroutineScope
import kotlinx.coroutines.test.TestScope
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -90,6 +95,8 @@ import org.mockito.junit.MockitoJUnit
private const val DISPLAY_ID = 2
private const val SENSOR_ID = 1
private const val REAR_DISPLAY_MODE_DEVICE_STATE = 3
@SmallTest
@RoboPilotTest
@RunWith(AndroidJUnit4::class)
@@ -112,7 +119,12 @@ class SideFpsControllerTest : SysuiTestCase() {
private lateinit var keyguardBouncerRepository: FakeKeyguardBouncerRepository
private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
private lateinit var displayStateInteractor: DisplayStateInteractor
private val executor = FakeExecutor(FakeSystemClock())
private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
private val testScope = TestScope(StandardTestDispatcher())
private lateinit var overlayController: ISidefpsController
private lateinit var sideFpsController: SideFpsController
@@ -142,6 +154,13 @@ class SideFpsControllerTest : SysuiTestCase() {
FakeDeviceEntryFingerprintAuthRepository(),
FakeSystemClock(),
)
displayStateInteractor =
DisplayStateInteractorImpl(
testScope.backgroundScope,
context,
executor,
rearDisplayStateRepository
)
context.addMockSystemService(DisplayManager::class.java, displayManager)
context.addMockSystemService(WindowManager::class.java, windowManager)
@@ -168,6 +187,7 @@ class SideFpsControllerTest : SysuiTestCase() {
isReverseDefaultRotation: Boolean = false,
initInfo: DisplayInfo.() -> Unit = {},
windowInsets: WindowInsets = insetsForSmallNavbar(),
inRearDisplayMode: Boolean = false,
block: () -> Unit
) {
this.deviceConfig = deviceConfig
@@ -228,6 +248,13 @@ class SideFpsControllerTest : SysuiTestCase() {
isReverseDefaultRotation
)
val rearDisplayDeviceStates =
if (inRearDisplayMode) intArrayOf(REAR_DISPLAY_MODE_DEVICE_STATE) else intArrayOf()
sideFpsControllerContext.orCreateTestableResources.addOverride(
com.android.internal.R.array.config_rearDisplayDeviceStates,
rearDisplayDeviceStates
)
sideFpsController =
SideFpsController(
sideFpsControllerContext,
@@ -237,12 +264,14 @@ class SideFpsControllerTest : SysuiTestCase() {
activityTaskManager,
overviewProxyService,
displayManager,
displayStateInteractor,
executor,
handler,
alternateBouncerInteractor,
TestCoroutineScope(),
dumpManager,
dumpManager
)
rearDisplayStateRepository.setIsInRearDisplayMode(inRearDisplayMode)
overlayController =
ArgumentCaptor.forClass(ISidefpsController::class.java)
@@ -584,10 +613,62 @@ class SideFpsControllerTest : SysuiTestCase() {
verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible = true)
}
@Test
fun verifiesSfpsIndicatorNotAddedInRearDisplayMode_0() =
testWithDisplay(
deviceConfig = DeviceConfig.Y_ALIGNED,
isReverseDefaultRotation = false,
{ rotation = Surface.ROTATION_0 },
inRearDisplayMode = true,
) {
verifySfpsIndicator_notAdded_InRearDisplayMode()
}
@Test
fun verifiesSfpsIndicatorNotAddedInRearDisplayMode_90() =
testWithDisplay(
deviceConfig = DeviceConfig.Y_ALIGNED,
isReverseDefaultRotation = false,
{ rotation = Surface.ROTATION_90 },
inRearDisplayMode = true,
) {
verifySfpsIndicator_notAdded_InRearDisplayMode()
}
@Test
fun verifiesSfpsIndicatorNotAddedInRearDisplayMode_180() =
testWithDisplay(
deviceConfig = DeviceConfig.Y_ALIGNED,
isReverseDefaultRotation = false,
{ rotation = Surface.ROTATION_180 },
inRearDisplayMode = true,
) {
verifySfpsIndicator_notAdded_InRearDisplayMode()
}
@Test
fun verifiesSfpsIndicatorNotAddedInRearDisplayMode_270() =
testWithDisplay(
deviceConfig = DeviceConfig.Y_ALIGNED,
isReverseDefaultRotation = false,
{ rotation = Surface.ROTATION_270 },
inRearDisplayMode = true,
) {
verifySfpsIndicator_notAdded_InRearDisplayMode()
}
private fun verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible: Boolean) {
sideFpsController.overlayOffsets = sensorLocation
}
private fun verifySfpsIndicator_notAdded_InRearDisplayMode() {
sideFpsController.overlayOffsets = sensorLocation
overlayController.show(SENSOR_ID, REASON_UNKNOWN)
executor.runAllReady()
verify(windowManager, never()).addView(any(), any())
}
fun alternateBouncerVisibility_showAndHideSideFpsUI() = testWithDisplay {
// WHEN alternate bouncer is visible
keyguardBouncerRepository.setAlternateVisible(true)
@@ -624,7 +705,7 @@ class SideFpsControllerTest : SysuiTestCase() {
* in other rotations have been omitted.
*/
@Test
fun verifiesIndicatorPlacementForXAlignedSensor_0() {
fun verifiesIndicatorPlacementForXAlignedSensor_0() =
testWithDisplay(
deviceConfig = DeviceConfig.X_ALIGNED,
isReverseDefaultRotation = false,
@@ -641,7 +722,6 @@ class SideFpsControllerTest : SysuiTestCase() {
assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
}
}
/**
* {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_270
@@ -650,7 +730,7 @@ class SideFpsControllerTest : SysuiTestCase() {
* correctly, tests for indicator placement in other rotations have been omitted.
*/
@Test
fun verifiesIndicatorPlacementForXAlignedSensor_InReverseDefaultRotation_270() {
fun verifiesIndicatorPlacementForXAlignedSensor_InReverseDefaultRotation_270() =
testWithDisplay(
deviceConfig = DeviceConfig.X_ALIGNED,
isReverseDefaultRotation = true,
@@ -667,7 +747,6 @@ class SideFpsControllerTest : SysuiTestCase() {
assertThat(overlayViewParamsCaptor.value.x).isEqualTo(sensorLocation.sensorLocationX)
assertThat(overlayViewParamsCaptor.value.y).isEqualTo(0)
}
}
/**
* {@link SideFpsController#updateOverlayParams} calculates indicator placement for ROTATION_0,

View File

@@ -0,0 +1,100 @@
/*
* 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 android.hardware.devicestate.DeviceStateManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.RearDisplayStateRepository
import com.android.systemui.biometrics.data.repository.RearDisplayStateRepositoryImpl
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule
private const val NORMAL_DISPLAY_MODE_DEVICE_STATE = 2
private const val REAR_DISPLAY_MODE_DEVICE_STATE = 3
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class RearDisplayStateRepositoryTest : SysuiTestCase() {
@JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
@Mock private lateinit var deviceStateManager: DeviceStateManager
private lateinit var underTest: RearDisplayStateRepository
private val testScope = TestScope(StandardTestDispatcher())
private val fakeExecutor = FakeExecutor(FakeSystemClock())
@Captor
private lateinit var callbackCaptor: ArgumentCaptor<DeviceStateManager.DeviceStateCallback>
@Before
fun setUp() {
val rearDisplayDeviceStates = intArrayOf(REAR_DISPLAY_MODE_DEVICE_STATE)
mContext.orCreateTestableResources.addOverride(
com.android.internal.R.array.config_rearDisplayDeviceStates,
rearDisplayDeviceStates
)
underTest =
RearDisplayStateRepositoryImpl(
testScope.backgroundScope,
mContext,
deviceStateManager,
fakeExecutor
)
}
@Test
fun updatesIsInRearDisplayMode_whenRearDisplayStateChanges() =
testScope.runTest {
val isInRearDisplayMode = collectLastValue(underTest.isInRearDisplayMode)
runCurrent()
val callback = deviceStateManager.captureCallback()
callback.onStateChanged(NORMAL_DISPLAY_MODE_DEVICE_STATE)
assertThat(isInRearDisplayMode()).isFalse()
callback.onStateChanged(REAR_DISPLAY_MODE_DEVICE_STATE)
assertThat(isInRearDisplayMode()).isTrue()
}
}
private fun DeviceStateManager.captureCallback() =
withArgCaptor<DeviceStateManager.DeviceStateCallback> {
verify(this@captureCallback).registerCallback(any(), capture())
}

View File

@@ -0,0 +1,84 @@
package com.android.systemui.biometrics.domain.interactor
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
import com.android.systemui.unfold.updates.FoldProvider
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class DisplayStateInteractorImplTest : SysuiTestCase() {
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
private val fakeExecutor = FakeExecutor(FakeSystemClock())
private val testScope = TestScope(StandardTestDispatcher())
private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
@Mock private lateinit var screenSizeFoldProvider: ScreenSizeFoldProvider
private lateinit var interactor: DisplayStateInteractorImpl
@Before
fun setup() {
interactor =
DisplayStateInteractorImpl(
testScope.backgroundScope,
mContext,
fakeExecutor,
rearDisplayStateRepository
)
interactor.setScreenSizeFoldProvider(screenSizeFoldProvider)
}
@Test
fun isInRearDisplayModeChanges() =
testScope.runTest {
val isInRearDisplayMode = collectLastValue(interactor.isInRearDisplayMode)
rearDisplayStateRepository.setIsInRearDisplayMode(false)
assertThat(isInRearDisplayMode()).isFalse()
rearDisplayStateRepository.setIsInRearDisplayMode(true)
assertThat(isInRearDisplayMode()).isTrue()
}
@Test
fun isFoldedChanges() =
testScope.runTest {
val isFolded = collectLastValue(interactor.isFolded)
runCurrent()
val callback = screenSizeFoldProvider.captureCallback()
callback.onFoldUpdated(isFolded = true)
assertThat(isFolded()).isTrue()
callback.onFoldUpdated(isFolded = false)
assertThat(isFolded()).isFalse()
}
}
private fun FoldProvider.captureCallback() =
withArgCaptor<FoldProvider.FoldCallback> {
verify(this@captureCallback).registerCallback(capture(), any())
}

View File

@@ -0,0 +1,69 @@
package com.android.systemui.biometrics.ui.viewmodel
import android.content.res.Configuration
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
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.junit.runners.JUnit4
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class AuthBiometricFingerprintViewModelTest : SysuiTestCase() {
private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
private val testScope = TestScope(StandardTestDispatcher())
private val fakeExecutor = FakeExecutor(FakeSystemClock())
private lateinit var interactor: DisplayStateInteractor
private lateinit var viewModel: AuthBiometricFingerprintViewModel
@Before
fun setup() {
interactor =
DisplayStateInteractorImpl(
testScope.backgroundScope,
mContext,
fakeExecutor,
rearDisplayStateRepository
)
viewModel = AuthBiometricFingerprintViewModel(interactor)
}
@Test
fun iconUpdates_onConfigurationChanged() {
testScope.runTest {
runCurrent()
val testConfig = Configuration()
val folded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP - 1
val unfolded = INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP + 1
val currentIcon = collectLastValue(viewModel.iconAsset)
testConfig.smallestScreenWidthDp = folded
viewModel.onConfigurationChanged(testConfig)
val foldedIcon = currentIcon()
testConfig.smallestScreenWidthDp = unfolded
viewModel.onConfigurationChanged(testConfig)
val unfoldedIcon = currentIcon()
assertThat(foldedIcon).isNotEqualTo(unfoldedIcon)
}
}
}
internal const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600

View File

@@ -0,0 +1,31 @@
/*
* 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.biometrics.data.repository
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class FakeRearDisplayStateRepository : RearDisplayStateRepository {
private val _isInRearDisplayMode = MutableStateFlow<Boolean>(false)
override val isInRearDisplayMode: StateFlow<Boolean> = _isInRearDisplayMode.asStateFlow()
fun setIsInRearDisplayMode(isInRearDisplayMode: Boolean) {
_isInRearDisplayMode.value = isInRearDisplayMode
}
}