[Keyguard Bouncer] Add new data flow.

Add MVVM architecture to the existing bouncer logic. Make this feature
flag enabled but true by default.

Bug: 240298897
Test: Added unit tests and test behavior on device.
- Tested on Large screen and standard screen device.
- Unlock sim
- Unlock pattern
- Unlock password
- Unlock from tapping settings icon in LS
- Unlock from dream
- Sim pin unlock
- Sim pin -> sim puk unlock
- Tested with feature flag off.

Change-Id: I0391f552b628991f6504a8c032dbe0e2ad65e859
This commit is contained in:
Aaron Liu
2022-07-26 13:37:19 -07:00
parent 9958359ced
commit 8854bfa19a
24 changed files with 1734 additions and 60 deletions

View File

@@ -103,6 +103,7 @@
android:layout_width="match_parent"
android:layout_weight="1"
android:background="@android:color/transparent"
android:visibility="invisible"
android:clipChildren="false"
android:clipToPadding="false" />
</LinearLayout>

View File

@@ -1098,6 +1098,7 @@ public class KeyguardSecurityContainer extends FrameLayout {
return;
}
mView.setAlpha(1f);
mUserSwitcherViewGroup.setAlpha(0f);
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mUserSwitcherViewGroup, View.ALPHA,
1f);

View File

