Merge "Unifies use-cases into interactors." into tm-qpr-dev

This commit is contained in:
Alejandro Nijamkin
2022-08-13 15:53:52 +00:00
committed by Android (Google) Code Review
36 changed files with 614 additions and 1007 deletions

View File

@@ -35,7 +35,7 @@ object ChannelExt {
* " - downstream canceled or failed.", * " - downstream canceled or failed.",
* it, * it,
* ) * )
*} * }
* ``` * ```
*/ */
fun <T> SendChannel<T>.trySendWithFailureLogging( fun <T> SendChannel<T>.trySendWithFailureLogging(

View File

@@ -1,34 +0,0 @@
/*
* 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.common.domain.model
import com.android.systemui.common.data.model.Position as DataLayerPosition
/** Models a two-dimensional position */
data class Position(
val x: Int,
val y: Int,
) {
companion object {
fun DataLayerPosition.toDomainLayer(): Position {
return Position(
x = x,
y = y,
)
}
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package com.android.systemui.common.data.model package com.android.systemui.common.shared.model
/** Models a two-dimensional position */ /** Models a two-dimensional position */
data class Position( data class Position(

View File

@@ -44,7 +44,6 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule; import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule; import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
import com.android.systemui.keyguard.domain.usecase.KeyguardUseCaseModule;
import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.statusbar.NotificationShadeDepthController; import com.android.systemui.statusbar.NotificationShadeDepthController;
import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -73,7 +72,6 @@ import dagger.Provides;
FalsingModule.class, FalsingModule.class,
KeyguardQuickAffordanceModule.class, KeyguardQuickAffordanceModule.class,
KeyguardRepositoryModule.class, KeyguardRepositoryModule.class,
KeyguardUseCaseModule.class,
}) })
public class KeyguardModule { public class KeyguardModule {
/** /**

View File

@@ -18,7 +18,7 @@ package com.android.systemui.keyguard.data.repository
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.common.data.model.Position import com.android.systemui.common.shared.model.Position
import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.KeyguardStateController

View File

@@ -0,0 +1,51 @@
/*
* 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 com.android.systemui.common.shared.model.Position
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
/** Encapsulates business-logic specifically related to the keyguard bottom area. */
@SysUISingleton
class KeyguardBottomAreaInteractor
@Inject
constructor(
private val repository: KeyguardRepository,
) {
/** Whether to animate the next doze mode transition. */
val animateDozingTransitions: Flow<Boolean> = repository.animateBottomAreaDozingTransitions
/** The amount of alpha for the UI components of the bottom area. */
val alpha: Flow<Float> = repository.bottomAreaAlpha
/** The position of the keyguard clock. */
val clockPosition: Flow<Position> = repository.clockPosition
fun setClockPosition(x: Int, y: Int) {
repository.setClockPosition(x, y)
}
fun setAlpha(alpha: Float) {
repository.setBottomAreaAlpha(alpha)
}
fun setAnimateDozingTransitions(animate: Boolean) {
repository.setAnimateDozingTransitions(animate)
}
}

View File

@@ -15,25 +15,29 @@
* *
*/ */
package com.android.systemui.keyguard.domain.usecase package com.android.systemui.keyguard.domain.interactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.data.repository.KeyguardRepository
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
/** /**
* Use-case for observing whether the keyguard is currently being shown. * Encapsulates business-logic related to the keyguard but not to a more specific part within it.
*
* Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in the
* z-order (which is not really above the system UI window, but rather - the lock-screen becomes
* invisible to reveal the "occluding activity").
*/ */
class ObserveIsKeyguardShowingUseCase @SysUISingleton
class KeyguardInteractor
@Inject @Inject
constructor( constructor(
private val repository: KeyguardRepository, repository: KeyguardRepository,
) { ) {
operator fun invoke(): Flow<Boolean> { /**
return repository.isKeyguardShowing * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
} * all.
*/
val dozeAmount: Flow<Float> = repository.dozeAmount
/** Whether the system is in doze mode. */
val isDozing: Flow<Boolean> = repository.isDozing
/** Whether the keyguard is showing ot not. */
val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
} }

View File

@@ -0,0 +1,136 @@
/*
* 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.Intent
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
import kotlin.reflect.KClass
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@SysUISingleton
class KeyguardQuickAffordanceInteractor
@Inject
constructor(
private val keyguardInteractor: KeyguardInteractor,
private val registry: KeyguardQuickAffordanceRegistry<out KeyguardQuickAffordanceConfig>,
private val lockPatternUtils: LockPatternUtils,
private val keyguardStateController: KeyguardStateController,
private val userTracker: UserTracker,
private val activityStarter: ActivityStarter,
) {
/** Returns an observable for the quick affordance at the given position. */
fun quickAffordance(
position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceModel> {
return combine(
quickAffordanceInternal(position),
keyguardInteractor.isDozing,
keyguardInteractor.isKeyguardShowing,
) { affordance, isDozing, isKeyguardShowing ->
if (!isDozing && isKeyguardShowing) {
affordance
} else {
KeyguardQuickAffordanceModel.Hidden
}
}
}
/**
* Notifies that a quick affordance has been clicked by the user.
*
* @param configKey The configuration key corresponding to the [KeyguardQuickAffordanceModel] of
* the affordance that was clicked
* @param animationController An optional controller for the activity-launch animation
*/
fun onQuickAffordanceClicked(
configKey: KClass<out KeyguardQuickAffordanceConfig>,
animationController: ActivityLaunchAnimator.Controller?,
) {
@Suppress("UNCHECKED_CAST") val config = registry.get(configKey as KClass<Nothing>)
when (val result = config.onQuickAffordanceClicked(animationController)) {
is KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity ->
launchQuickAffordance(
intent = result.intent,
canShowWhileLocked = result.canShowWhileLocked,
animationController = animationController
)
is KeyguardQuickAffordanceConfig.OnClickedResult.Handled -> Unit
}
}
private fun quickAffordanceInternal(
position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceModel> {
val configs = registry.getAll(position)
return combine(configs.map { config -> config.state }) { states ->
val index = states.indexOfFirst { it is KeyguardQuickAffordanceConfig.State.Visible }
if (index != -1) {
val visibleState = states[index] as KeyguardQuickAffordanceConfig.State.Visible
KeyguardQuickAffordanceModel.Visible(
configKey = configs[index]::class,
icon = visibleState.icon,
contentDescriptionResourceId = visibleState.contentDescriptionResourceId,
)
} else {
KeyguardQuickAffordanceModel.Hidden
}
}
}
private fun launchQuickAffordance(
intent: Intent,
canShowWhileLocked: Boolean,
animationController: ActivityLaunchAnimator.Controller?,
) {
@LockPatternUtils.StrongAuthTracker.StrongAuthFlags
val strongAuthFlags =
lockPatternUtils.getStrongAuthForUser(userTracker.userHandle.identifier)
val needsToUnlockFirst =
when {
strongAuthFlags ==
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT -> true
!canShowWhileLocked && !keyguardStateController.isUnlocked -> true
else -> false
}
if (needsToUnlockFirst) {
activityStarter.postStartActivityDismissingKeyguard(
intent,
0 /* delay */,
animationController
)
} else {
activityStarter.startActivity(
intent,
true /* dismissShade */,
animationController,
true /* showOverLockscreenWhenLocked */,
)
}
}
}

View File

@@ -42,21 +42,4 @@ sealed class KeyguardQuickAffordanceModel {
*/ */
@StringRes val contentDescriptionResourceId: Int, @StringRes val contentDescriptionResourceId: Int,
) : KeyguardQuickAffordanceModel() ) : KeyguardQuickAffordanceModel()
companion object {
fun from(
state: KeyguardQuickAffordanceConfig.State?,
configKey: KClass<out KeyguardQuickAffordanceConfig>,
): KeyguardQuickAffordanceModel {
return when (state) {
is KeyguardQuickAffordanceConfig.State.Visible ->
Visible(
configKey = configKey,
icon = state.icon,
contentDescriptionResourceId = state.contentDescriptionResourceId,
)
else -> Hidden
}
}
}
} }

View File

@@ -25,5 +25,5 @@ interface KeyguardQuickAffordanceModule {
@Binds @Binds
fun keyguardQuickAffordanceRegistry( fun keyguardQuickAffordanceRegistry(
impl: KeyguardQuickAffordanceRegistryImpl impl: KeyguardQuickAffordanceRegistryImpl
): KeyguardQuickAffordanceRegistry ): KeyguardQuickAffordanceRegistry<out KeyguardQuickAffordanceConfig>
} }

View File

@@ -22,9 +22,9 @@ import javax.inject.Inject
import kotlin.reflect.KClass import kotlin.reflect.KClass
/** Central registry of all known quick affordance configs. */ /** Central registry of all known quick affordance configs. */
interface KeyguardQuickAffordanceRegistry { interface KeyguardQuickAffordanceRegistry<T : KeyguardQuickAffordanceConfig> {
fun getAll(position: KeyguardQuickAffordancePosition): List<KeyguardQuickAffordanceConfig> fun getAll(position: KeyguardQuickAffordancePosition): List<T>
fun get(configClass: KClass<out KeyguardQuickAffordanceConfig>): KeyguardQuickAffordanceConfig fun get(configClass: KClass<out T>): T
} }
class KeyguardQuickAffordanceRegistryImpl class KeyguardQuickAffordanceRegistryImpl
@@ -33,7 +33,7 @@ constructor(
homeControls: HomeControlsKeyguardQuickAffordanceConfig, homeControls: HomeControlsKeyguardQuickAffordanceConfig,
quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig, quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig, qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
) : KeyguardQuickAffordanceRegistry { ) : KeyguardQuickAffordanceRegistry<KeyguardQuickAffordanceConfig> {
private val configsByPosition = private val configsByPosition =
mapOf( mapOf(
KeyguardQuickAffordancePosition.BOTTOM_START to KeyguardQuickAffordancePosition.BOTTOM_START to

View File

@@ -1,34 +0,0 @@
/*
* 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.usecase
import dagger.Binds
import dagger.Module
@Module
interface KeyguardUseCaseModule {
@Binds
fun launchQuickAffordance(
impl: LaunchKeyguardQuickAffordanceUseCaseImpl
): LaunchKeyguardQuickAffordanceUseCase
@Binds
fun observeKeyguardQuickAffordance(
impl: ObserveKeyguardQuickAffordanceUseCaseImpl
): ObserveKeyguardQuickAffordanceUseCase
}

View File

@@ -1,76 +0,0 @@
/*
* 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.usecase
import android.content.Intent
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.StrongAuthFlags
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
/** Defines interface for classes that can launch a quick affordance. */
interface LaunchKeyguardQuickAffordanceUseCase {
operator fun invoke(
intent: Intent,
canShowWhileLocked: Boolean,
animationController: ActivityLaunchAnimator.Controller?,
)
}
/** Real implementation of [LaunchKeyguardQuickAffordanceUseCase] */
class LaunchKeyguardQuickAffordanceUseCaseImpl
@Inject
constructor(
private val lockPatternUtils: LockPatternUtils,
private val keyguardStateController: KeyguardStateController,
private val userTracker: UserTracker,
private val activityStarter: ActivityStarter,
) : LaunchKeyguardQuickAffordanceUseCase {
override operator fun invoke(
intent: Intent,
canShowWhileLocked: Boolean,
animationController: ActivityLaunchAnimator.Controller?,
) {
@StrongAuthFlags
val strongAuthFlags =
lockPatternUtils.getStrongAuthForUser(userTracker.userHandle.identifier)
val needsToUnlockFirst =
when {
strongAuthFlags ==
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT -> true
!canShowWhileLocked && !keyguardStateController.isUnlocked -> true
else -> false
}
if (needsToUnlockFirst) {
activityStarter.postStartActivityDismissingKeyguard(
intent,
0 /* delay */,
animationController
)
} else {
activityStarter.startActivity(
intent,
true /* dismissShade */,
animationController,
true /* showOverLockscreenWhenLocked */,
)
}
}
}

View File

@@ -1,32 +0,0 @@
/*
* 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.usecase
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
/** Use-case for observing whether doze state transitions should animate the bottom area */
class ObserveAnimateBottomAreaTransitionsUseCase
@Inject
constructor(
private val repository: KeyguardRepository,
) {
operator fun invoke(): Flow<Boolean> {
return repository.animateBottomAreaDozingTransitions
}
}

View File

@@ -1,32 +0,0 @@
/*
* 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.usecase
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
/** Use-case for observing the alpha of the bottom area */
class ObserveBottomAreaAlphaUseCase
@Inject
constructor(
private val repository: KeyguardRepository,
) {
operator fun invoke(): Flow<Float> {
return repository.bottomAreaAlpha
}
}

View File

@@ -1,35 +0,0 @@
/*
* 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.usecase
import com.android.systemui.common.domain.model.Position
import com.android.systemui.common.domain.model.Position.Companion.toDomainLayer
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
/** Use-case for observing the position of the clock. */
class ObserveClockPositionUseCase
@Inject
constructor(
private val repository: KeyguardRepository,
) {
operator fun invoke(): Flow<Position> {
return repository.clockPosition.map { it.toDomainLayer() }
}
}

View File

@@ -1,32 +0,0 @@
/*
* 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.usecase
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
/** Use-case for observing the amount of doze the system is in. */
class ObserveDozeAmountUseCase
@Inject
constructor(
private val repository: KeyguardRepository,
) {
operator fun invoke(): Flow<Float> {
return repository.dozeAmount
}
}

View File

@@ -1,32 +0,0 @@
/*
* 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.usecase
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
/** Use-case for observing whether we are dozing. */
class ObserveIsDozingUseCase
@Inject
constructor(
private val repository: KeyguardRepository,
) {
operator fun invoke(): Flow<Boolean> {
return repository.isDozing
}
}

View File

@@ -1,75 +0,0 @@
/*
* 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.usecase
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
/** Defines interface for use-case for observing the model of a quick affordance in the keyguard. */
interface ObserveKeyguardQuickAffordanceUseCase {
operator fun invoke(
position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceModel>
}
class ObserveKeyguardQuickAffordanceUseCaseImpl
@Inject
constructor(
private val registry: KeyguardQuickAffordanceRegistry,
private val isDozingUseCase: ObserveIsDozingUseCase,
private val isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase,
) : ObserveKeyguardQuickAffordanceUseCase {
override fun invoke(
position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceModel> {
return combine(
affordance(position),
isDozingUseCase(),
isKeyguardShowingUseCase(),
) { affordance, isDozing, isKeyguardShowing ->
if (!isDozing && isKeyguardShowing) {
affordance
} else {
KeyguardQuickAffordanceModel.Hidden
}
}
}
private fun affordance(
position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceModel> {
val configs = registry.getAll(position)
return combine(configs.map { config -> config.state }) { states ->
val index =
states.indexOfFirst { state ->
state is KeyguardQuickAffordanceConfig.State.Visible
}
val visibleState =
if (index != -1) {
states[index] as KeyguardQuickAffordanceConfig.State.Visible
} else {
null
}
KeyguardQuickAffordanceModel.from(visibleState, configs[index]::class)
}
}
}

View File

@@ -1,49 +0,0 @@
/*
* 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.usecase
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
import javax.inject.Inject
import kotlin.reflect.KClass
/** Use-case for handling a click on a keyguard quick affordance (e.g. bottom button). */
class OnKeyguardQuickAffordanceClickedUseCase
@Inject
constructor(
private val registry: KeyguardQuickAffordanceRegistry,
private val launchAffordanceUseCase: LaunchKeyguardQuickAffordanceUseCase,
) {
operator fun invoke(
configKey: KClass<*>,
animationController: ActivityLaunchAnimator.Controller?,
) {
@Suppress("UNCHECKED_CAST")
val config = registry.get(configKey as KClass<out KeyguardQuickAffordanceConfig>)
when (val result = config.onQuickAffordanceClicked(animationController)) {
is OnClickedResult.StartActivity ->
launchAffordanceUseCase(
intent = result.intent,
canShowWhileLocked = result.canShowWhileLocked,
animationController = animationController
)
is OnClickedResult.Handled -> Unit
}
}
}

View File

@@ -1,31 +0,0 @@
/*
* 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.usecase
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import javax.inject.Inject
/** Use-case for setting the updated clock position. */
class SetClockPositionUseCase
@Inject
constructor(
private val keyguardRepository: KeyguardRepository,
) {
operator fun invoke(x: Int, y: Int) {
keyguardRepository.setClockPosition(x, y)
}
}

View File

@@ -1,31 +0,0 @@
/*
* 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.usecase
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import javax.inject.Inject
/** Use-case for setting the alpha that the keyguard bottom area should use */
class SetKeyguardBottomAreaAlphaUseCase
@Inject
constructor(
private val repository: KeyguardRepository,
) {
operator fun invoke(alpha: Float) {
repository.setBottomAreaAlpha(alpha)
}
}

View File

@@ -1,33 +0,0 @@
/*
* 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.usecase
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import javax.inject.Inject
/**
* Use-case for setting whether the keyguard bottom area should animate the next doze transitions
*/
class SetKeyguardBottomAreaAnimateDozingTransitionsUseCase
@Inject
constructor(
private val repository: KeyguardRepository,
) {
operator fun invoke(animate: Boolean) {
repository.setAnimateDozingTransitions(animate)
}
}

View File

@@ -17,15 +17,11 @@
package com.android.systemui.keyguard.ui.viewmodel package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.doze.util.BurnInHelperWrapper import com.android.systemui.doze.util.BurnInHelperWrapper
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.domain.usecase.ObserveAnimateBottomAreaTransitionsUseCase
import com.android.systemui.keyguard.domain.usecase.ObserveBottomAreaAlphaUseCase
import com.android.systemui.keyguard.domain.usecase.ObserveClockPositionUseCase
import com.android.systemui.keyguard.domain.usecase.ObserveDozeAmountUseCase
import com.android.systemui.keyguard.domain.usecase.ObserveIsDozingUseCase
import com.android.systemui.keyguard.domain.usecase.ObserveKeyguardQuickAffordanceUseCase
import com.android.systemui.keyguard.domain.usecase.OnKeyguardQuickAffordanceClickedUseCase
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
@@ -36,13 +32,9 @@ import kotlinx.coroutines.flow.map
class KeyguardBottomAreaViewModel class KeyguardBottomAreaViewModel
@Inject @Inject
constructor( constructor(
private val observeQuickAffordanceUseCase: ObserveKeyguardQuickAffordanceUseCase, private val keyguardInteractor: KeyguardInteractor,
private val onQuickAffordanceClickedUseCase: OnKeyguardQuickAffordanceClickedUseCase, private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor,
observeBottomAreaAlphaUseCase: ObserveBottomAreaAlphaUseCase, bottomAreaInteractor: KeyguardBottomAreaInteractor,
observeIsDozingUseCase: ObserveIsDozingUseCase,
observeAnimateBottomAreaTransitionsUseCase: ObserveAnimateBottomAreaTransitionsUseCase,
private val observeDozeAmountUseCase: ObserveDozeAmountUseCase,
observeClockPositionUseCase: ObserveClockPositionUseCase,
private val burnInHelperWrapper: BurnInHelperWrapper, private val burnInHelperWrapper: BurnInHelperWrapper,
) { ) {
/** An observable for the view-model of the "start button" quick affordance. */ /** An observable for the view-model of the "start button" quick affordance. */
@@ -56,12 +48,12 @@ constructor(
* animate. * animate.
*/ */
val animateButtonReveal: Flow<Boolean> = val animateButtonReveal: Flow<Boolean> =
observeAnimateBottomAreaTransitionsUseCase().distinctUntilChanged() bottomAreaInteractor.animateDozingTransitions.distinctUntilChanged()
/** An observable for whether the overlay container should be visible. */ /** An observable for whether the overlay container should be visible. */
val isOverlayContainerVisible: Flow<Boolean> = val isOverlayContainerVisible: Flow<Boolean> =
observeIsDozingUseCase().map { !it }.distinctUntilChanged() keyguardInteractor.isDozing.map { !it }.distinctUntilChanged()
/** An observable for the alpha level for the entire bottom area. */ /** An observable for the alpha level for the entire bottom area. */
val alpha: Flow<Float> = observeBottomAreaAlphaUseCase().distinctUntilChanged() val alpha: Flow<Float> = bottomAreaInteractor.alpha.distinctUntilChanged()
/** An observable for whether the indication area should be padded. */ /** An observable for whether the indication area should be padded. */
val isIndicationAreaPadded: Flow<Boolean> = val isIndicationAreaPadded: Flow<Boolean> =
combine(startButton, endButton) { startButtonModel, endButtonModel -> combine(startButton, endButton) { startButtonModel, endButtonModel ->
@@ -70,11 +62,11 @@ constructor(
.distinctUntilChanged() .distinctUntilChanged()
/** An observable for the x-offset by which the indication area should be translated. */ /** An observable for the x-offset by which the indication area should be translated. */
val indicationAreaTranslationX: Flow<Float> = val indicationAreaTranslationX: Flow<Float> =
observeClockPositionUseCase().map { it.x.toFloat() }.distinctUntilChanged() bottomAreaInteractor.clockPosition.map { it.x.toFloat() }.distinctUntilChanged()
/** Returns an observable for the y-offset by which the indication area should be translated. */ /** Returns an observable for the y-offset by which the indication area should be translated. */
fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> { fun indicationAreaTranslationY(defaultBurnInOffset: Int): Flow<Float> {
return observeDozeAmountUseCase() return keyguardInteractor.dozeAmount
.map { dozeAmount -> .map { dozeAmount ->
dozeAmount * dozeAmount *
(burnInHelperWrapper.burnInOffset( (burnInHelperWrapper.burnInOffset(
@@ -88,7 +80,8 @@ constructor(
private fun button( private fun button(
position: KeyguardQuickAffordancePosition position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceViewModel> { ): Flow<KeyguardQuickAffordanceViewModel> {
return observeQuickAffordanceUseCase(position) return quickAffordanceInteractor
.quickAffordance(position)
.map { model -> model.toViewModel() } .map { model -> model.toViewModel() }
.distinctUntilChanged() .distinctUntilChanged()
} }
@@ -102,7 +95,7 @@ constructor(
icon = icon, icon = icon,
contentDescriptionResourceId = contentDescriptionResourceId, contentDescriptionResourceId = contentDescriptionResourceId,
onClicked = { parameters -> onClicked = { parameters ->
onQuickAffordanceClickedUseCase( quickAffordanceInteractor.onQuickAffordanceClicked(
configKey = parameters.configKey, configKey = parameters.configKey,
animationController = parameters.animationController, animationController = parameters.animationController,
) )

View File

@@ -19,18 +19,19 @@ package com.android.systemui.keyguard.ui.viewmodel
import androidx.annotation.StringRes import androidx.annotation.StringRes
import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.containeddrawable.ContainedDrawable import com.android.systemui.containeddrawable.ContainedDrawable
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
import kotlin.reflect.KClass import kotlin.reflect.KClass
/** Models the UI state of a keyguard quick affordance button. */ /** Models the UI state of a keyguard quick affordance button. */
data class KeyguardQuickAffordanceViewModel( data class KeyguardQuickAffordanceViewModel(
val configKey: KClass<*>? = null, val configKey: KClass<out KeyguardQuickAffordanceConfig>? = null,
val isVisible: Boolean = false, val isVisible: Boolean = false,
val icon: ContainedDrawable = ContainedDrawable.WithResource(0), val icon: ContainedDrawable = ContainedDrawable.WithResource(0),
@StringRes val contentDescriptionResourceId: Int = 0, @StringRes val contentDescriptionResourceId: Int = 0,
val onClicked: (OnClickedParameters) -> Unit = {}, val onClicked: (OnClickedParameters) -> Unit = {},
) { ) {
data class OnClickedParameters( data class OnClickedParameters(
val configKey: KClass<*>, val configKey: KClass<out KeyguardQuickAffordanceConfig>,
val animationController: ActivityLaunchAnimator.Controller?, val animationController: ActivityLaunchAnimator.Controller?,
) )
} }

View File

@@ -127,9 +127,7 @@ import com.android.systemui.flags.Flags;
import com.android.systemui.fragments.FragmentHostManager.FragmentListener; import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
import com.android.systemui.fragments.FragmentService; import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.usecase.SetClockPositionUseCase; import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAlphaUseCase;
import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel; import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
import com.android.systemui.media.KeyguardMediaController; import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.media.MediaDataManager; import com.android.systemui.media.MediaDataManager;
@@ -705,11 +703,7 @@ public final class NotificationPanelViewController extends PanelViewController {
private final CameraGestureHelper mCameraGestureHelper; private final CameraGestureHelper mCameraGestureHelper;
private final Provider<KeyguardBottomAreaViewModel> mKeyguardBottomAreaViewModelProvider; private final Provider<KeyguardBottomAreaViewModel> mKeyguardBottomAreaViewModelProvider;
private final Provider<SetClockPositionUseCase> mSetClockPositionUseCaseProvider; private final Provider<KeyguardBottomAreaInteractor> mKeyguardBottomAreaInteractorProvider;
private final Provider<SetKeyguardBottomAreaAlphaUseCase>
mSetKeyguardBottomAreaAlphaUseCaseProvider;
private final Provider<SetKeyguardBottomAreaAnimateDozingTransitionsUseCase>
mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider;
@Inject @Inject
public NotificationPanelViewController(NotificationPanelView view, public NotificationPanelViewController(NotificationPanelView view,
@@ -781,10 +775,7 @@ public final class NotificationPanelViewController extends PanelViewController {
SystemClock systemClock, SystemClock systemClock,
CameraGestureHelper cameraGestureHelper, CameraGestureHelper cameraGestureHelper,
Provider<KeyguardBottomAreaViewModel> keyguardBottomAreaViewModelProvider, Provider<KeyguardBottomAreaViewModel> keyguardBottomAreaViewModelProvider,
Provider<SetClockPositionUseCase> setClockPositionUseCaseProvider, Provider<KeyguardBottomAreaInteractor> keyguardBottomAreaInteractorProvider) {
Provider<SetKeyguardBottomAreaAlphaUseCase> setKeyguardBottomAreaAlphaUseCaseProvider,
Provider<SetKeyguardBottomAreaAnimateDozingTransitionsUseCase>
setKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider) {
super(view, super(view,
falsingManager, falsingManager,
dozeLog, dozeLog,
@@ -966,10 +957,7 @@ public final class NotificationPanelViewController extends PanelViewController {
} }
}); });
mCameraGestureHelper = cameraGestureHelper; mCameraGestureHelper = cameraGestureHelper;
mSetClockPositionUseCaseProvider = setClockPositionUseCaseProvider; mKeyguardBottomAreaInteractorProvider = keyguardBottomAreaInteractorProvider;
mSetKeyguardBottomAreaAlphaUseCaseProvider = setKeyguardBottomAreaAlphaUseCaseProvider;
mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider =
setKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider;
} }
@VisibleForTesting @VisibleForTesting
@@ -1487,7 +1475,7 @@ public final class NotificationPanelViewController extends PanelViewController {
mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard), mKeyguardStatusViewController.getClockBottom(mStatusBarHeaderHeightKeyguard),
mKeyguardStatusViewController.isClockTopAligned()); mKeyguardStatusViewController.isClockTopAligned());
mClockPositionAlgorithm.run(mClockPositionResult); mClockPositionAlgorithm.run(mClockPositionResult);
mSetClockPositionUseCaseProvider.get().invoke( mKeyguardBottomAreaInteractorProvider.get().setClockPosition(
mClockPositionResult.clockX, mClockPositionResult.clockY); mClockPositionResult.clockX, mClockPositionResult.clockY);
boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending(); boolean animate = mNotificationStackScrollLayoutController.isAddOrRemoveAnimationPending();
boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange; boolean animateClock = (animate || mAnimateNextPositionUpdate) && shouldAnimateClockChange;
@@ -3261,7 +3249,7 @@ public final class NotificationPanelViewController extends PanelViewController {
float alpha = Math.min(expansionAlpha, 1 - computeQsExpansionFraction()); float alpha = Math.min(expansionAlpha, 1 - computeQsExpansionFraction());
alpha *= mBottomAreaShadeAlpha; alpha *= mBottomAreaShadeAlpha;
mKeyguardBottomArea.setComponentAlphas(alpha); mKeyguardBottomArea.setComponentAlphas(alpha);
mSetKeyguardBottomAreaAlphaUseCaseProvider.get().invoke(alpha); mKeyguardBottomAreaInteractorProvider.get().setAlpha(alpha);
mLockIconViewController.setAlpha(alpha); mLockIconViewController.setAlpha(alpha);
} }
@@ -3461,7 +3449,7 @@ public final class NotificationPanelViewController extends PanelViewController {
private void updateDozingVisibilities(boolean animate) { private void updateDozingVisibilities(boolean animate) {
mKeyguardBottomArea.setDozing(mDozing, animate); mKeyguardBottomArea.setDozing(mDozing, animate);
mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider.get().invoke(animate); mKeyguardBottomAreaInteractorProvider.get().setAnimateDozingTransitions(animate);
if (!mDozing && animate) { if (!mDozing && animate) {
mKeyguardStatusBarViewController.animateKeyguardStatusBarIn(); mKeyguardStatusBarViewController.animateKeyguardStatusBarIn();
} }
@@ -3764,7 +3752,7 @@ public final class NotificationPanelViewController extends PanelViewController {
mDozing = dozing; mDozing = dozing;
mNotificationStackScrollLayoutController.setDozing(mDozing, animate, wakeUpTouchLocation); mNotificationStackScrollLayoutController.setDozing(mDozing, animate, wakeUpTouchLocation);
mKeyguardBottomArea.setDozing(mDozing, animate); mKeyguardBottomArea.setDozing(mDozing, animate);
mSetKeyguardBottomAreaAnimateDozingTransitionsUseCaseProvider.get().invoke(animate); mKeyguardBottomAreaInteractorProvider.get().setAnimateDozingTransitions(animate);
mKeyguardStatusBarViewController.setDozing(mDozing); mKeyguardStatusBarViewController.setDozing(mDozing);
if (dozing) { if (dozing) {

View File

@@ -16,11 +16,10 @@
package com.android.systemui.keyguard.data.repository package com.android.systemui.keyguard.data.repository
import com.android.systemui.common.data.model.Position import com.android.systemui.common.shared.model.Position
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.yield
/** Fake implementation of [KeyguardRepository] */ /** Fake implementation of [KeyguardRepository] */
class FakeKeyguardRepository : KeyguardRepository { class FakeKeyguardRepository : KeyguardRepository {
@@ -56,30 +55,15 @@ class FakeKeyguardRepository : KeyguardRepository {
_clockPosition.value = Position(x, y) _clockPosition.value = Position(x, y)
} }
suspend fun setKeyguardShowing(isShowing: Boolean) { fun setKeyguardShowing(isShowing: Boolean) {
_isKeyguardShowing.value = isShowing _isKeyguardShowing.value = isShowing
// Yield to allow the test's collection coroutine to "catch up" and collect this value
// before the test continues to the next line.
// TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
// https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
yield()
} }
suspend fun setDozing(isDozing: Boolean) { fun setDozing(isDozing: Boolean) {
_isDozing.value = isDozing _isDozing.value = isDozing
// Yield to allow the test's collection coroutine to "catch up" and collect this value
// before the test continues to the next line.
// TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
// https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
yield()
} }
suspend fun setDozeAmount(dozeAmount: Float) { fun setDozeAmount(dozeAmount: Float) {
_dozeAmount.value = dozeAmount _dozeAmount.value = dozeAmount
// Yield to allow the test's collection coroutine to "catch up" and collect this value
// before the test continues to the next line.
// TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
// https://developer.android.com/kotlin/flow/test#continuous-collection and remove this.
yield()
} }
} }

View File

@@ -18,7 +18,7 @@ package com.android.systemui.keyguard.data.repository
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.common.data.model.Position import com.android.systemui.common.shared.model.Position
import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.argumentCaptor

View File

@@ -23,18 +23,18 @@ import kotlin.reflect.KClass
/** Fake implementation of [FakeKeyguardQuickAffordanceRegistry], for tests. */ /** Fake implementation of [FakeKeyguardQuickAffordanceRegistry], for tests. */
class FakeKeyguardQuickAffordanceRegistry( class FakeKeyguardQuickAffordanceRegistry(
private val configsByPosition: private val configsByPosition:
Map<KeyguardQuickAffordancePosition, List<KeyguardQuickAffordanceConfig>>, Map<KeyguardQuickAffordancePosition, List<FakeKeyguardQuickAffordanceConfig>>,
) : KeyguardQuickAffordanceRegistry { ) : KeyguardQuickAffordanceRegistry<FakeKeyguardQuickAffordanceConfig> {
override fun getAll( override fun getAll(
position: KeyguardQuickAffordancePosition position: KeyguardQuickAffordancePosition
): List<KeyguardQuickAffordanceConfig> { ): List<FakeKeyguardQuickAffordanceConfig> {
return configsByPosition.getValue(position) return configsByPosition.getValue(position)
} }
override fun get( override fun get(
configClass: KClass<out KeyguardQuickAffordanceConfig> configClass: KClass<out FakeKeyguardQuickAffordanceConfig>
): KeyguardQuickAffordanceConfig { ): FakeKeyguardQuickAffordanceConfig {
return configsByPosition.values return configsByPosition.values
.flatten() .flatten()
.associateBy { config -> config::class } .associateBy { config -> config::class }

View File

@@ -1,47 +0,0 @@
/*
* 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.usecase
import android.content.Intent
import com.android.systemui.animation.ActivityLaunchAnimator
/** Fake implementation of [LaunchKeyguardQuickAffordanceUseCase], for tests. */
class FakeLaunchKeyguardQuickAffordanceUseCase : LaunchKeyguardQuickAffordanceUseCase {
data class Invocation(
val intent: Intent,
val canShowWhileLocked: Boolean,
val animationController: ActivityLaunchAnimator.Controller?
)
private val _invocations = mutableListOf<Invocation>()
val invocations: List<Invocation> = _invocations
override fun invoke(
intent: Intent,
canShowWhileLocked: Boolean,
animationController: ActivityLaunchAnimator.Controller?
) {
_invocations.add(
Invocation(
intent = intent,
canShowWhileLocked = canShowWhileLocked,
animationController = animationController,
)
)
}
}

View File

@@ -1,46 +0,0 @@
/*
* 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.usecase
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
class FakeObserveKeyguardQuickAffordanceUseCase : ObserveKeyguardQuickAffordanceUseCase {
private val affordanceByPosition =
mutableMapOf<
KeyguardQuickAffordancePosition, MutableStateFlow<KeyguardQuickAffordanceModel>>()
init {
KeyguardQuickAffordancePosition.values().forEach { position ->
affordanceByPosition[position] = MutableStateFlow(KeyguardQuickAffordanceModel.Hidden)
}
}
override fun invoke(
position: KeyguardQuickAffordancePosition
): Flow<KeyguardQuickAffordanceModel> {
return affordanceByPosition[position] ?: error("Flow unexpectedly missing!")
}
fun setModel(position: KeyguardQuickAffordancePosition, model: KeyguardQuickAffordanceModel) {
affordanceByPosition[position]?.value = model
}
}

View File

@@ -0,0 +1,294 @@
/*
* 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.usecase
import android.content.Intent
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.containeddrawable.ContainedDrawable
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import kotlinx.coroutines.test.runBlockingTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameter
import org.junit.runners.Parameterized.Parameters
import org.mockito.ArgumentMatchers.eq
import org.mockito.ArgumentMatchers.same
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(Parameterized::class)
class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
companion object {
private val INTENT = Intent("some.intent.action")
private val DRAWABLE = mock<ContainedDrawable>()
private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
@Parameters(
name =
"needStrongAuthAfterBoot={0}, canShowWhileLocked={1}," +
" keyguardIsUnlocked={2}, needsToUnlockFirst={3}, startActivity={4}"
)
@JvmStatic
fun data() =
listOf(
arrayOf(
/* needStrongAuthAfterBoot= */ false,
/* canShowWhileLocked= */ false,
/* keyguardIsUnlocked= */ false,
/* needsToUnlockFirst= */ true,
/* startActivity= */ false,
),
arrayOf(
/* needStrongAuthAfterBoot= */ false,
/* canShowWhileLocked= */ false,
/* keyguardIsUnlocked= */ true,
/* needsToUnlockFirst= */ false,
/* startActivity= */ false,
),
arrayOf(
/* needStrongAuthAfterBoot= */ false,
/* canShowWhileLocked= */ true,
/* keyguardIsUnlocked= */ false,
/* needsToUnlockFirst= */ false,
/* startActivity= */ false,
),
arrayOf(
/* needStrongAuthAfterBoot= */ false,
/* canShowWhileLocked= */ true,
/* keyguardIsUnlocked= */ true,
/* needsToUnlockFirst= */ false,
/* startActivity= */ false,
),
arrayOf(
/* needStrongAuthAfterBoot= */ true,
/* canShowWhileLocked= */ false,
/* keyguardIsUnlocked= */ false,
/* needsToUnlockFirst= */ true,
/* startActivity= */ false,
),
arrayOf(
/* needStrongAuthAfterBoot= */ true,
/* canShowWhileLocked= */ false,
/* keyguardIsUnlocked= */ true,
/* needsToUnlockFirst= */ true,
/* startActivity= */ false,
),
arrayOf(
/* needStrongAuthAfterBoot= */ true,
/* canShowWhileLocked= */ true,
/* keyguardIsUnlocked= */ false,
/* needsToUnlockFirst= */ true,
/* startActivity= */ false,
),
arrayOf(
/* needStrongAuthAfterBoot= */ true,
/* canShowWhileLocked= */ true,
/* keyguardIsUnlocked= */ true,
/* needsToUnlockFirst= */ true,
/* startActivity= */ false,
),
arrayOf(
/* needStrongAuthAfterBoot= */ false,
/* canShowWhileLocked= */ false,
/* keyguardIsUnlocked= */ false,
/* needsToUnlockFirst= */ true,
/* startActivity= */ true,
),
arrayOf(
/* needStrongAuthAfterBoot= */ false,
/* canShowWhileLocked= */ false,
/* keyguardIsUnlocked= */ true,
/* needsToUnlockFirst= */ false,
/* startActivity= */ true,
),
arrayOf(
/* needStrongAuthAfterBoot= */ false,
/* canShowWhileLocked= */ true,
/* keyguardIsUnlocked= */ false,
/* needsToUnlockFirst= */ false,
/* startActivity= */ true,
),
arrayOf(
/* needStrongAuthAfterBoot= */ false,
/* canShowWhileLocked= */ true,
/* keyguardIsUnlocked= */ true,
/* needsToUnlockFirst= */ false,
/* startActivity= */ true,
),
arrayOf(
/* needStrongAuthAfterBoot= */ true,
/* canShowWhileLocked= */ false,
/* keyguardIsUnlocked= */ false,
/* needsToUnlockFirst= */ true,
/* startActivity= */ true,
),
arrayOf(
/* needStrongAuthAfterBoot= */ true,
/* canShowWhileLocked= */ false,
/* keyguardIsUnlocked= */ true,
/* needsToUnlockFirst= */ true,
/* startActivity= */ true,
),
arrayOf(
/* needStrongAuthAfterBoot= */ true,
/* canShowWhileLocked= */ true,
/* keyguardIsUnlocked= */ false,
/* needsToUnlockFirst= */ true,
/* startActivity= */ true,
),
arrayOf(
/* needStrongAuthAfterBoot= */ true,
/* canShowWhileLocked= */ true,
/* keyguardIsUnlocked= */ true,
/* needsToUnlockFirst= */ true,
/* startActivity= */ true,
),
)
}
@Mock private lateinit var lockPatternUtils: LockPatternUtils
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
private lateinit var underTest: KeyguardQuickAffordanceInteractor
@JvmField @Parameter(0) var needStrongAuthAfterBoot: Boolean = false
@JvmField @Parameter(1) var canShowWhileLocked: Boolean = false
@JvmField @Parameter(2) var keyguardIsUnlocked: Boolean = false
@JvmField @Parameter(3) var needsToUnlockFirst: Boolean = false
@JvmField @Parameter(4) var startActivity: Boolean = false
private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
underTest =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = KeyguardInteractor(repository = FakeKeyguardRepository()),
registry =
FakeKeyguardQuickAffordanceRegistry(
mapOf(
KeyguardQuickAffordancePosition.BOTTOM_START to
listOf(
homeControls,
),
KeyguardQuickAffordancePosition.BOTTOM_END to
listOf(
object : FakeKeyguardQuickAffordanceConfig() {},
object : FakeKeyguardQuickAffordanceConfig() {},
),
),
),
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
activityStarter = activityStarter,
)
}
@Test
fun onQuickAffordanceClicked() = runBlockingTest {
setUpMocks(
needStrongAuthAfterBoot = needStrongAuthAfterBoot,
keyguardIsUnlocked = keyguardIsUnlocked,
)
homeControls.setState(
state =
KeyguardQuickAffordanceConfig.State.Visible(
icon = DRAWABLE,
contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
)
)
homeControls.onClickedResult =
if (startActivity) {
KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
intent = INTENT,
canShowWhileLocked = canShowWhileLocked,
)
} else {
KeyguardQuickAffordanceConfig.OnClickedResult.Handled
}
underTest.onQuickAffordanceClicked(
configKey = homeControls::class,
animationController = animationController,
)
if (startActivity) {
if (needsToUnlockFirst) {
verify(activityStarter)
.postStartActivityDismissingKeyguard(
any(),
/* delay= */ eq(0),
same(animationController),
)
} else {
verify(activityStarter)
.startActivity(
any(),
/* dismissShade= */ eq(true),
same(animationController),
/* showOverLockscreenWhenLocked= */ eq(true),
)
}
} else {
verifyZeroInteractions(activityStarter)
}
}
private fun setUpMocks(
needStrongAuthAfterBoot: Boolean = true,
keyguardIsUnlocked: Boolean = false,
) {
whenever(userTracker.userHandle).thenReturn(mock())
whenever(lockPatternUtils.getStrongAuthForUser(any()))
.thenReturn(
if (needStrongAuthAfterBoot) {
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
} else {
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
}
)
whenever(keyguardStateController.isUnlocked).thenReturn(keyguardIsUnlocked)
}
}

View File

@@ -17,14 +17,20 @@
package com.android.systemui.keyguard.domain.usecase package com.android.systemui.keyguard.domain.usecase
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.containeddrawable.ContainedDrawable import com.android.systemui.containeddrawable.ContainedDrawable
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
@@ -34,33 +40,39 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.runners.JUnit4 import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@SmallTest @SmallTest
@RunWith(JUnit4::class) @RunWith(JUnit4::class)
class ObserveKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() { class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
private lateinit var underTest: ObserveKeyguardQuickAffordanceUseCase @Mock private lateinit var lockPatternUtils: LockPatternUtils
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var activityStarter: ActivityStarter
private lateinit var underTest: KeyguardQuickAffordanceInteractor
private lateinit var repository: FakeKeyguardRepository private lateinit var repository: FakeKeyguardRepository
private lateinit var isDozingUseCase: ObserveIsDozingUseCase
private lateinit var isKeyguardShowingUseCase: ObserveIsKeyguardShowingUseCase
private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig
private lateinit var qrCodeScanner: FakeKeyguardQuickAffordanceConfig private lateinit var qrCodeScanner: FakeKeyguardQuickAffordanceConfig
@Before @Before
fun setUp() = runBlockingTest { fun setUp() {
MockitoAnnotations.initMocks(this)
repository = FakeKeyguardRepository() repository = FakeKeyguardRepository()
repository.setKeyguardShowing(true) repository.setKeyguardShowing(true)
isDozingUseCase = ObserveIsDozingUseCase(repository)
isKeyguardShowingUseCase = ObserveIsKeyguardShowingUseCase(repository)
homeControls = object : FakeKeyguardQuickAffordanceConfig() {} homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
quickAccessWallet = object : FakeKeyguardQuickAffordanceConfig() {} quickAccessWallet = object : FakeKeyguardQuickAffordanceConfig() {}
qrCodeScanner = object : FakeKeyguardQuickAffordanceConfig() {} qrCodeScanner = object : FakeKeyguardQuickAffordanceConfig() {}
underTest = underTest =
ObserveKeyguardQuickAffordanceUseCaseImpl( KeyguardQuickAffordanceInteractor(
keyguardInteractor = KeyguardInteractor(repository = repository),
registry = registry =
FakeKeyguardQuickAffordanceRegistry( FakeKeyguardQuickAffordanceRegistry(
mapOf( mapOf(
@@ -75,13 +87,15 @@ class ObserveKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
), ),
), ),
), ),
isDozingUseCase = isDozingUseCase, lockPatternUtils = lockPatternUtils,
isKeyguardShowingUseCase = isKeyguardShowingUseCase, keyguardStateController = keyguardStateController,
userTracker = userTracker,
activityStarter = activityStarter,
) )
} }
@Test @Test
fun `invoke - bottom start affordance is visible`() = runBlockingTest { fun `quickAffordance - bottom start affordance is visible`() = runBlockingTest {
val configKey = homeControls::class val configKey = homeControls::class
homeControls.setState( homeControls.setState(
KeyguardQuickAffordanceConfig.State.Visible( KeyguardQuickAffordanceConfig.State.Visible(
@@ -92,7 +106,8 @@ class ObserveKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
var latest: KeyguardQuickAffordanceModel? = null var latest: KeyguardQuickAffordanceModel? = null
val job = val job =
underTest(KeyguardQuickAffordancePosition.BOTTOM_START) underTest
.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
.onEach { latest = it } .onEach { latest = it }
.launchIn(this) .launchIn(this)
@@ -106,7 +121,7 @@ class ObserveKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
} }
@Test @Test
fun `invoke - bottom end affordance is visible`() = runBlockingTest { fun `quickAffordance - bottom end affordance is visible`() = runBlockingTest {
val configKey = quickAccessWallet::class val configKey = quickAccessWallet::class
quickAccessWallet.setState( quickAccessWallet.setState(
KeyguardQuickAffordanceConfig.State.Visible( KeyguardQuickAffordanceConfig.State.Visible(
@@ -117,7 +132,8 @@ class ObserveKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
var latest: KeyguardQuickAffordanceModel? = null var latest: KeyguardQuickAffordanceModel? = null
val job = val job =
underTest(KeyguardQuickAffordancePosition.BOTTOM_END) underTest
.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
.onEach { latest = it } .onEach { latest = it }
.launchIn(this) .launchIn(this)
@@ -131,7 +147,7 @@ class ObserveKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
} }
@Test @Test
fun `invoke - bottom start affordance hidden while dozing`() = runBlockingTest { fun `quickAffordance - bottom start affordance hidden while dozing`() = runBlockingTest {
repository.setDozing(true) repository.setDozing(true)
homeControls.setState( homeControls.setState(
KeyguardQuickAffordanceConfig.State.Visible( KeyguardQuickAffordanceConfig.State.Visible(
@@ -142,7 +158,8 @@ class ObserveKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
var latest: KeyguardQuickAffordanceModel? = null var latest: KeyguardQuickAffordanceModel? = null
val job = val job =
underTest(KeyguardQuickAffordancePosition.BOTTOM_START) underTest
.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
.onEach { latest = it } .onEach { latest = it }
.launchIn(this) .launchIn(this)
assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden) assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
@@ -150,7 +167,7 @@ class ObserveKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
} }
@Test @Test
fun `invoke - bottom start affordance hidden when lockscreen is not showing`() = fun `quickAffordance - bottom start affordance hidden when lockscreen is not showing`() =
runBlockingTest { runBlockingTest {
repository.setKeyguardShowing(false) repository.setKeyguardShowing(false)
homeControls.setState( homeControls.setState(
@@ -162,7 +179,8 @@ class ObserveKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
var latest: KeyguardQuickAffordanceModel? = null var latest: KeyguardQuickAffordanceModel? = null
val job = val job =
underTest(KeyguardQuickAffordancePosition.BOTTOM_START) underTest
.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
.onEach { latest = it } .onEach { latest = it }
.launchIn(this) .launchIn(this)
assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden) assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)

View File

@@ -1,178 +0,0 @@
/*
* 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.usecase
import android.content.Intent
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameter
import org.junit.runners.Parameterized.Parameters
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(Parameterized::class)
class LaunchKeyguardQuickAffordanceUseCaseImplTest : SysuiTestCase() {
companion object {
private val INTENT = Intent("some.intent.action")
@Parameters(
name =
"needStrongAuthAfterBoot={0}, canShowWhileLocked={1}," +
" keyguardIsUnlocked={2}, needsToUnlockFirst={3}"
)
@JvmStatic
fun data() =
listOf(
arrayOf(
/* needStrongAuthAfterBoot= */ false,
/* canShowWhileLocked= */ false,
/* keyguardIsUnlocked= */ false,
/* needsToUnlockFirst= */ true,
),
arrayOf(
/* needStrongAuthAfterBoot= */ false,
/* canShowWhileLocked= */ false,
/* keyguardIsUnlocked= */ true,
/* needsToUnlockFirst= */ false,
),
arrayOf(
/* needStrongAuthAfterBoot= */ false,
/* canShowWhileLocked= */ true,
/* keyguardIsUnlocked= */ false,
/* needsToUnlockFirst= */ false,
),
arrayOf(
/* needStrongAuthAfterBoot= */ false,
/* canShowWhileLocked= */ true,
/* keyguardIsUnlocked= */ true,
/* needsToUnlockFirst= */ false,
),
arrayOf(
/* needStrongAuthAfterBoot= */ true,
/* canShowWhileLocked= */ false,
/* keyguardIsUnlocked= */ false,
/* needsToUnlockFirst= */ true,
),
arrayOf(
/* needStrongAuthAfterBoot= */ true,
/* canShowWhileLocked= */ false,
/* keyguardIsUnlocked= */ true,
/* needsToUnlockFirst= */ true,
),
arrayOf(
/* needStrongAuthAfterBoot= */ true,
/* canShowWhileLocked= */ true,
/* keyguardIsUnlocked= */ false,
/* needsToUnlockFirst= */ true,
),
arrayOf(
/* needStrongAuthAfterBoot= */ true,
/* canShowWhileLocked= */ true,
/* keyguardIsUnlocked= */ true,
/* needsToUnlockFirst= */ true,
),
)
}
@Mock private lateinit var lockPatternUtils: LockPatternUtils
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
private lateinit var underTest: LaunchKeyguardQuickAffordanceUseCase
@JvmField @Parameter(0) var needStrongAuthAfterBoot: Boolean = false
@JvmField @Parameter(1) var canShowWhileLocked: Boolean = false
@JvmField @Parameter(2) var keyguardIsUnlocked: Boolean = false
@JvmField @Parameter(3) var needsToUnlockFirst: Boolean = false
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
underTest =
LaunchKeyguardQuickAffordanceUseCaseImpl(
lockPatternUtils = lockPatternUtils,
keyguardStateController = keyguardStateController,
userTracker = userTracker,
activityStarter = activityStarter,
)
}
@Test
fun invoke() {
setUpMocks(
needStrongAuthAfterBoot = needStrongAuthAfterBoot,
keyguardIsUnlocked = keyguardIsUnlocked,
)
underTest(
intent = INTENT,
canShowWhileLocked = canShowWhileLocked,
animationController = animationController,
)
if (needsToUnlockFirst) {
verify(activityStarter)
.postStartActivityDismissingKeyguard(
INTENT,
/* delay= */ 0,
animationController,
)
} else {
verify(activityStarter)
.startActivity(
INTENT,
/* dismissShade= */ true,
animationController,
/* showOverLockscreenWhenLocked= */ true,
)
}
}
private fun setUpMocks(
needStrongAuthAfterBoot: Boolean = true,
keyguardIsUnlocked: Boolean = false,
) {
whenever(userTracker.userHandle).thenReturn(mock())
whenever(lockPatternUtils.getStrongAuthForUser(any()))
.thenReturn(
if (needStrongAuthAfterBoot) {
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
} else {
LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
}
)
whenever(keyguardStateController.isUnlocked).thenReturn(keyguardIsUnlocked)
}
}

View File

@@ -18,24 +18,22 @@ package com.android.systemui.keyguard.ui.viewmodel
import android.content.Intent import android.content.Intent
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.ActivityLaunchAnimator import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.containeddrawable.ContainedDrawable import com.android.systemui.containeddrawable.ContainedDrawable
import com.android.systemui.doze.util.BurnInHelperWrapper import com.android.systemui.doze.util.BurnInHelperWrapper
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceConfig
import com.android.systemui.keyguard.domain.usecase.FakeLaunchKeyguardQuickAffordanceUseCase import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.keyguard.domain.usecase.FakeObserveKeyguardQuickAffordanceUseCase import com.android.systemui.settings.UserTracker
import com.android.systemui.keyguard.domain.usecase.ObserveAnimateBottomAreaTransitionsUseCase import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.keyguard.domain.usecase.ObserveBottomAreaAlphaUseCase
import com.android.systemui.keyguard.domain.usecase.ObserveClockPositionUseCase
import com.android.systemui.keyguard.domain.usecase.ObserveDozeAmountUseCase
import com.android.systemui.keyguard.domain.usecase.ObserveIsDozingUseCase
import com.android.systemui.keyguard.domain.usecase.OnKeyguardQuickAffordanceClickedUseCase
import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
@@ -49,6 +47,8 @@ import org.junit.runner.RunWith
import org.junit.runners.JUnit4 import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.Mockito.`when` as whenever import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
@@ -58,17 +58,18 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
@Mock private lateinit var animationController: ActivityLaunchAnimator.Controller @Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
@Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper @Mock private lateinit var burnInHelperWrapper: BurnInHelperWrapper
@Mock private lateinit var lockPatternUtils: LockPatternUtils
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var activityStarter: ActivityStarter
private lateinit var underTest: KeyguardBottomAreaViewModel private lateinit var underTest: KeyguardBottomAreaViewModel
private lateinit var repository: FakeKeyguardRepository private lateinit var repository: FakeKeyguardRepository
private lateinit var registry: FakeKeyguardQuickAffordanceRegistry private lateinit var registry: FakeKeyguardQuickAffordanceRegistry
private lateinit var isDozingUseCase: ObserveIsDozingUseCase
private lateinit var launchQuickAffordanceUseCase: FakeLaunchKeyguardQuickAffordanceUseCase
private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig private lateinit var homeControlsQuickAffordanceConfig: FakeKeyguardQuickAffordanceConfig
private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig private lateinit var quickAccessWalletAffordanceConfig: FakeKeyguardQuickAffordanceConfig
private lateinit var qrCodeScannerAffordanceConfig: FakeKeyguardQuickAffordanceConfig private lateinit var qrCodeScannerAffordanceConfig: FakeKeyguardQuickAffordanceConfig
private lateinit var observeQuickAffordanceUseCase: FakeObserveKeyguardQuickAffordanceUseCase
@Before @Before
fun setUp() { fun setUp() {
@@ -94,57 +95,31 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
), ),
) )
repository = FakeKeyguardRepository() repository = FakeKeyguardRepository()
isDozingUseCase =
ObserveIsDozingUseCase(
repository = repository,
)
launchQuickAffordanceUseCase = FakeLaunchKeyguardQuickAffordanceUseCase()
observeQuickAffordanceUseCase = FakeObserveKeyguardQuickAffordanceUseCase()
val keyguardInteractor = KeyguardInteractor(repository = repository)
whenever(userTracker.userHandle).thenReturn(mock())
whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
.thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
underTest = underTest =
KeyguardBottomAreaViewModel( KeyguardBottomAreaViewModel(
observeQuickAffordanceUseCase = observeQuickAffordanceUseCase, keyguardInteractor = keyguardInteractor,
onQuickAffordanceClickedUseCase = quickAffordanceInteractor =
OnKeyguardQuickAffordanceClickedUseCase( KeyguardQuickAffordanceInteractor(
registry = keyguardInteractor = keyguardInteractor,
FakeKeyguardQuickAffordanceRegistry( registry = registry,
mapOf( lockPatternUtils = lockPatternUtils,
KeyguardQuickAffordancePosition.BOTTOM_START to keyguardStateController = keyguardStateController,
listOf( userTracker = userTracker,
homeControlsQuickAffordanceConfig, activityStarter = activityStarter,
),
KeyguardQuickAffordancePosition.BOTTOM_END to
listOf(
quickAccessWalletAffordanceConfig,
qrCodeScannerAffordanceConfig,
),
),
),
launchAffordanceUseCase = launchQuickAffordanceUseCase,
),
observeBottomAreaAlphaUseCase =
ObserveBottomAreaAlphaUseCase(
repository = repository,
),
observeIsDozingUseCase = isDozingUseCase,
observeAnimateBottomAreaTransitionsUseCase =
ObserveAnimateBottomAreaTransitionsUseCase(
repository = repository,
),
observeDozeAmountUseCase =
ObserveDozeAmountUseCase(
repository = repository,
),
observeClockPositionUseCase =
ObserveClockPositionUseCase(
repository = repository,
), ),
bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
burnInHelperWrapper = burnInHelperWrapper, burnInHelperWrapper = burnInHelperWrapper,
) )
} }
@Test @Test
fun `startButton - present - visible model - starts activity on click`() = runBlockingTest { fun `startButton - present - visible model - starts activity on click`() = runBlockingTest {
repository.setKeyguardShowing(true)
var latest: KeyguardQuickAffordanceViewModel? = null var latest: KeyguardQuickAffordanceViewModel? = null
val job = underTest.startButton.onEach { latest = it }.launchIn(this) val job = underTest.startButton.onEach { latest = it }.launchIn(this)
@@ -171,6 +146,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
@Test @Test
fun `endButton - present - visible model - do nothing on click`() = runBlockingTest { fun `endButton - present - visible model - do nothing on click`() = runBlockingTest {
repository.setKeyguardShowing(true)
var latest: KeyguardQuickAffordanceViewModel? = null var latest: KeyguardQuickAffordanceViewModel? = null
val job = underTest.endButton.onEach { latest = it }.launchIn(this) val job = underTest.endButton.onEach { latest = it }.launchIn(this)
@@ -357,7 +333,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
private suspend fun setUpQuickAffordanceModel( private suspend fun setUpQuickAffordanceModel(
position: KeyguardQuickAffordancePosition, position: KeyguardQuickAffordancePosition,
testConfig: TestConfig, testConfig: TestConfig,
): KClass<*> { ): KClass<out FakeKeyguardQuickAffordanceConfig> {
val config = val config =
when (position) { when (position) {
KeyguardQuickAffordancePosition.BOTTOM_START -> homeControlsQuickAffordanceConfig KeyguardQuickAffordancePosition.BOTTOM_START -> homeControlsQuickAffordanceConfig
@@ -381,20 +357,13 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
KeyguardQuickAffordanceConfig.State.Hidden KeyguardQuickAffordanceConfig.State.Hidden
} }
config.setState(state) config.setState(state)
return config::class
val configKey = config::class
observeQuickAffordanceUseCase.setModel(
position,
KeyguardQuickAffordanceModel.from(state, configKey)
)
return configKey
} }
private fun assertQuickAffordanceViewModel( private fun assertQuickAffordanceViewModel(
viewModel: KeyguardQuickAffordanceViewModel?, viewModel: KeyguardQuickAffordanceViewModel?,
testConfig: TestConfig, testConfig: TestConfig,
configKey: KClass<*>, configKey: KClass<out FakeKeyguardQuickAffordanceConfig>,
) { ) {
checkNotNull(viewModel) checkNotNull(viewModel)
assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible) assertThat(viewModel.isVisible).isEqualTo(testConfig.isVisible)
@@ -406,19 +375,11 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
animationController = animationController, animationController = animationController,
) )
) )
testConfig.intent?.let { intent -> if (testConfig.intent != null) {
assertThat(launchQuickAffordanceUseCase.invocations) assertThat(Mockito.mockingDetails(activityStarter).invocations).hasSize(1)
.isEqualTo( } else {
listOf( verifyZeroInteractions(activityStarter)
FakeLaunchKeyguardQuickAffordanceUseCase.Invocation(
intent = intent,
canShowWhileLocked = testConfig.canShowWhileLocked,
animationController = animationController,
)
)
)
} }
?: run { assertThat(launchQuickAffordanceUseCase.invocations).isEmpty() }
} else { } else {
assertThat(viewModel.isVisible).isFalse() assertThat(viewModel.isVisible).isFalse()
} }

View File

@@ -98,9 +98,7 @@ import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentHostManager; import com.android.systemui.fragments.FragmentHostManager;
import com.android.systemui.fragments.FragmentService; import com.android.systemui.fragments.FragmentService;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.keyguard.domain.usecase.SetClockPositionUseCase; import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAlphaUseCase;
import com.android.systemui.keyguard.domain.usecase.SetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel; import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
import com.android.systemui.media.KeyguardMediaController; import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.media.MediaDataManager; import com.android.systemui.media.MediaDataManager;
@@ -379,10 +377,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Mock @Mock
private ViewTreeObserver mViewTreeObserver; private ViewTreeObserver mViewTreeObserver;
@Mock private KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel; @Mock private KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
@Mock private SetClockPositionUseCase mSetClockPositionUseCase; @Mock private KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
@Mock private SetKeyguardBottomAreaAlphaUseCase mSetKeyguardBottomAreaAlphaUseCase;
@Mock private SetKeyguardBottomAreaAnimateDozingTransitionsUseCase
mSetKeyguardBottomAreaAnimateDozingTransitionsUseCase;
private NotificationPanelViewController.PanelEventsEmitter mPanelEventsEmitter; private NotificationPanelViewController.PanelEventsEmitter mPanelEventsEmitter;
private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty(); private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
private SysuiStatusBarStateController mStatusBarStateController; private SysuiStatusBarStateController mStatusBarStateController;
@@ -577,9 +572,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mSystemClock, mSystemClock,
mock(CameraGestureHelper.class), mock(CameraGestureHelper.class),
() -> mKeyguardBottomAreaViewModel, () -> mKeyguardBottomAreaViewModel,
() -> mSetClockPositionUseCase, () -> mKeyguardBottomAreaInteractor);
() -> mSetKeyguardBottomAreaAlphaUseCase,
() -> mSetKeyguardBottomAreaAnimateDozingTransitionsUseCase);
mNotificationPanelViewController.initDependencies( mNotificationPanelViewController.initDependencies(
mCentralSurfaces, mCentralSurfaces,
() -> {}, () -> {},