Add assets and rear display state handling in SideFpsController and BiometricPrompt
Adds new assets for BiometricPrompt in rear display mode, and adds RearDisplayState repository and interactor to update and retrieve rear display state, to properly show/hide the SFPS indicator and show the correct assets in BiometricPrompt. Also adds new View-ViewBinder-ViewModel structure Test: atest AuthControllerTest Test: atest SideFpsControllerTest Test: atest RearDisplayStateRepositoryTest Test: atest DisplayStateInteractorImplTest Test: atest AuthBiometricFingerprintViewModelTest Test: (manual) Install dogfood camera app at b/264499484#comment11, then check indicator shows up correctly in all orientations in normal display mode, and indicator is not visible in all orientations in rear display mode Fixes: 265198124 Change-Id: If2f288514ef328391084e0be9eb4eeb82d9d34b4 Merged-In: If2f288514ef328391084e0be9eb4eeb82d9d34b4
This commit is contained in:
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
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user