@@ -106,6 +106,14 @@ public class KeyguardSimPukViewController
mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
}
@Override
public void onResume(int reason) {
super.onResume(reason);
if (mShowDefaultMessage) {
showDefaultMessage();
}
}
@Override
void resetState() {
super.resetState();

View File

@@ -41,6 +41,7 @@ import com.android.systemui.dreams.dagger.DreamModule;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FlagsModule;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.data.BouncerViewModule;
import com.android.systemui.log.dagger.LogModule;
import com.android.systemui.media.dagger.MediaProjectionModule;
import com.android.systemui.model.SysUiState;
@@ -116,6 +117,7 @@ import dagger.Provides;
AppOpsModule.class,
AssistModule.class,
BiometricsModule.class,
BouncerViewModule.class,
ClockModule.class,
CoroutinesModule.class,
DreamModule.class,

View File

@@ -35,6 +35,7 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.complication.ComplicationHostViewController;
import com.android.systemui.dreams.dagger.DreamOverlayComponent;
import com.android.systemui.dreams.dagger.DreamOverlayModule;
import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
import com.android.systemui.statusbar.BlurUtils;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -73,6 +74,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
// Main thread handler used to schedule periodic tasks (e.g. burn-in protection updates).
private final Handler mHandler;
private final int mDreamOverlayMaxTranslationY;
private final BouncerCallbackInteractor mBouncerCallbackInteractor;
private long mJitterStartTimeMillis;
@@ -131,7 +133,8 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
@Named(DreamOverlayModule.MAX_BURN_IN_OFFSET) int maxBurnInOffset,
@Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long
burnInProtectionUpdateInterval,
@Named(DreamOverlayModule.MILLIS_UNTIL_FULL_JITTER) long millisUntilFullJitter) {
@Named(DreamOverlayModule.MILLIS_UNTIL_FULL_JITTER) long millisUntilFullJitter,
BouncerCallbackInteractor bouncerCallbackInteractor) {
super(containerView);
mDreamOverlayContentView = contentView;
mStatusBarViewController = statusBarViewController;
@@ -151,6 +154,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
mMaxBurnInOffset = maxBurnInOffset;
mBurnInProtectionUpdateInterval = burnInProtectionUpdateInterval;
mMillisUntilFullJitter = millisUntilFullJitter;
mBouncerCallbackInteractor = bouncerCallbackInteractor;
}
@Override
@@ -167,6 +171,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
if (bouncer != null) {
bouncer.addBouncerExpansionCallback(mBouncerExpansionCallback);
}
mBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback);
}
@Override
@@ -176,6 +181,7 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
if (bouncer != null) {
bouncer.removeBouncerExpansionCallback(mBouncerExpansionCallback);
}
mBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
}
View getContainerView() {

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.systemui.keyguard.data
import android.view.KeyEvent
import com.android.systemui.dagger.SysUISingleton
import java.lang.ref.WeakReference
import javax.inject.Inject
/** An abstraction to interface with the ui layer, without changing state. */
interface BouncerView {
var delegate: BouncerViewDelegate?
}
/** A lightweight class to hold reference to the ui delegate. */
@SysUISingleton
class BouncerViewImpl @Inject constructor() : BouncerView {
private var _delegate: WeakReference<BouncerViewDelegate?> = WeakReference(null)
override var delegate: BouncerViewDelegate?
get() = _delegate.get()
set(value) {
_delegate = WeakReference(value)
}
}
/** An abstraction that implements view logic. */
interface BouncerViewDelegate {
fun isFullScreenBouncer(): Boolean
fun shouldDismissOnMenuPressed(): Boolean
fun interceptMediaKey(event: KeyEvent?): Boolean
fun dispatchBackKeyEventPreIme(): Boolean
fun showNextSecurityScreenOrFinish(): Boolean
fun resume()
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.keyguard.data
import dagger.Binds
import dagger.Module
@Module
interface BouncerViewModule {
/** Binds BouncerView to BouncerViewImpl and makes it injectable. */
@Binds fun bindBouncerView(bouncerViewImpl: BouncerViewImpl): BouncerView
}

View File

@@ -0,0 +1,154 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.systemui.keyguard.data.repository
import android.hardware.biometrics.BiometricSourceType
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.keyguard.ViewMediatorCallback
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_HIDDEN
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
/** Encapsulates app state for the lock screen bouncer. */
@SysUISingleton
class KeyguardBouncerRepository
@Inject
constructor(
private val viewMediatorCallback: ViewMediatorCallback,
keyguardUpdateMonitor: KeyguardUpdateMonitor,
) {
var bouncerPromptReason: Int? = null
/** Determines if we want to instantaneously show the bouncer instead of translating. */
private val _isScrimmed = MutableStateFlow(false)
val isScrimmed = _isScrimmed.asStateFlow()
/** Set amount of how much of the bouncer is showing on the screen */
private val _expansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
val expansionAmount = _expansionAmount.asStateFlow()
private val _isVisible = MutableStateFlow(false)
val isVisible = _isVisible.asStateFlow()
private val _show = MutableStateFlow<KeyguardBouncerModel?>(null)
val show = _show.asStateFlow()
private val _showingSoon = MutableStateFlow(false)
val showingSoon = _showingSoon.asStateFlow()
private val _hide = MutableStateFlow(false)
val hide = _hide.asStateFlow()
private val _startingToHide = MutableStateFlow(false)
val startingToHide = _startingToHide.asStateFlow()
private val _onDismissAction = MutableStateFlow<BouncerCallbackActionsModel?>(null)
val onDismissAction = _onDismissAction.asStateFlow()
private val _disappearAnimation = MutableStateFlow<Runnable?>(null)
val startingDisappearAnimation = _disappearAnimation.asStateFlow()
private val _keyguardPosition = MutableStateFlow(0f)
val keyguardPosition = _keyguardPosition.asStateFlow()
private val _resourceUpdateRequests = MutableStateFlow(false)
val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
val showMessage = _showMessage.asStateFlow()
private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
/** Determines if user is already unlocked */
val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
private val _onScreenTurnedOff = MutableStateFlow(false)
val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
val bouncerErrorMessage: CharSequence?
get() = viewMediatorCallback.consumeCustomMessage()
init {
val callback =
object : KeyguardUpdateMonitorCallback() {
override fun onStrongAuthStateChanged(userId: Int) {
bouncerPromptReason = viewMediatorCallback.bouncerPromptReason
}
override fun onLockedOutStateChanged(type: BiometricSourceType) {
if (type == BiometricSourceType.FINGERPRINT) {
bouncerPromptReason = viewMediatorCallback.bouncerPromptReason
}
}
}
keyguardUpdateMonitor.registerCallback(callback)
}
fun setScrimmed(isScrimmed: Boolean) {
_isScrimmed.value = isScrimmed
}
fun setExpansion(expansion: Float) {
_expansionAmount.value = expansion
}
fun setVisible(isVisible: Boolean) {
_isVisible.value = isVisible
}
fun setShow(keyguardBouncerModel: KeyguardBouncerModel?) {
_show.value = keyguardBouncerModel
}
fun setShowingSoon(showingSoon: Boolean) {
_showingSoon.value = showingSoon
}
fun setHide(hide: Boolean) {
_hide.value = hide
}
fun setStartingToHide(startingToHide: Boolean) {
_startingToHide.value = startingToHide
}
fun setOnDismissAction(bouncerCallbackActionsModel: BouncerCallbackActionsModel?) {
_onDismissAction.value = bouncerCallbackActionsModel
}
fun setStartDisappearAnimation(runnable: Runnable?) {
_disappearAnimation.value = runnable
}
fun setKeyguardPosition(keyguardPosition: Float) {
_keyguardPosition.value = keyguardPosition
}
fun setResourceUpdateRequests(willUpdateResources: Boolean) {
_resourceUpdateRequests.value = willUpdateResources
}
fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
_showMessage.value = bouncerShowMessageModel
}
fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
_keyguardAuthenticated.value = keyguardAuthenticated
}
fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
_isBackButtonEnabled.value = isBackButtonEnabled
}
fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) {
_onScreenTurnedOff.value = onScreenTurnedOff
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.systemui.keyguard.domain.interactor
import android.view.View
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.phone.KeyguardBouncer
import com.android.systemui.util.ListenerSet
import javax.inject.Inject
/** Interactor to add and remove callbacks for the bouncer. */
@SysUISingleton
class BouncerCallbackInteractor @Inject constructor() {
private var resetCallbacks = ListenerSet<KeyguardBouncer.KeyguardResetCallback>()
private var expansionCallbacks = ArrayList<KeyguardBouncer.BouncerExpansionCallback>()
/** Add a KeyguardResetCallback. */
fun addKeyguardResetCallback(callback: KeyguardBouncer.KeyguardResetCallback) {
resetCallbacks.addIfAbsent(callback)
}
/** Remove a KeyguardResetCallback. */
fun removeKeyguardResetCallback(callback: KeyguardBouncer.KeyguardResetCallback) {
resetCallbacks.remove(callback)
}
/** Adds a callback to listen to bouncer expansion updates. */
fun addBouncerExpansionCallback(callback: KeyguardBouncer.BouncerExpansionCallback) {
if (!expansionCallbacks.contains(callback)) {
expansionCallbacks.add(callback)
}
}
/**
* Removes a previously added callback. If the callback was never added, this method does
* nothing.
*/
fun removeBouncerExpansionCallback(callback: KeyguardBouncer.BouncerExpansionCallback) {
expansionCallbacks.remove(callback)
}
/** Propagate fully shown to bouncer expansion callbacks. */
fun dispatchFullyShown() {
for (callback in expansionCallbacks) {
callback.onFullyShown()
}
}
/** Propagate starting to hide to bouncer expansion callbacks. */
fun dispatchStartingToHide() {
for (callback in expansionCallbacks) {
callback.onStartingToHide()
}
}
/** Propagate starting to show to bouncer expansion callbacks. */
fun dispatchStartingToShow() {
for (callback in expansionCallbacks) {
callback.onStartingToShow()
}
}
/** Propagate fully hidden to bouncer expansion callbacks. */
fun dispatchFullyHidden() {
for (callback in expansionCallbacks) {
callback.onFullyHidden()
}
}
/** Propagate expansion changes to bouncer expansion callbacks. */
fun dispatchExpansionChanged(expansion: Float) {
for (callback in expansionCallbacks) {
callback.onExpansionChanged(expansion)
}
}
/** Propagate visibility changes to bouncer expansion callbacks. */
fun dispatchVisibilityChanged(visibility: Int) {
for (callback in expansionCallbacks) {
callback.onVisibilityChanged(visibility == View.VISIBLE)
}
}
/** Propagate keyguard reset. */
fun dispatchReset() {
for (callback in resetCallbacks) {
callback.onKeyguardReset()
}
}
}

View File

@@ -0,0 +1,324 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.keyguard.domain.interactor
import android.content.res.ColorStateList
import android.os.Handler
import android.os.Trace
import android.os.UserHandle
import android.os.UserManager
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.DejankUtils
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.statusbar.phone.KeyguardBouncer
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
/** Encapsulates business logic for interacting with the lock-screen bouncer. */
@SysUISingleton
class BouncerInteractor
@Inject
constructor(
private val repository: KeyguardBouncerRepository,
private val bouncerView: BouncerView,
@Main private val mainHandler: Handler,
private val keyguardStateController: KeyguardStateController,
private val keyguardSecurityModel: KeyguardSecurityModel,
private val callbackInteractor: BouncerCallbackInteractor,
private val falsingCollector: FalsingCollector,
private val dismissCallbackRegistry: DismissCallbackRegistry,
keyguardBypassController: KeyguardBypassController,
keyguardUpdateMonitor: KeyguardUpdateMonitor,
) {
/** Whether we want to wait for face auth. */
private val bouncerFaceDelay =
keyguardStateController.isFaceAuthEnabled &&
!keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
KeyguardUpdateMonitor.getCurrentUser()
) &&
!needsFullscreenBouncer() &&
!keyguardUpdateMonitor.userNeedsStrongAuth() &&
!keyguardBypassController.bypassEnabled
/** Runnable to show the bouncer. */
val showRunnable = Runnable {
repository.setVisible(true)
repository.setShow(
KeyguardBouncerModel(
promptReason = repository.bouncerPromptReason ?: 0,
errorMessage = repository.bouncerErrorMessage,
expansionAmount = repository.expansionAmount.value
)
)
repository.setShowingSoon(false)
}
val keyguardAuthenticated: Flow<Boolean> = repository.keyguardAuthenticated.filterNotNull()
val screenTurnedOff: Flow<Unit> = repository.onScreenTurnedOff.filter { it }.map {}
val show: Flow<KeyguardBouncerModel> = repository.show.filterNotNull()
val hide: Flow<Unit> = repository.hide.filter { it }.map {}
val startingToHide: Flow<Unit> = repository.startingToHide.filter { it }.map {}
val isVisible: Flow<Boolean> = repository.isVisible
val isBackButtonEnabled: Flow<Boolean> = repository.isBackButtonEnabled.filterNotNull()
val expansionAmount: Flow<Float> = repository.expansionAmount
val showMessage: Flow<BouncerShowMessageModel> = repository.showMessage.filterNotNull()
val startingDisappearAnimation: Flow<Runnable> =
repository.startingDisappearAnimation.filterNotNull()
val onDismissAction: Flow<BouncerCallbackActionsModel> =
repository.onDismissAction.filterNotNull()
val resourceUpdateRequests: Flow<Boolean> = repository.resourceUpdateRequests.filter { it }
val keyguardPosition: Flow<Float> = repository.keyguardPosition
// TODO(b/243685699): Move isScrimmed logic to data layer.
// TODO(b/243695312): Encapsulate all of the show logic for the bouncer.
/** Show the bouncer if necessary and set the relevant states. */
@JvmOverloads
fun show(isScrimmed: Boolean) {
// Reset some states as we show the bouncer.
repository.setShowMessage(null)
repository.setOnScreenTurnedOff(false)
repository.setKeyguardAuthenticated(null)
repository.setHide(false)
repository.setStartingToHide(false)
val resumeBouncer =
(repository.isVisible.value || repository.showingSoon.value) && needsFullscreenBouncer()
if (!resumeBouncer && repository.show.value != null) {
// If bouncer is visible, the bouncer is already showing.
return
}
val keyguardUserId = KeyguardUpdateMonitor.getCurrentUser()
if (keyguardUserId == UserHandle.USER_SYSTEM && UserManager.isSplitSystemUser()) {
// In split system user mode, we never unlock system user.
return
}
Trace.beginSection("KeyguardBouncer#show")
repository.setScrimmed(isScrimmed)
if (isScrimmed) {
setExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
}
if (resumeBouncer) {
bouncerView.delegate?.resume()
// Bouncer is showing the next security screen and we just need to prompt a resume.
return
}
if (bouncerView.delegate?.showNextSecurityScreenOrFinish() == true) {
// Keyguard is done.
return
}
repository.setShowingSoon(true)
if (bouncerFaceDelay) {
mainHandler.postDelayed(showRunnable, 1200L)
} else {
DejankUtils.postAfterTraversal(showRunnable)
}
keyguardStateController.notifyBouncerShowing(true)
callbackInteractor.dispatchStartingToShow()
Trace.endSection()
}
/** Sets the correct bouncer states to hide the bouncer. */
fun hide() {
Trace.beginSection("KeyguardBouncer#hide")
if (isFullyShowing()) {
SysUiStatsLog.write(
SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED,
SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN
)
dismissCallbackRegistry.notifyDismissCancelled()
}
falsingCollector.onBouncerHidden()
keyguardStateController.notifyBouncerShowing(false /* showing */)
cancelShowRunnable()
repository.setShowingSoon(false)
repository.setOnDismissAction(null)
repository.setVisible(false)
repository.setHide(true)
repository.setShow(null)
Trace.endSection()
}
/**
* Sets the panel expansion which is calculated further upstream. Expansion is from 0f to 1f
* where 0f => showing and 1f => hiding
*/
fun setExpansion(expansion: Float) {
val oldExpansion = repository.expansionAmount.value
val expansionChanged = oldExpansion != expansion
if (repository.startingDisappearAnimation.value == null) {
repository.setExpansion(expansion)
}
if (
expansion == KeyguardBouncer.EXPANSION_VISIBLE &&
oldExpansion != KeyguardBouncer.EXPANSION_VISIBLE
) {
falsingCollector.onBouncerShown()
callbackInteractor.dispatchFullyShown()
} else if (
expansion == KeyguardBouncer.EXPANSION_HIDDEN &&
oldExpansion != KeyguardBouncer.EXPANSION_HIDDEN
) {
repository.setVisible(false)
repository.setShow(null)
falsingCollector.onBouncerHidden()
DejankUtils.postAfterTraversal { callbackInteractor.dispatchReset() }
callbackInteractor.dispatchFullyHidden()
} else if (
expansion != KeyguardBouncer.EXPANSION_VISIBLE &&
oldExpansion == KeyguardBouncer.EXPANSION_VISIBLE
) {
callbackInteractor.dispatchStartingToHide()
repository.setStartingToHide(true)
}
if (expansionChanged) {
callbackInteractor.dispatchExpansionChanged(expansion)
}
}
/** Set the initial keyguard message to show when bouncer is shown. */
fun showMessage(message: String?, colorStateList: ColorStateList?) {
repository.setShowMessage(BouncerShowMessageModel(message, colorStateList))
}
/**
* Sets actions to the bouncer based on how the bouncer is dismissed. If the bouncer is
* unlocked, we will run the onDismissAction. If the bouncer is existed before unlocking, we
* call cancelAction.
*/
fun setDismissAction(
onDismissAction: ActivityStarter.OnDismissAction?,
cancelAction: Runnable?
) {
repository.setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction))
}
/** Update the resources of the views. */
fun updateResources() {
repository.setResourceUpdateRequests(true)
}
/** Tell the bouncer that keyguard is authenticated. */
fun notifyKeyguardAuthenticated(strongAuth: Boolean) {
repository.setKeyguardAuthenticated(strongAuth)
}
/** Tell the bouncer the screen has turned off. */
fun onScreenTurnedOff() {
repository.setOnScreenTurnedOff(true)
}
/** Update the position of the bouncer when showing. */
fun setKeyguardPosition(position: Float) {
repository.setKeyguardPosition(position)
}
/** Notifies that the state change was handled. */
fun notifyKeyguardAuthenticatedHandled() {
repository.setKeyguardAuthenticated(null)
}
/** Notify that view visibility has changed. */
fun notifyBouncerVisibilityHasChanged(visibility: Int) {
callbackInteractor.dispatchVisibilityChanged(visibility)
}
/** Notify that the resources have been updated */
fun notifyUpdatedResources() {
repository.setResourceUpdateRequests(false)
}
/** Set whether back button is enabled when on the bouncer screen. */
fun setBackButtonEnabled(enabled: Boolean) {
repository.setIsBackButtonEnabled(enabled)
}
/** Tell the bouncer to start the pre hide animation. */
fun startDisappearAnimation(runnable: Runnable) {
val finishRunnable = Runnable {
repository.setStartDisappearAnimation(null)
runnable.run()
}
repository.setStartDisappearAnimation(finishRunnable)
}
/** Returns whether bouncer is fully showing. */
fun isFullyShowing(): Boolean {
return (repository.showingSoon.value || repository.isVisible.value) &&
repository.expansionAmount.value == KeyguardBouncer.EXPANSION_VISIBLE &&
repository.startingDisappearAnimation.value == null
}
/** Returns whether bouncer is scrimmed. */
fun isScrimmed(): Boolean {
return repository.isScrimmed.value
}
/** If bouncer expansion is between 0f and 1f non-inclusive. */
fun isInTransit(): Boolean {
return repository.showingSoon.value ||
repository.expansionAmount.value != KeyguardBouncer.EXPANSION_HIDDEN &&
repository.expansionAmount.value != KeyguardBouncer.EXPANSION_VISIBLE
}
/** Return whether bouncer is animating away. */
fun isAnimatingAway(): Boolean {
return repository.startingDisappearAnimation.value != null
}
/** Return whether bouncer will dismiss with actions */
fun willDismissWithAction(): Boolean {
return repository.onDismissAction.value?.onDismissAction != null
}
/** Returns whether the bouncer should be full screen. */
private fun needsFullscreenBouncer(): Boolean {
val mode: KeyguardSecurityModel.SecurityMode =
keyguardSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser())
return mode == KeyguardSecurityModel.SecurityMode.SimPin ||
mode == KeyguardSecurityModel.SecurityMode.SimPuk
}
/** Remove the show runnable from the main handler queue to improve performance. */
private fun cancelShowRunnable() {
DejankUtils.removeCallbacks(showRunnable)
mainHandler.removeCallbacks(showRunnable)
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.systemui.keyguard.shared.model
import com.android.systemui.plugins.ActivityStarter
/** Encapsulates callbacks to be invoked by the bouncer logic. */
// TODO(b/243683121): Move dismiss logic from view controllers
data class BouncerCallbackActionsModel(
val onDismissAction: ActivityStarter.OnDismissAction?,
val cancelAction: Runnable?
)

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.systemui.keyguard.shared.model
import android.content.res.ColorStateList
/** Show a keyguard message to the bouncer. */
data class BouncerShowMessageModel(val message: String?, val colorStateList: ColorStateList?)

View File

@@ -0,0 +1,24 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.systemui.keyguard.shared.model
/** Models the state of the lock-screen bouncer */
data class KeyguardBouncerModel(
val promptReason: Int = 0,
val errorMessage: CharSequence? = null,
val expansionAmount: Float = 0f,
)

View File

@@ -0,0 +1,220 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.systemui.keyguard.ui.binder
import android.view.KeyEvent
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.internal.policy.SystemBarUtils
import com.android.keyguard.KeyguardHostViewController
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.keyguard.dagger.KeyguardBouncerComponent
import com.android.systemui.keyguard.data.BouncerViewDelegate
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch
/** Binds the bouncer container to its view model. */
object KeyguardBouncerViewBinder {
@JvmStatic
fun bind(
view: ViewGroup,
viewModel: KeyguardBouncerViewModel,
componentFactory: KeyguardBouncerComponent.Factory
) {
// Builds the KeyguardHostViewController from bouncer view group.
val hostViewController: KeyguardHostViewController =
componentFactory.create(view).keyguardHostViewController
hostViewController.init()
val delegate =
object : BouncerViewDelegate {
override fun isFullScreenBouncer(): Boolean {
val mode = hostViewController.currentSecurityMode
return mode == KeyguardSecurityModel.SecurityMode.SimPin ||
mode == KeyguardSecurityModel.SecurityMode.SimPuk
}
override fun shouldDismissOnMenuPressed(): Boolean {
return hostViewController.shouldEnableMenuKey()
}
override fun interceptMediaKey(event: KeyEvent?): Boolean {
return hostViewController.interceptMediaKey(event)
}
override fun dispatchBackKeyEventPreIme(): Boolean {
return hostViewController.dispatchBackKeyEventPreIme()
}
override fun showNextSecurityScreenOrFinish(): Boolean {
return hostViewController.dismiss(KeyguardUpdateMonitor.getCurrentUser())
}
override fun resume() {
hostViewController.showPrimarySecurityScreen()
hostViewController.onResume()
}
}
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
try {
viewModel.setBouncerViewDelegate(delegate)
launch {
viewModel.show.collect {
hostViewController.showPrimarySecurityScreen()
hostViewController.appear(
SystemBarUtils.getStatusBarHeight(view.context)
)
}
}
launch {
viewModel.showPromptReason.collect { prompt ->
hostViewController.showPromptReason(prompt)
}
}
launch {
viewModel.showBouncerErrorMessage.collect { errorMessage ->
hostViewController.showErrorMessage(errorMessage)
}
}
launch {
viewModel.showWithFullExpansion.collect { model ->
hostViewController.resetSecurityContainer()
hostViewController.showPromptReason(model.promptReason)
hostViewController.onResume()
}
}
launch {
viewModel.hide.collect {
hostViewController.cancelDismissAction()
hostViewController.cleanUp()
hostViewController.resetSecurityContainer()
}
}
launch {
viewModel.startingToHide.collect { hostViewController.onStartingToHide() }
}
launch {
viewModel.setDismissAction.collect {
hostViewController.setOnDismissAction(
it.onDismissAction,
it.cancelAction
)
}
}
launch {
viewModel.startDisappearAnimation.collect {
hostViewController.startDisappearAnimation(it)
}
}
launch {
viewModel.bouncerExpansionAmount.collect { expansion ->
hostViewController.setExpansion(expansion)
}
}
launch {
viewModel.bouncerExpansionAmount
.filter { it == EXPANSION_VISIBLE }
.collect {
hostViewController.onResume()
view.announceForAccessibility(
hostViewController.accessibilityTitleForCurrentMode
)
}
}
launch {
viewModel.isBouncerVisible.collect { isVisible ->
val visibility = if (isVisible) View.VISIBLE else View.INVISIBLE
view.visibility = visibility
hostViewController.onBouncerVisibilityChanged(visibility)
viewModel.notifyBouncerVisibilityHasChanged(visibility)
}
}
launch {
viewModel.isBouncerVisible
.filter { !it }
.collect {
// Remove existing input for security reasons.
hostViewController.resetSecurityContainer()
}
}
launch {
viewModel.keyguardPosition.collect { position ->
hostViewController.updateKeyguardPosition(position)
}
}
launch {
viewModel.updateResources.collect {
hostViewController.updateResources()
viewModel.notifyUpdateResources()
}
}
launch {
viewModel.bouncerShowMessage.collect {
hostViewController.showMessage(it.message, it.colorStateList)
}
}
launch {
viewModel.keyguardAuthenticated.collect {
hostViewController.finish(it, KeyguardUpdateMonitor.getCurrentUser())
viewModel.notifyKeyguardAuthenticated()
}
}
launch {
viewModel
.observeOnIsBackButtonEnabled { view.systemUiVisibility }
.collect { view.systemUiVisibility = it }
}
launch {
viewModel.screenTurnedOff.collect {
if (view.visibility == View.VISIBLE) {
hostViewController.onPause()
}
}
}
awaitCancellation()
} finally {
viewModel.setBouncerViewDelegate(null)
}
}
}
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.systemui.keyguard.ui.viewmodel
import android.view.View
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.BouncerViewDelegate
import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
/** Models UI state for the lock screen bouncer; handles user input. */
class KeyguardBouncerViewModel
@Inject
constructor(
private val view: BouncerView,
private val interactor: BouncerInteractor,
) {
/** Observe on bouncer expansion amount. */
val bouncerExpansionAmount: Flow<Float> = interactor.expansionAmount
/** Observe on bouncer visibility. */
val isBouncerVisible: Flow<Boolean> = interactor.isVisible
/** Observe whether bouncer is showing. */
val show: Flow<KeyguardBouncerModel> = interactor.show
/** Observe bouncer prompt when bouncer is showing. */
val showPromptReason: Flow<Int> = interactor.show.map { it.promptReason }
/** Observe bouncer error message when bouncer is showing. */
val showBouncerErrorMessage: Flow<CharSequence> =
interactor.show.map { it.errorMessage }.filterNotNull()
/** Observe visible expansion when bouncer is showing. */
val showWithFullExpansion: Flow<KeyguardBouncerModel> =
interactor.show.filter { it.expansionAmount == EXPANSION_VISIBLE }
/** Observe whether bouncer is hiding. */
val hide: Flow<Unit> = interactor.hide
/** Observe whether bouncer is starting to hide. */
val startingToHide: Flow<Unit> = interactor.startingToHide
/** Observe whether we want to set the dismiss action to the bouncer. */
val setDismissAction: Flow<BouncerCallbackActionsModel> = interactor.onDismissAction
/** Observe whether we want to start the disappear animation. */
val startDisappearAnimation: Flow<Runnable> = interactor.startingDisappearAnimation
/** Observe whether we want to update keyguard position. */
val keyguardPosition: Flow<Float> = interactor.keyguardPosition
/** Observe whether we want to update resources. */
val updateResources: Flow<Boolean> = interactor.resourceUpdateRequests
/** Observe whether we want to set a keyguard message when the bouncer shows. */
val bouncerShowMessage: Flow<BouncerShowMessageModel> = interactor.showMessage
/** Observe whether keyguard is authenticated already. */
val keyguardAuthenticated: Flow<Boolean> = interactor.keyguardAuthenticated
/** Observe whether screen is turned off. */
val screenTurnedOff: Flow<Unit> = interactor.screenTurnedOff
/** Notify that view visibility has changed. */
fun notifyBouncerVisibilityHasChanged(visibility: Int) {
return interactor.notifyBouncerVisibilityHasChanged(visibility)
}
/** Observe whether we want to update resources. */
fun notifyUpdateResources() {
interactor.notifyUpdatedResources()
}
/** Notify that keyguard authenticated was handled */
fun notifyKeyguardAuthenticated() {
interactor.notifyKeyguardAuthenticatedHandled()
}
/** Observe whether back button is enabled. */
fun observeOnIsBackButtonEnabled(systemUiVisibility: () -> Int): Flow<Int> {
return interactor.isBackButtonEnabled.map { enabled ->
var vis: Int = systemUiVisibility()
vis =
if (enabled) {
vis and View.STATUS_BAR_DISABLE_BACK.inv()
} else {
vis or View.STATUS_BAR_DISABLE_BACK
}
vis
}
}
/** Set an abstraction that will hold reference to the ui delegate for the bouncer view. */
fun setBouncerViewDelegate(delegate: BouncerViewDelegate?) {
view.delegate = delegate
}
}

View File

@@ -31,10 +31,15 @@ import android.view.ViewGroup;
import com.android.internal.annotations.VisibleForTesting;
import com.android.keyguard.AuthKeyguardMessageArea;
import com.android.keyguard.LockIconViewController;
import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.systemui.R;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dock.DockManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.ui.binder.KeyguardBouncerViewBinder;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -108,7 +113,10 @@ public class NotificationShadeWindowViewController {
NotificationShadeWindowController controller,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
AmbientState ambientState,
PulsingGestureListener pulsingGestureListener
PulsingGestureListener pulsingGestureListener,
FeatureFlags featureFlags,
KeyguardBouncerViewModel keyguardBouncerViewModel,
KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory
) {
mLockscreenShadeTransitionController = transitionController;
mFalsingCollector = falsingCollector;
@@ -130,6 +138,12 @@ public class NotificationShadeWindowViewController {
// This view is not part of the newly inflated expanded status bar.
mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container);
if (featureFlags.isEnabled(Flags.MODERN_BOUNCER)) {
KeyguardBouncerViewBinder.bind(
mView.findViewById(R.id.keyguard_bouncer_container),
keyguardBouncerViewModel,
keyguardBouncerComponentFactory);
}
}
/**

View File

@@ -56,7 +56,9 @@ import javax.inject.Inject;
/**
* A class which manages the bouncer on the lockscreen.
* @deprecated Use KeyguardBouncerRepository
*/
@Deprecated
public class KeyguardBouncer {
private static final String TAG = "KeyguardBouncer";

View File

@@ -46,6 +46,7 @@ import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.AuthKeyguardMessageArea;
import com.android.keyguard.KeyguardMessageAreaController;
import com.android.keyguard.KeyguardSecurityModel;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.KeyguardViewController;
@@ -53,6 +54,12 @@ import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.data.BouncerView;
import com.android.systemui.keyguard.data.BouncerViewDelegate;
import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -123,6 +130,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
@Nullable
private final FoldAodAnimationController mFoldAodAnimationController;
private KeyguardMessageAreaController<AuthKeyguardMessageArea> mKeyguardMessageAreaController;
private final BouncerCallbackInteractor mBouncerCallbackInteractor;
private final BouncerInteractor mBouncerInteractor;
private final BouncerViewDelegate mBouncerViewDelegate;
private final Lazy<com.android.systemui.shade.ShadeController> mShadeController;
private final BouncerExpansionCallback mExpansionCallback = new BouncerExpansionCallback() {
@@ -197,7 +207,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private View mNotificationContainer;
protected KeyguardBouncer mBouncer;
@Nullable protected KeyguardBouncer mBouncer;
protected boolean mShowing;
protected boolean mOccluded;
protected boolean mRemoteInputActive;
@@ -223,6 +233,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private int mLastBiometricMode;
private boolean mLastScreenOffAnimationPlaying;
private float mQsExpansion;
private boolean mIsModernBouncerEnabled;
private OnDismissAction mAfterKeyguardGoneAction;
private Runnable mKeyguardGoneCancelAction;
@@ -237,6 +248,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private final DockManager mDockManager;
private final KeyguardUpdateMonitor mKeyguardUpdateManager;
private final LatencyTracker mLatencyTracker;
private final KeyguardSecurityModel mKeyguardSecurityModel;
private KeyguardBypassController mBypassController;
@Nullable private AlternateAuthInterceptor mAlternateAuthInterceptor;
@@ -271,7 +283,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
KeyguardMessageAreaController.Factory keyguardMessageAreaFactory,
Optional<SysUIUnfoldComponent> sysUIUnfoldComponent,
Lazy<ShadeController> shadeController,
LatencyTracker latencyTracker) {
LatencyTracker latencyTracker,
KeyguardSecurityModel keyguardSecurityModel,
FeatureFlags featureFlags,
BouncerCallbackInteractor bouncerCallbackInteractor,
BouncerInteractor bouncerInteractor,
BouncerView bouncerView) {
mContext = context;
mViewMediatorCallback = callback;
mLockPatternUtils = lockPatternUtils;
@@ -288,8 +305,13 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mKeyguardMessageAreaFactory = keyguardMessageAreaFactory;
mShadeController = shadeController;
mLatencyTracker = latencyTracker;
mKeyguardSecurityModel = keyguardSecurityModel;
mBouncerCallbackInteractor = bouncerCallbackInteractor;
mBouncerInteractor = bouncerInteractor;
mBouncerViewDelegate = bouncerView.getDelegate();
mFoldAodAnimationController = sysUIUnfoldComponent
.map(SysUIUnfoldComponent::getFoldAodAnimationController).orElse(null);
mIsModernBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_BOUNCER);
}
@Override
@@ -303,7 +325,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mBiometricUnlockController = biometricUnlockController;
ViewGroup container = mCentralSurfaces.getBouncerContainer();
mBouncer = mKeyguardBouncerFactory.create(container, mExpansionCallback);
if (mIsModernBouncerEnabled) {
mBouncerCallbackInteractor.addBouncerExpansionCallback(mExpansionCallback);
} else {
mBouncer = mKeyguardBouncerFactory.create(container, mExpansionCallback);
}
mNotificationPanelViewController = notificationPanelViewController;
if (panelExpansionStateManager != null) {
panelExpansionStateManager.addExpansionListener(this);
@@ -377,29 +403,45 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
if (mDozing && !mPulsing) {
return;
} else if (mNotificationPanelViewController.isUnlockHintRunning()) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
if (mBouncer != null) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
}
mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
} else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
// Don't expand to the bouncer. Instead transition back to the lock screen (see
// CentralSurfaces#showBouncerOrLockScreenIfKeyguard)
return;
} else if (bouncerNeedsScrimming()) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
if (mBouncer != null) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
}
mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
} else if (mShowing && !hideBouncerOverDream) {
if (!isWakeAndUnlocking()
&& !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER)
&& !mCentralSurfaces.isInLaunchTransition()
&& !isUnlockCollapsing()) {
mBouncer.setExpansion(fraction);
if (mBouncer != null) {
mBouncer.setExpansion(fraction);
}
mBouncerInteractor.setExpansion(fraction);
}
if (fraction != KeyguardBouncer.EXPANSION_HIDDEN && tracking
&& !mKeyguardStateController.canDismissLockScreen()
&& !mBouncer.isShowing() && !mBouncer.isAnimatingAway()) {
mBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */);
&& !bouncerIsShowing()
&& !bouncerIsAnimatingAway()) {
if (mBouncer != null) {
mBouncer.show(false /* resetSecuritySelection */, false /* scrimmed */);
}
mBouncerInteractor.show(/* isScrimmed= */false);
}
} else if (!mShowing && mBouncer.inTransit()) {
} else if (!mShowing && isBouncerInTransit()) {
// Keyguard is not visible anymore, but expansion animation was still running.
// We need to hide the bouncer, otherwise it will be stuck in transit.
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
if (mBouncer != null) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
}
mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
} else if (mPulsing && fraction == KeyguardBouncer.EXPANSION_VISIBLE) {
// Panel expanded while pulsing but didn't translate the bouncer (because we are
// unlocked.) Let's simply wake-up to dismiss the lock screen.
@@ -440,15 +482,20 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
* {@link KeyguardBouncer#needsFullscreenBouncer()}.
*/
protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) {
if (mBouncer.needsFullscreenBouncer() && !mDozing) {
if (needsFullscreenBouncer() && !mDozing) {
// The keyguard might be showing (already). So we need to hide it.
mCentralSurfaces.hideKeyguard();
mBouncer.show(true /* resetSecuritySelection */);
if (mBouncer != null) {
mBouncer.show(true /* resetSecuritySelection */);
}
mBouncerInteractor.show(true);
} else {
mCentralSurfaces.showKeyguard();
if (hideBouncerWhenShowing) {
hideBouncer(false /* destroyView */);
mBouncer.prepare();
if (mBouncer != null) {
mBouncer.prepare();
}
}
}
updateStates();
@@ -480,10 +527,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
*/
@VisibleForTesting
void hideBouncer(boolean destroyView) {
if (mBouncer == null) {
return;
if (mBouncer != null) {
mBouncer.hide(destroyView);
}
mBouncer.hide(destroyView);
mBouncerInteractor.hide();
if (mShowing) {
// If we were showing the bouncer and then aborting, we need to also clear out any
// potential actions unless we actually unlocked.
@@ -501,8 +548,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
public void showBouncer(boolean scrimmed) {
resetAlternateAuth(false);
if (mShowing && !mBouncer.isShowing()) {
mBouncer.show(false /* resetSecuritySelection */, scrimmed);
if (mShowing && !isBouncerShowing()) {
if (mBouncer != null) {
mBouncer.show(false /* resetSecuritySelection */, scrimmed);
}
mBouncerInteractor.show(scrimmed);
}
updateStates();
}
@@ -535,7 +585,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
// instead of the bouncer.
if (shouldShowAltAuth()) {
if (!afterKeyguardGone) {
mBouncer.setDismissAction(mAfterKeyguardGoneAction,
if (mBouncer != null) {
mBouncer.setDismissAction(mAfterKeyguardGoneAction,
mKeyguardGoneCancelAction);
}
mBouncerInteractor.setDismissAction(mAfterKeyguardGoneAction,
mKeyguardGoneCancelAction);
mAfterKeyguardGoneAction = null;
mKeyguardGoneCancelAction = null;
@@ -549,12 +603,18 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
if (afterKeyguardGone) {
// we'll handle the dismiss action after keyguard is gone, so just show the
// bouncer
mBouncer.show(false /* resetSecuritySelection */);
mBouncerInteractor.show(/* isScrimmed= */true);
if (mBouncer != null) mBouncer.show(false /* resetSecuritySelection */);
} else {
// after authentication success, run dismiss action with the option to defer
// hiding the keyguard based on the return value of the OnDismissAction
mBouncer.showWithDismissAction(mAfterKeyguardGoneAction,
mKeyguardGoneCancelAction);
mBouncerInteractor.setDismissAction(
mAfterKeyguardGoneAction, mKeyguardGoneCancelAction);
mBouncerInteractor.show(/* isScrimmed= */true);
if (mBouncer != null) {
mBouncer.showWithDismissAction(mAfterKeyguardGoneAction,
mKeyguardGoneCancelAction);
}
// bouncer will handle the dismiss action, so we no longer need to track it here
mAfterKeyguardGoneAction = null;
mKeyguardGoneCancelAction = null;
@@ -591,7 +651,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
// Hide bouncer and quick-quick settings.
if (mOccluded && !mDozing) {
mCentralSurfaces.hideKeyguard();
if (hideBouncerWhenShowing || mBouncer.needsFullscreenBouncer()) {
if (hideBouncerWhenShowing || needsFullscreenBouncer()) {
hideBouncer(false /* destroyView */);
}
} else {
@@ -655,7 +715,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
@Override
public void onFinishedGoingToSleep() {
mBouncer.onScreenTurnedOff();
if (mBouncer != null) {
mBouncer.onScreenTurnedOff();
}
mBouncerInteractor.onScreenTurnedOff();
}
@Override
@@ -746,7 +809,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
// by a FLAG_DISMISS_KEYGUARD_ACTIVITY.
reset(isOccluding /* hideBouncerWhenShowing*/);
}
if (animate && !mOccluded && mShowing && !mBouncer.isShowing()) {
if (animate && !mOccluded && mShowing && !bouncerIsShowing()) {
mCentralSurfaces.animateKeyguardUnoccluding();
}
}
@@ -762,8 +825,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
@Override
public void startPreHideAnimation(Runnable finishRunnable) {
if (mBouncer.isShowing()) {
mBouncer.startPreHideAnimation(finishRunnable);
if (bouncerIsShowing()) {
if (mBouncer != null) {
mBouncer.startPreHideAnimation(finishRunnable);
}
mBouncerInteractor.startDisappearAnimation(finishRunnable);
mCentralSurfaces.onBouncerPreHideAnimation();
// We update the state (which will show the keyguard) only if an animation will run on
@@ -873,8 +939,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
public void onThemeChanged() {
boolean wasShowing = mBouncer.isShowing();
boolean wasScrimmed = mBouncer.isScrimmed();
if (mIsModernBouncerEnabled) {
updateResources();
return;
}
boolean wasShowing = bouncerIsShowing();
boolean wasScrimmed = bouncerIsScrimmed();
hideBouncer(true /* destroyView */);
mBouncer.prepare();
@@ -924,7 +994,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
* WARNING: This method might cause Binder calls.
*/
public boolean isSecure() {
return mBouncer.isSecure();
if (mBouncer != null) {
return mBouncer.isSecure();
}
return mKeyguardSecurityModel.getSecurityMode(
KeyguardUpdateMonitor.getCurrentUser()) != KeyguardSecurityModel.SecurityMode.None;
}
@Override
@@ -941,10 +1016,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
* @return whether the back press has been handled
*/
public boolean onBackPressed(boolean hideImmediately) {
if (mBouncer.isShowing()) {
if (bouncerIsShowing()) {
mCentralSurfaces.endAffordanceLaunch();
// The second condition is for SIM card locked bouncer
if (mBouncer.isScrimmed() && !mBouncer.needsFullscreenBouncer()) {
if (bouncerIsScrimmed()
&& !needsFullscreenBouncer()) {
hideBouncer(false);
updateStates();
} else {
@@ -957,16 +1033,19 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
@Override
public boolean isBouncerShowing() {
return mBouncer.isShowing() || isShowingAlternateAuth();
return bouncerIsShowing() || isShowingAlternateAuth();
}
@Override
public boolean bouncerIsOrWillBeShowing() {
return isBouncerShowing() || mBouncer.inTransit();
return isBouncerShowing() || isBouncerInTransit();
}
public boolean isFullscreenBouncer() {
return mBouncer.isFullscreenBouncer();
if (mBouncerViewDelegate != null) {
return mBouncerViewDelegate.isFullScreenBouncer();
}
return mBouncer != null && mBouncer.isFullscreenBouncer();
}
/**
@@ -987,7 +1066,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
private long getNavBarShowDelay() {
if (mKeyguardStateController.isKeyguardFadingAway()) {
return mKeyguardStateController.getKeyguardFadingAwayDelay();
} else if (mBouncer.isShowing()) {
} else if (isBouncerShowing()) {
return NAV_BAR_SHOW_DELAY_BOUNCER;
} else {
// No longer dozing, or remote input is active. No delay.
@@ -1010,18 +1089,24 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
protected void updateStates() {
boolean showing = mShowing;
boolean occluded = mOccluded;
boolean bouncerShowing = mBouncer.isShowing();
boolean bouncerShowing = bouncerIsShowing();
boolean bouncerIsOrWillBeShowing = bouncerIsOrWillBeShowing();
boolean bouncerDismissible = !mBouncer.isFullscreenBouncer();
boolean bouncerDismissible = !isFullscreenBouncer();
boolean remoteInputActive = mRemoteInputActive;
if ((bouncerDismissible || !showing || remoteInputActive) !=
(mLastBouncerDismissible || !mLastShowing || mLastRemoteInputActive)
|| mFirstUpdate) {
if (bouncerDismissible || !showing || remoteInputActive) {
mBouncer.setBackButtonEnabled(true);
if (mBouncer != null) {
mBouncer.setBackButtonEnabled(true);
}
mBouncerInteractor.setBackButtonEnabled(true);
} else {
mBouncer.setBackButtonEnabled(false);
if (mBouncer != null) {
mBouncer.setBackButtonEnabled(false);
}
mBouncerInteractor.setBackButtonEnabled(false);
}
}
@@ -1098,7 +1183,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
|| mPulsing && !mIsDocked)
&& mGesturalNav;
return (!keyguardShowing && !hideWhileDozing && !mScreenOffAnimationPlaying
|| mBouncer.isShowing() || mRemoteInputActive || keyguardWithGestureNav
|| bouncerIsShowing()
|| mRemoteInputActive
|| keyguardWithGestureNav
|| mGlobalActionsVisible);
}
@@ -1117,18 +1204,27 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
public boolean shouldDismissOnMenuPressed() {
return mBouncer.shouldDismissOnMenuPressed();
if (mBouncerViewDelegate != null) {
return mBouncerViewDelegate.shouldDismissOnMenuPressed();
}
return mBouncer != null && mBouncer.shouldDismissOnMenuPressed();
}
public boolean interceptMediaKey(KeyEvent event) {
return mBouncer.interceptMediaKey(event);
if (mBouncerViewDelegate != null) {
return mBouncerViewDelegate.interceptMediaKey(event);
}
return mBouncer != null && mBouncer.interceptMediaKey(event);
}
/**
* @return true if the pre IME back event should be handled
*/
public boolean dispatchBackKeyEventPreIme() {
return mBouncer.dispatchBackKeyEventPreIme();
if (mBouncerViewDelegate != null) {
return mBouncerViewDelegate.dispatchBackKeyEventPreIme();
}
return mBouncer != null && mBouncer.dispatchBackKeyEventPreIme();
}
public void readyForKeyguardDone() {
@@ -1151,7 +1247,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
public boolean isSecure(int userId) {
return mBouncer.isSecure() || mLockPatternUtils.isSecure(userId);
return isSecure() || mLockPatternUtils.isSecure(userId);
}
@Override
@@ -1174,7 +1270,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
* fingerprint.
*/
public void notifyKeyguardAuthenticated(boolean strongAuth) {
mBouncer.notifyKeyguardAuthenticated(strongAuth);
if (mBouncer != null) {
mBouncer.notifyKeyguardAuthenticated(strongAuth);
}
mBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
if (mAlternateAuthInterceptor != null && isShowingAlternateAuthOrAnimating()) {
resetAlternateAuth(false);
@@ -1189,7 +1288,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mKeyguardMessageAreaController.setMessage(message);
}
} else {
mBouncer.showMessage(message, colorState);
if (mBouncer != null) {
mBouncer.showMessage(message, colorState);
}
mBouncerInteractor.showMessage(message, colorState);
}
}
@@ -1222,9 +1324,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
public boolean bouncerNeedsScrimming() {
// When a dream overlay is active, scrimming will cause any expansion to immediately expand.
return (mOccluded && !mDreamOverlayStateController.isOverlayActive())
|| mBouncer.willDismissWithAction()
|| (mBouncer.isShowing() && mBouncer.isScrimmed())
|| mBouncer.isFullscreenBouncer();
|| bouncerWillDismissWithAction()
|| (bouncerIsShowing()
&& bouncerIsScrimmed())
|| isFullscreenBouncer();
}
/**
@@ -1236,6 +1339,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
if (mBouncer != null) {
mBouncer.updateResources();
}
mBouncerInteractor.updateResources();
}
public void dump(PrintWriter pw) {
@@ -1289,6 +1393,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
}
@Nullable
public KeyguardBouncer getBouncer() {
return mBouncer;
}
@@ -1320,6 +1425,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
if (mBouncer != null) {
mBouncer.updateKeyguardPosition(x);
}
mBouncerInteractor.setKeyguardPosition(x);
}
private static class DismissWithActionRequest {
@@ -1359,9 +1466,65 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
* Returns if bouncer expansion is between 0 and 1 non-inclusive.
*/
public boolean isBouncerInTransit() {
if (mBouncer == null) return false;
if (mBouncer != null) {
return mBouncer.inTransit();
}
return mBouncer.inTransit();
return mBouncerInteractor.isInTransit();
}
/**
* Returns if bouncer is showing
*/
public boolean bouncerIsShowing() {
if (mBouncer != null) {
return mBouncer.isShowing();
}
return mBouncerInteractor.isFullyShowing();
}
/**
* Returns if bouncer is scrimmed
*/
public boolean bouncerIsScrimmed() {
if (mBouncer != null) {
return mBouncer.isScrimmed();
}
return mBouncerInteractor.isScrimmed();
}
/**
* Returns if bouncer is animating away
*/
public boolean bouncerIsAnimatingAway() {
if (mBouncer != null) {
return mBouncer.isAnimatingAway();
}
return mBouncerInteractor.isAnimatingAway();
}
/**
* Returns if bouncer will dismiss with action
*/
public boolean bouncerWillDismissWithAction() {
if (mBouncer != null) {
return mBouncer.willDismissWithAction();
}
return mBouncerInteractor.willDismissWithAction();
}
/**
* Returns if bouncer needs fullscreen bouncer. i.e. sim pin security method
*/
public boolean needsFullscreenBouncer() {
KeyguardSecurityModel.SecurityMode mode = mKeyguardSecurityModel.getSecurityMode(
KeyguardUpdateMonitor.getCurrentUser());
return mode == KeyguardSecurityModel.SecurityMode.SimPin
|| mode == KeyguardSecurityModel.SecurityMode.SimPuk;
}
/**

View File

@@ -36,6 +36,7 @@ import androidx.test.filters.SmallTest;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dreams.complication.ComplicationHostViewController;
import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
import com.android.systemui.statusbar.BlurUtils;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback;
@@ -88,6 +89,9 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
@Mock
ViewRootImpl mViewRoot;
@Mock
BouncerCallbackInteractor mBouncerCallbackInteractor;
DreamOverlayContainerViewController mController;
@Before
@@ -110,7 +114,8 @@ public class DreamOverlayContainerViewControllerTest extends SysuiTestCase {
mResources,
MAX_BURN_IN_OFFSET,
BURN_IN_PROTECTION_UPDATE_INTERVAL,
MILLIS_UNTIL_FULL_JITTER);
MILLIS_UNTIL_FULL_JITTER,
mBouncerCallbackInteractor);
}
@Test

View File

@@ -0,0 +1,86 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.keyguard.domain.interactor
import android.view.View
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.phone.KeyguardBouncer
import org.junit.Before
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.MockitoAnnotations
@SmallTest
@RunWith(JUnit4::class)
class BouncerCallbackInteractorTest : SysuiTestCase() {
private val bouncerCallbackInteractor = BouncerCallbackInteractor()
@Mock private lateinit var bouncerExpansionCallback: KeyguardBouncer.BouncerExpansionCallback
@Mock private lateinit var keyguardResetCallback: KeyguardBouncer.KeyguardResetCallback
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
bouncerCallbackInteractor.addBouncerExpansionCallback(bouncerExpansionCallback)
bouncerCallbackInteractor.addKeyguardResetCallback(keyguardResetCallback)
}
@Test
fun testOnFullyShown() {
bouncerCallbackInteractor.dispatchFullyShown()
verify(bouncerExpansionCallback).onFullyShown()
}
@Test
fun testOnFullyHidden() {
bouncerCallbackInteractor.dispatchFullyHidden()
verify(bouncerExpansionCallback).onFullyHidden()
}
@Test
fun testOnExpansionChanged() {
bouncerCallbackInteractor.dispatchExpansionChanged(5f)
verify(bouncerExpansionCallback).onExpansionChanged(5f)
}
@Test
fun testOnVisibilityChanged() {
bouncerCallbackInteractor.dispatchVisibilityChanged(View.INVISIBLE)
verify(bouncerExpansionCallback).onVisibilityChanged(false)
}
@Test
fun testOnStartingToHide() {
bouncerCallbackInteractor.dispatchStartingToHide()
verify(bouncerExpansionCallback).onStartingToHide()
}
@Test
fun testOnStartingToShow() {
bouncerCallbackInteractor.dispatchStartingToShow()
verify(bouncerExpansionCallback).onStartingToShow()
}
@Test
fun testOnKeyguardReset() {
bouncerCallbackInteractor.dispatchReset()
verify(keyguardResetCallback).onKeyguardReset()
}
}

View File

@@ -0,0 +1,278 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package com.android.systemui.keyguard.domain.interactor
import android.os.Looper
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.DejankUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_HIDDEN
import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Answers
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@RunWithLooper(setAsMainLooper = true)
@RunWith(AndroidTestingRunner::class)
class BouncerInteractorTest : SysuiTestCase() {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private lateinit var repository: KeyguardBouncerRepository
@Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var bouncerView: BouncerView
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
@Mock private lateinit var bouncerCallbackInteractor: BouncerCallbackInteractor
@Mock private lateinit var falsingCollector: FalsingCollector
@Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
@Mock private lateinit var keyguardBypassController: KeyguardBypassController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
private val mainHandler = FakeHandler(Looper.getMainLooper())
private lateinit var bouncerInteractor: BouncerInteractor
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
DejankUtils.setImmediate(true)
bouncerInteractor =
BouncerInteractor(
repository,
bouncerView,
mainHandler,
keyguardStateController,
keyguardSecurityModel,
bouncerCallbackInteractor,
falsingCollector,
dismissCallbackRegistry,
keyguardBypassController,
keyguardUpdateMonitor,
)
`when`(repository.startingDisappearAnimation.value).thenReturn(null)
`when`(repository.show.value).thenReturn(null)
}
@Test
fun testShow_isScrimmed() {
bouncerInteractor.show(true)
verify(repository).setShowMessage(null)
verify(repository).setOnScreenTurnedOff(false)
verify(repository).setKeyguardAuthenticated(null)
verify(repository).setHide(false)
verify(repository).setStartingToHide(false)
verify(repository).setScrimmed(true)
verify(repository).setExpansion(EXPANSION_VISIBLE)
verify(repository).setShowingSoon(true)
verify(keyguardStateController).notifyBouncerShowing(true)
verify(bouncerCallbackInteractor).dispatchStartingToShow()
verify(repository).setVisible(true)
verify(repository).setShow(any(KeyguardBouncerModel::class.java))
verify(repository).setShowingSoon(false)
}
@Test
fun testShow_isNotScrimmed() {
verify(repository, never()).setExpansion(EXPANSION_VISIBLE)
}
@Test
fun testShow_keyguardIsDone() {
`when`(bouncerView.delegate?.showNextSecurityScreenOrFinish()).thenReturn(true)
verify(keyguardStateController, never()).notifyBouncerShowing(true)
verify(bouncerCallbackInteractor, never()).dispatchStartingToShow()
}
@Test
fun testHide() {
bouncerInteractor.hide()
verify(falsingCollector).onBouncerHidden()
verify(keyguardStateController).notifyBouncerShowing(false)
verify(repository).setShowingSoon(false)
verify(repository).setOnDismissAction(null)
verify(repository).setVisible(false)
verify(repository).setHide(true)
verify(repository).setShow(null)
}
@Test
fun testExpansion() {
`when`(repository.expansionAmount.value).thenReturn(0.5f)
bouncerInteractor.setExpansion(0.6f)
verify(repository).setExpansion(0.6f)
verify(bouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
}
@Test
fun testExpansion_fullyShown() {
`when`(repository.expansionAmount.value).thenReturn(0.5f)
`when`(repository.startingDisappearAnimation.value).thenReturn(null)
bouncerInteractor.setExpansion(EXPANSION_VISIBLE)
verify(falsingCollector).onBouncerShown()
verify(bouncerCallbackInteractor).dispatchFullyShown()
}
@Test
fun testExpansion_fullyHidden() {
`when`(repository.expansionAmount.value).thenReturn(0.5f)
`when`(repository.startingDisappearAnimation.value).thenReturn(null)
bouncerInteractor.setExpansion(EXPANSION_HIDDEN)
verify(repository).setVisible(false)
verify(repository).setShow(null)
verify(falsingCollector).onBouncerHidden()
verify(bouncerCallbackInteractor).dispatchReset()
verify(bouncerCallbackInteractor).dispatchFullyHidden()
}
@Test
fun testExpansion_startingToHide() {
`when`(repository.expansionAmount.value).thenReturn(EXPANSION_VISIBLE)
bouncerInteractor.setExpansion(0.1f)
verify(repository).setStartingToHide(true)
verify(bouncerCallbackInteractor).dispatchStartingToHide()
}
@Test
fun testShowMessage() {
bouncerInteractor.showMessage("abc", null)
verify(repository).setShowMessage(BouncerShowMessageModel("abc", null))
}
@Test
fun testDismissAction() {
val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
val cancelAction = mock(Runnable::class.java)
bouncerInteractor.setDismissAction(onDismissAction, cancelAction)
verify(repository)
.setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction))
}
@Test
fun testUpdateResources() {
bouncerInteractor.updateResources()
verify(repository).setResourceUpdateRequests(true)
}
@Test
fun testNotifyKeyguardAuthenticated() {
bouncerInteractor.notifyKeyguardAuthenticated(true)
verify(repository).setKeyguardAuthenticated(true)
}
@Test
fun testOnScreenTurnedOff() {
bouncerInteractor.onScreenTurnedOff()
verify(repository).setOnScreenTurnedOff(true)
}
@Test
fun testSetKeyguardPosition() {
bouncerInteractor.setKeyguardPosition(0f)
verify(repository).setKeyguardPosition(0f)
}
@Test
fun testNotifyKeyguardAuthenticatedHandled() {
bouncerInteractor.notifyKeyguardAuthenticatedHandled()
verify(repository).setKeyguardAuthenticated(null)
}
@Test
fun testNotifyUpdatedResources() {
bouncerInteractor.notifyUpdatedResources()
verify(repository).setResourceUpdateRequests(false)
}
@Test
fun testSetBackButtonEnabled() {
bouncerInteractor.setBackButtonEnabled(true)
verify(repository).setIsBackButtonEnabled(true)
}
@Test
fun testStartDisappearAnimation() {
val runnable = mock(Runnable::class.java)
bouncerInteractor.startDisappearAnimation(runnable)
verify(repository).setStartDisappearAnimation(any(Runnable::class.java))
}
@Test
fun testIsFullShowing() {
`when`(repository.isVisible.value).thenReturn(true)
`when`(repository.expansionAmount.value).thenReturn(EXPANSION_VISIBLE)
`when`(repository.startingDisappearAnimation.value).thenReturn(null)
assertThat(bouncerInteractor.isFullyShowing()).isTrue()
`when`(repository.isVisible.value).thenReturn(false)
assertThat(bouncerInteractor.isFullyShowing()).isFalse()
}
@Test
fun testIsScrimmed() {
`when`(repository.isScrimmed.value).thenReturn(true)
assertThat(bouncerInteractor.isScrimmed()).isTrue()
`when`(repository.isScrimmed.value).thenReturn(false)
assertThat(bouncerInteractor.isScrimmed()).isFalse()
}
@Test
fun testIsInTransit() {
`when`(repository.showingSoon.value).thenReturn(true)
assertThat(bouncerInteractor.isInTransit()).isTrue()
`when`(repository.showingSoon.value).thenReturn(false)
assertThat(bouncerInteractor.isInTransit()).isFalse()
`when`(repository.expansionAmount.value).thenReturn(0.5f)
assertThat(bouncerInteractor.isInTransit()).isTrue()
}
@Test
fun testIsAnimatingAway() {
`when`(repository.startingDisappearAnimation.value).thenReturn(Runnable {})
assertThat(bouncerInteractor.isAnimatingAway()).isTrue()
`when`(repository.startingDisappearAnimation.value).thenReturn(null)
assertThat(bouncerInteractor.isAnimatingAway()).isFalse()
}
@Test
fun testWillDismissWithAction() {
`when`(repository.onDismissAction.value?.onDismissAction)
.thenReturn(mock(ActivityStarter.OnDismissAction::class.java))
assertThat(bouncerInteractor.willDismissWithAction()).isTrue()
`when`(repository.onDismissAction.value?.onDismissAction).thenReturn(null)
assertThat(bouncerInteractor.willDismissWithAction()).isFalse()
}
}

View File

@@ -21,12 +21,16 @@ import android.testing.TestableLooper.RunWithLooper
import android.view.MotionEvent
import android.view.ViewGroup
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardHostViewController
import com.android.keyguard.LockIconViewController
import com.android.keyguard.dagger.KeyguardBouncerComponent
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.dock.DockManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.NotificationShadeDepthController
@@ -51,9 +55,9 @@ import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
@Mock
private lateinit var view: NotificationShadeWindowView
@@ -72,8 +76,12 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
@Mock
private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
@Mock
private lateinit var featureFlags: FeatureFlags
@Mock
private lateinit var ambientState: AmbientState
@Mock
private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
@Mock
private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
@Mock
private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
@@ -87,6 +95,10 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController
@Mock
private lateinit var pulsingGestureListener: PulsingGestureListener
@Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
@Mock lateinit var keyguardBouncerContainer: ViewGroup
@Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
@Mock lateinit var keyguardHostViewController: KeyguardHostViewController
private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
private lateinit var interactionEventHandler: InteractionEventHandler
@@ -97,7 +109,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(view.bottom).thenReturn(VIEW_BOTTOM)
underTest = NotificationShadeWindowViewController(
lockscreenShadeTransitionController,
FalsingCollectorFake(),
@@ -115,7 +126,10 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
notificationShadeWindowController,
keyguardUnlockAnimationController,
ambientState,
pulsingGestureListener
pulsingGestureListener,
featureFlags,
keyguardBouncerViewModel,
keyguardBouncerComponentFactory
)
underTest.setupExpandedStatusBar()

View File

@@ -33,11 +33,14 @@ import android.view.MotionEvent;
import androidx.test.filters.SmallTest;
import com.android.keyguard.LockIconViewController;
import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.dock.DockManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
import com.android.systemui.statusbar.DragDownHelper;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -86,6 +89,9 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase {
@Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@Mock private AmbientState mAmbientState;
@Mock private PulsingGestureListener mPulsingGestureListener;
@Mock private FeatureFlags mFeatureFlags;
@Mock private KeyguardBouncerViewModel mKeyguardBouncerViewModel;
@Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
@Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler>
mInteractionEventHandlerCaptor;
@@ -121,7 +127,10 @@ public class NotificationShadeWindowViewTest extends SysuiTestCase {
mNotificationShadeWindowController,
mKeyguardUnlockAnimationController,
mAmbientState,
mPulsingGestureListener
mPulsingGestureListener,
mFeatureFlags,
mKeyguardBouncerViewModel,
mKeyguardBouncerComponentFactory
);
mController.setupExpandedStatusBar();
mController.setDragDownHelper(mDragDownHelper);

View File

@@ -41,11 +41,17 @@ import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardMessageArea;
import com.android.keyguard.KeyguardMessageAreaController;
import com.android.keyguard.KeyguardSecurityModel;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.data.BouncerView;
import com.android.systemui.keyguard.data.BouncerViewDelegate;
import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor;
import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.shade.NotificationPanelViewController;
@@ -101,6 +107,13 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
@Mock private SysUIUnfoldComponent mSysUiUnfoldComponent;
@Mock private DreamOverlayStateController mDreamOverlayStateController;
@Mock private LatencyTracker mLatencyTracker;
@Mock private FeatureFlags mFeatureFlags;
@Mock private KeyguardSecurityModel mKeyguardSecurityModel;
@Mock private BouncerCallbackInteractor mBouncerCallbackInteractor;
@Mock private BouncerInteractor mBouncerInteractor;
@Mock private BouncerView mBouncerView;
// @Mock private WeakReference<BouncerViewDelegate> mBouncerViewDelegateWeakReference;
@Mock private BouncerViewDelegate mBouncerViewDelegate;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback;
@@ -115,6 +128,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea);
when(mKeyguardMessageAreaFactory.create(any(KeyguardMessageArea.class)))
.thenReturn(mKeyguardMessageAreaController);
when(mBouncerView.getDelegate()).thenReturn(mBouncerViewDelegate);
mStatusBarKeyguardViewManager =
new StatusBarKeyguardViewManager(
getContext(),
@@ -133,7 +148,12 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mKeyguardMessageAreaFactory,
Optional.of(mSysUiUnfoldComponent),
() -> mShadeController,
mLatencyTracker);
mLatencyTracker,
mKeyguardSecurityModel,
mFeatureFlags,
mBouncerCallbackInteractor,
mBouncerInteractor,
mBouncerView);
mStatusBarKeyguardViewManager.registerCentralSurfaces(
mCentralSurfaces,
mNotificationPanelView,