Merge "KeyguardBottomAreaView new impl. - data layer" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
d3a180b0fc
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* 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.coroutine
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import kotlinx.coroutines.channels.SendChannel
|
||||||
|
import kotlinx.coroutines.channels.onFailure
|
||||||
|
|
||||||
|
object ChannelExt {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience wrapper around [SendChannel.trySend] that also logs on failure. This is the
|
||||||
|
* equivalent of calling:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* sendChannel.trySend(element).onFailure {
|
||||||
|
* Log.e(
|
||||||
|
* loggingTag,
|
||||||
|
* "Failed to send $elementDescription" +
|
||||||
|
* " - downstream canceled or failed.",
|
||||||
|
* it,
|
||||||
|
* )
|
||||||
|
*}
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
fun <T> SendChannel<T>.trySendWithFailureLogging(
|
||||||
|
element: T,
|
||||||
|
loggingTag: String,
|
||||||
|
elementDescription: String = "updated state",
|
||||||
|
) {
|
||||||
|
trySend(element).onFailure {
|
||||||
|
Log.e(
|
||||||
|
loggingTag,
|
||||||
|
"Failed to send $elementDescription - downstream canceled or failed.",
|
||||||
|
it,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* 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.coroutine
|
||||||
|
|
||||||
|
import kotlin.experimental.ExperimentalTypeInference
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.channels.Channel
|
||||||
|
import kotlinx.coroutines.channels.ProducerScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.buffer
|
||||||
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
|
|
||||||
|
object ConflatedCallbackFlow {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A [callbackFlow] that uses a buffer [Channel] that is "conflated" meaning that, if
|
||||||
|
* backpressure occurs (if the producer that emits new values into the flow is faster than the
|
||||||
|
* consumer(s) of the values in the flow), the values are buffered and, if the buffer fills up,
|
||||||
|
* we drop the oldest values automatically instead of suspending the producer.
|
||||||
|
*/
|
||||||
|
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
|
||||||
|
@OptIn(ExperimentalTypeInference::class, ExperimentalCoroutinesApi::class)
|
||||||
|
fun <T> conflatedCallbackFlow(
|
||||||
|
@BuilderInference block: suspend ProducerScope<T>.() -> Unit,
|
||||||
|
): Flow<T> = callbackFlow(block).buffer(capacity = Channel.CONFLATED)
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* 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.data.model
|
||||||
|
|
||||||
|
/** Models a two-dimensional position */
|
||||||
|
data class Position(
|
||||||
|
val x: Int,
|
||||||
|
val y: Int,
|
||||||
|
)
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* 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.containeddrawable
|
||||||
|
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
|
||||||
|
/** Convenience container for [Drawable] or a way to load it later. */
|
||||||
|
sealed class ContainedDrawable {
|
||||||
|
data class WithDrawable(val drawable: Drawable) : ContainedDrawable()
|
||||||
|
data class WithResource(@DrawableRes val resourceId: Int) : ContainedDrawable()
|
||||||
|
}
|
||||||
@@ -21,20 +21,22 @@ import android.content.Context
|
|||||||
import android.database.ContentObserver
|
import android.database.ContentObserver
|
||||||
import android.os.UserHandle
|
import android.os.UserHandle
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
|
import com.android.internal.widget.LockPatternUtils
|
||||||
|
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
|
||||||
import com.android.systemui.controls.controller.ControlsController
|
import com.android.systemui.controls.controller.ControlsController
|
||||||
|
import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
|
||||||
|
import com.android.systemui.controls.controller.ControlsTileResourceConfigurationImpl
|
||||||
import com.android.systemui.controls.management.ControlsListingController
|
import com.android.systemui.controls.management.ControlsListingController
|
||||||
import com.android.systemui.controls.ui.ControlsUiController
|
import com.android.systemui.controls.ui.ControlsUiController
|
||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
import com.android.systemui.settings.UserTracker
|
import com.android.systemui.settings.UserTracker
|
||||||
import com.android.systemui.statusbar.policy.KeyguardStateController
|
import com.android.systemui.statusbar.policy.KeyguardStateController
|
||||||
import com.android.systemui.util.settings.SecureSettings
|
import com.android.systemui.util.settings.SecureSettings
|
||||||
import com.android.internal.widget.LockPatternUtils
|
|
||||||
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
|
|
||||||
import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
|
|
||||||
import com.android.systemui.controls.controller.ControlsTileResourceConfigurationImpl
|
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import java.util.Optional
|
import java.util.Optional
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pseudo-component to inject into classes outside `com.android.systemui.controls`.
|
* Pseudo-component to inject into classes outside `com.android.systemui.controls`.
|
||||||
@@ -59,7 +61,8 @@ class ControlsComponent @Inject constructor(
|
|||||||
private val contentResolver: ContentResolver
|
private val contentResolver: ContentResolver
|
||||||
get() = context.contentResolver
|
get() = context.contentResolver
|
||||||
|
|
||||||
private var canShowWhileLockedSetting = false
|
private val _canShowWhileLockedSetting = MutableStateFlow(false)
|
||||||
|
val canShowWhileLockedSetting = _canShowWhileLockedSetting.asStateFlow()
|
||||||
|
|
||||||
private val controlsTileResourceConfiguration: ControlsTileResourceConfiguration =
|
private val controlsTileResourceConfiguration: ControlsTileResourceConfiguration =
|
||||||
optionalControlsTileResourceConfiguration.orElse(
|
optionalControlsTileResourceConfiguration.orElse(
|
||||||
@@ -117,7 +120,7 @@ class ControlsComponent @Inject constructor(
|
|||||||
== STRONG_AUTH_REQUIRED_AFTER_BOOT) {
|
== STRONG_AUTH_REQUIRED_AFTER_BOOT) {
|
||||||
return Visibility.AVAILABLE_AFTER_UNLOCK
|
return Visibility.AVAILABLE_AFTER_UNLOCK
|
||||||
}
|
}
|
||||||
if (!canShowWhileLockedSetting && !keyguardStateController.isUnlocked()) {
|
if (!canShowWhileLockedSetting.value && !keyguardStateController.isUnlocked()) {
|
||||||
return Visibility.AVAILABLE_AFTER_UNLOCK
|
return Visibility.AVAILABLE_AFTER_UNLOCK
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +128,7 @@ class ControlsComponent @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateShowWhileLocked() {
|
private fun updateShowWhileLocked() {
|
||||||
canShowWhileLockedSetting = secureSettings.getIntForUser(
|
_canShowWhileLockedSetting.value = secureSettings.getIntForUser(
|
||||||
Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 0, UserHandle.USER_CURRENT) != 0
|
Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 0, UserHandle.USER_CURRENT) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import com.android.systemui.dump.DumpManager;
|
|||||||
import com.android.systemui.keyguard.DismissCallbackRegistry;
|
import com.android.systemui.keyguard.DismissCallbackRegistry;
|
||||||
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
|
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.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;
|
||||||
@@ -66,7 +67,10 @@ import dagger.Provides;
|
|||||||
KeyguardStatusBarViewComponent.class,
|
KeyguardStatusBarViewComponent.class,
|
||||||
KeyguardStatusViewComponent.class,
|
KeyguardStatusViewComponent.class,
|
||||||
KeyguardUserSwitcherComponent.class},
|
KeyguardUserSwitcherComponent.class},
|
||||||
includes = {FalsingModule.class})
|
includes = {
|
||||||
|
FalsingModule.class,
|
||||||
|
KeyguardRepositoryModule.class,
|
||||||
|
})
|
||||||
public class KeyguardModule {
|
public class KeyguardModule {
|
||||||
/**
|
/**
|
||||||
* Provides our instance of KeyguardViewMediator which is considered optional.
|
* Provides our instance of KeyguardViewMediator which is considered optional.
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* 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.quickaffordance
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
import com.android.systemui.animation.ActivityLaunchAnimator
|
||||||
|
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
|
||||||
|
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
||||||
|
import com.android.systemui.containeddrawable.ContainedDrawable
|
||||||
|
import com.android.systemui.controls.ControlsServiceInfo
|
||||||
|
import com.android.systemui.controls.controller.StructureInfo
|
||||||
|
import com.android.systemui.controls.dagger.ControlsComponent
|
||||||
|
import com.android.systemui.controls.management.ControlsListingController
|
||||||
|
import com.android.systemui.controls.ui.ControlsActivity
|
||||||
|
import com.android.systemui.controls.ui.ControlsUiController
|
||||||
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
|
import com.android.systemui.dagger.qualifiers.Application
|
||||||
|
import com.android.systemui.util.kotlin.getOrNull
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
|
||||||
|
/** Home controls quick affordance data source. */
|
||||||
|
@SysUISingleton
|
||||||
|
class HomeControlsKeyguardQuickAffordanceConfig
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
|
@Application context: Context,
|
||||||
|
private val component: ControlsComponent,
|
||||||
|
) : KeyguardQuickAffordanceConfig {
|
||||||
|
|
||||||
|
private val appContext = context.applicationContext
|
||||||
|
|
||||||
|
override val state: Flow<KeyguardQuickAffordanceConfig.State> =
|
||||||
|
stateInternal(component.getControlsListingController().getOrNull())
|
||||||
|
|
||||||
|
override fun onQuickAffordanceClicked(
|
||||||
|
animationController: ActivityLaunchAnimator.Controller?,
|
||||||
|
): KeyguardQuickAffordanceConfig.OnClickedResult {
|
||||||
|
return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
|
||||||
|
intent =
|
||||||
|
Intent(appContext, ControlsActivity::class.java)
|
||||||
|
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
.putExtra(
|
||||||
|
ControlsUiController.EXTRA_ANIMATE,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
canShowWhileLocked = component.canShowWhileLockedSetting.value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stateInternal(
|
||||||
|
listingController: ControlsListingController?,
|
||||||
|
): Flow<KeyguardQuickAffordanceConfig.State> {
|
||||||
|
if (listingController == null) {
|
||||||
|
return flowOf(KeyguardQuickAffordanceConfig.State.Hidden)
|
||||||
|
}
|
||||||
|
|
||||||
|
return conflatedCallbackFlow {
|
||||||
|
val callback =
|
||||||
|
object : ControlsListingController.ControlsListingCallback {
|
||||||
|
override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
|
||||||
|
val favorites: List<StructureInfo>? =
|
||||||
|
component.getControlsController().getOrNull()?.getFavorites()
|
||||||
|
|
||||||
|
trySendWithFailureLogging(
|
||||||
|
state(
|
||||||
|
isFeatureEnabled = component.isEnabled(),
|
||||||
|
hasFavorites = favorites?.isNotEmpty() == true,
|
||||||
|
hasServiceInfos = serviceInfos.isNotEmpty(),
|
||||||
|
iconResourceId = component.getTileImageId(),
|
||||||
|
),
|
||||||
|
TAG,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listingController.addCallback(callback)
|
||||||
|
|
||||||
|
awaitClose { listingController.removeCallback(callback) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun state(
|
||||||
|
isFeatureEnabled: Boolean,
|
||||||
|
hasFavorites: Boolean,
|
||||||
|
hasServiceInfos: Boolean,
|
||||||
|
@DrawableRes iconResourceId: Int?,
|
||||||
|
): KeyguardQuickAffordanceConfig.State {
|
||||||
|
return if (isFeatureEnabled && hasFavorites && hasServiceInfos && iconResourceId != null) {
|
||||||
|
KeyguardQuickAffordanceConfig.State.Visible(
|
||||||
|
icon = ContainedDrawable.WithResource(iconResourceId),
|
||||||
|
contentDescriptionResourceId = component.getTileTitleId(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
KeyguardQuickAffordanceConfig.State.Hidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "HomeControlsKeyguardQuickAffordanceConfig"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* 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.quickaffordance
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import com.android.systemui.animation.ActivityLaunchAnimator
|
||||||
|
import com.android.systemui.containeddrawable.ContainedDrawable
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
/** Defines interface that can act as data source for a single quick affordance model. */
|
||||||
|
interface KeyguardQuickAffordanceConfig {
|
||||||
|
|
||||||
|
val state: Flow<State>
|
||||||
|
|
||||||
|
fun onQuickAffordanceClicked(
|
||||||
|
animationController: ActivityLaunchAnimator.Controller?
|
||||||
|
): OnClickedResult
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulates the state of a "quick affordance" in the keyguard bottom area (for example, a
|
||||||
|
* button on the lock-screen).
|
||||||
|
*/
|
||||||
|
sealed class State {
|
||||||
|
|
||||||
|
/** No affordance should show up. */
|
||||||
|
object Hidden : State()
|
||||||
|
|
||||||
|
/** An affordance is visible. */
|
||||||
|
data class Visible(
|
||||||
|
/** An icon for the affordance. */
|
||||||
|
val icon: ContainedDrawable,
|
||||||
|
/**
|
||||||
|
* Resource ID for a string to use for the accessibility content description text of the
|
||||||
|
* affordance.
|
||||||
|
*/
|
||||||
|
@StringRes val contentDescriptionResourceId: Int,
|
||||||
|
) : State()
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class OnClickedResult {
|
||||||
|
/**
|
||||||
|
* Returning this as a result from the [onQuickAffordanceClicked] method means that the
|
||||||
|
* implementation has taken care of the click, the system will do nothing.
|
||||||
|
*/
|
||||||
|
object Handled : OnClickedResult()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returning this as a result from the [onQuickAffordanceClicked] method means that the
|
||||||
|
* implementation has _not_ taken care of the click and the system should start an activity
|
||||||
|
* using the given [Intent].
|
||||||
|
*/
|
||||||
|
data class StartActivity(
|
||||||
|
val intent: Intent,
|
||||||
|
val canShowWhileLocked: Boolean,
|
||||||
|
) : OnClickedResult()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* 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.quickaffordance
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.animation.ActivityLaunchAnimator
|
||||||
|
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
|
||||||
|
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
||||||
|
import com.android.systemui.containeddrawable.ContainedDrawable
|
||||||
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
|
import com.android.systemui.dagger.qualifiers.Application
|
||||||
|
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
/** QR code scanner quick affordance data source. */
|
||||||
|
@SysUISingleton
|
||||||
|
class QrCodeScannerKeyguardQuickAffordanceConfig
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
|
@Application context: Context,
|
||||||
|
private val controller: QRCodeScannerController,
|
||||||
|
) : KeyguardQuickAffordanceConfig {
|
||||||
|
|
||||||
|
private val appContext = context.applicationContext
|
||||||
|
|
||||||
|
override val state: Flow<KeyguardQuickAffordanceConfig.State> = conflatedCallbackFlow {
|
||||||
|
val callback =
|
||||||
|
object : QRCodeScannerController.Callback {
|
||||||
|
override fun onQRCodeScannerActivityChanged() {
|
||||||
|
trySendWithFailureLogging(state(), TAG)
|
||||||
|
}
|
||||||
|
override fun onQRCodeScannerPreferenceChanged() {
|
||||||
|
trySendWithFailureLogging(state(), TAG)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.addCallback(callback)
|
||||||
|
controller.registerQRCodeScannerChangeObservers(
|
||||||
|
QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
|
||||||
|
QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
|
||||||
|
)
|
||||||
|
// Registering does not push an initial update.
|
||||||
|
trySendWithFailureLogging(state(), "initial state", TAG)
|
||||||
|
|
||||||
|
awaitClose {
|
||||||
|
controller.unregisterQRCodeScannerChangeObservers(
|
||||||
|
QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
|
||||||
|
QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
|
||||||
|
)
|
||||||
|
controller.removeCallback(callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onQuickAffordanceClicked(
|
||||||
|
animationController: ActivityLaunchAnimator.Controller?,
|
||||||
|
): KeyguardQuickAffordanceConfig.OnClickedResult {
|
||||||
|
return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
|
||||||
|
intent = controller.intent,
|
||||||
|
canShowWhileLocked = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun state(): KeyguardQuickAffordanceConfig.State {
|
||||||
|
return if (controller.isEnabledForLockScreenButton) {
|
||||||
|
KeyguardQuickAffordanceConfig.State.Visible(
|
||||||
|
icon = ContainedDrawable.WithResource(R.drawable.ic_qr_code_scanner),
|
||||||
|
contentDescriptionResourceId = R.string.accessibility_qr_code_scanner_button,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
KeyguardQuickAffordanceConfig.State.Hidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "QrCodeScannerKeyguardQuickAffordanceConfig"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
* 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.quickaffordance
|
||||||
|
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.service.quickaccesswallet.GetWalletCardsError
|
||||||
|
import android.service.quickaccesswallet.GetWalletCardsResponse
|
||||||
|
import android.service.quickaccesswallet.QuickAccessWalletClient
|
||||||
|
import android.util.Log
|
||||||
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.animation.ActivityLaunchAnimator
|
||||||
|
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
|
||||||
|
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
||||||
|
import com.android.systemui.containeddrawable.ContainedDrawable
|
||||||
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
|
import com.android.systemui.plugins.ActivityStarter
|
||||||
|
import com.android.systemui.statusbar.policy.KeyguardStateController
|
||||||
|
import com.android.systemui.statusbar.policy.KeyguardStateControllerExt.isKeyguardShowing
|
||||||
|
import com.android.systemui.wallet.controller.QuickAccessWalletController
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
|
||||||
|
/** Quick access wallet quick affordance data source. */
|
||||||
|
@SysUISingleton
|
||||||
|
class QuickAccessWalletKeyguardQuickAffordanceConfig
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
|
private val keyguardStateController: KeyguardStateController,
|
||||||
|
private val walletController: QuickAccessWalletController,
|
||||||
|
private val activityStarter: ActivityStarter,
|
||||||
|
) : KeyguardQuickAffordanceConfig {
|
||||||
|
|
||||||
|
override val state: Flow<KeyguardQuickAffordanceConfig.State> =
|
||||||
|
keyguardStateController
|
||||||
|
.isKeyguardShowing(TAG)
|
||||||
|
.flatMapLatest { isKeyguardShowing ->
|
||||||
|
stateInternal(isKeyguardShowing)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onQuickAffordanceClicked(
|
||||||
|
animationController: ActivityLaunchAnimator.Controller?,
|
||||||
|
): KeyguardQuickAffordanceConfig.OnClickedResult {
|
||||||
|
walletController.startQuickAccessUiIntent(
|
||||||
|
activityStarter,
|
||||||
|
animationController,
|
||||||
|
/* hasCard= */ true,
|
||||||
|
)
|
||||||
|
return KeyguardQuickAffordanceConfig.OnClickedResult.Handled
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stateInternal(
|
||||||
|
isKeyguardShowing: Boolean
|
||||||
|
): Flow<KeyguardQuickAffordanceConfig.State> {
|
||||||
|
if (!isKeyguardShowing) {
|
||||||
|
return flowOf(KeyguardQuickAffordanceConfig.State.Hidden)
|
||||||
|
}
|
||||||
|
|
||||||
|
return conflatedCallbackFlow {
|
||||||
|
val callback =
|
||||||
|
object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
|
||||||
|
override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
|
||||||
|
trySendWithFailureLogging(
|
||||||
|
state(
|
||||||
|
isFeatureEnabled = walletController.isWalletEnabled,
|
||||||
|
hasCard = response?.walletCards?.isNotEmpty() == true,
|
||||||
|
tileIcon = walletController.walletClient.tileIcon,
|
||||||
|
),
|
||||||
|
TAG,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
|
||||||
|
Log.e(TAG, "Wallet card retrieval error, message: \"${error?.message}\"")
|
||||||
|
trySendWithFailureLogging(
|
||||||
|
KeyguardQuickAffordanceConfig.State.Hidden,
|
||||||
|
TAG,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
walletController.setupWalletChangeObservers(
|
||||||
|
callback,
|
||||||
|
QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
|
||||||
|
QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
|
||||||
|
)
|
||||||
|
walletController.updateWalletPreference()
|
||||||
|
walletController.queryWalletCards(callback)
|
||||||
|
|
||||||
|
awaitClose {
|
||||||
|
walletController.unregisterWalletChangeObservers(
|
||||||
|
QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
|
||||||
|
QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun state(
|
||||||
|
isFeatureEnabled: Boolean,
|
||||||
|
hasCard: Boolean,
|
||||||
|
tileIcon: Drawable?,
|
||||||
|
): KeyguardQuickAffordanceConfig.State {
|
||||||
|
return if (isFeatureEnabled && hasCard && tileIcon != null) {
|
||||||
|
KeyguardQuickAffordanceConfig.State.Visible(
|
||||||
|
icon = ContainedDrawable.WithDrawable(tileIcon),
|
||||||
|
contentDescriptionResourceId = R.string.accessibility_wallet_button,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
KeyguardQuickAffordanceConfig.State.Hidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "QuickAccessWalletKeyguardQuickAffordanceConfig"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* 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.config
|
||||||
|
|
||||||
|
import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
|
||||||
|
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
|
||||||
|
import com.android.systemui.keyguard.data.quickaffordance.QrCodeScannerKeyguardQuickAffordanceConfig
|
||||||
|
import com.android.systemui.keyguard.data.quickaffordance.QuickAccessWalletKeyguardQuickAffordanceConfig
|
||||||
|
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
/** Injectable provider of the positioning of the known quick affordance configs. */
|
||||||
|
interface KeyguardQuickAffordanceConfigs {
|
||||||
|
fun getAll(position: KeyguardQuickAffordancePosition): List<KeyguardQuickAffordanceConfig>
|
||||||
|
fun get(configClass: KClass<out KeyguardQuickAffordanceConfig>): KeyguardQuickAffordanceConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
class KeyguardQuickAffordanceConfigsImpl
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
|
homeControls: HomeControlsKeyguardQuickAffordanceConfig,
|
||||||
|
quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
|
||||||
|
qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
|
||||||
|
) : KeyguardQuickAffordanceConfigs {
|
||||||
|
private val configsByPosition =
|
||||||
|
mapOf(
|
||||||
|
KeyguardQuickAffordancePosition.BOTTOM_START to
|
||||||
|
listOf(
|
||||||
|
homeControls,
|
||||||
|
),
|
||||||
|
KeyguardQuickAffordancePosition.BOTTOM_END to
|
||||||
|
listOf(
|
||||||
|
quickAccessWallet,
|
||||||
|
qrCodeScanner,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
private val configByClass =
|
||||||
|
configsByPosition.values.flatten().associateBy { config -> config::class }
|
||||||
|
|
||||||
|
override fun getAll(
|
||||||
|
position: KeyguardQuickAffordancePosition,
|
||||||
|
): List<KeyguardQuickAffordanceConfig> {
|
||||||
|
return configsByPosition.getValue(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun get(
|
||||||
|
configClass: KClass<out KeyguardQuickAffordanceConfig>
|
||||||
|
): KeyguardQuickAffordanceConfig {
|
||||||
|
return configByClass.getValue(configClass)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.android.systemui.dagger.SysUISingleton
|
||||||
|
import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
|
||||||
|
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.State
|
||||||
|
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
|
||||||
|
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
|
||||||
|
/** Defines interface for classes that encapsulate quick affordance state for the keyguard. */
|
||||||
|
interface KeyguardQuickAffordanceRepository {
|
||||||
|
fun affordance(position: KeyguardQuickAffordancePosition): Flow<KeyguardQuickAffordanceModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Real implementation of [KeyguardQuickAffordanceRepository] */
|
||||||
|
@SysUISingleton
|
||||||
|
class KeyguardQuickAffordanceRepositoryImpl
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
|
private val configs: KeyguardQuickAffordanceConfigs,
|
||||||
|
) : KeyguardQuickAffordanceRepository {
|
||||||
|
|
||||||
|
/** Returns an observable for the quick affordance model in the given position. */
|
||||||
|
override fun affordance(
|
||||||
|
position: KeyguardQuickAffordancePosition
|
||||||
|
): Flow<KeyguardQuickAffordanceModel> {
|
||||||
|
val configs = configs.getAll(position)
|
||||||
|
return combine(configs.map { config -> config.state }) { states ->
|
||||||
|
val index = states.indexOfFirst { state -> state is State.Visible }
|
||||||
|
val visibleState =
|
||||||
|
if (index != -1) {
|
||||||
|
states[index] as State.Visible
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
if (visibleState != null) {
|
||||||
|
KeyguardQuickAffordanceModel.Visible(
|
||||||
|
configKey = configs[index]::class,
|
||||||
|
icon = visibleState.icon,
|
||||||
|
contentDescriptionResourceId = visibleState.contentDescriptionResourceId,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
KeyguardQuickAffordanceModel.Hidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
|
||||||
|
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
||||||
|
import com.android.systemui.common.data.model.Position
|
||||||
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
|
import com.android.systemui.plugins.statusbar.StatusBarStateController
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|
||||||
|
/** Defines interface for classes that encapsulate application state for the keyguard. */
|
||||||
|
interface KeyguardRepository {
|
||||||
|
/**
|
||||||
|
* Observable for whether the bottom area UI should animate the transition out of doze state.
|
||||||
|
*
|
||||||
|
* To learn more about doze state, please see [isDozing].
|
||||||
|
*/
|
||||||
|
val animateBottomAreaDozingTransitions: StateFlow<Boolean>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observable for the current amount of alpha that should be used for rendering the bottom area.
|
||||||
|
* UI.
|
||||||
|
*/
|
||||||
|
val bottomAreaAlpha: StateFlow<Float>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observable of the relative offset of the lock-screen clock from its natural position on the
|
||||||
|
* screen.
|
||||||
|
*/
|
||||||
|
val clockPosition: StateFlow<Position>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observable for whether we are in doze state.
|
||||||
|
*
|
||||||
|
* Doze state is the same as "Always on Display" or "AOD". It is the state that the device can
|
||||||
|
* enter to conserve battery when the device is locked and inactive.
|
||||||
|
*
|
||||||
|
* Note that it is possible for the system to be transitioning into doze while this flow still
|
||||||
|
* returns `false`. In order to account for that, observers should also use the [dozeAmount]
|
||||||
|
* flow to check if it's greater than `0`
|
||||||
|
*/
|
||||||
|
val isDozing: Flow<Boolean>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observable for the amount of doze we are currently in.
|
||||||
|
*
|
||||||
|
* While in doze state, this amount can change - driving a cycle of animations designed to avoid
|
||||||
|
* pixel burn-in, etc.
|
||||||
|
*
|
||||||
|
* Also note that the value here may be greater than `0` while [isDozing] is still `false`, this
|
||||||
|
* happens during an animation/transition into doze mode. An observer would be wise to account
|
||||||
|
* for both flows if needed.
|
||||||
|
*/
|
||||||
|
val dozeAmount: Flow<Float>
|
||||||
|
|
||||||
|
/** Sets whether the bottom area UI should animate the transition out of doze state. */
|
||||||
|
fun setAnimateDozingTransitions(animate: Boolean)
|
||||||
|
|
||||||
|
/** Sets the current amount of alpha that should be used for rendering the bottom area. */
|
||||||
|
fun setBottomAreaAlpha(alpha: Float)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the relative offset of the lock-screen clock from its natural position on the screen.
|
||||||
|
*/
|
||||||
|
fun setClockPosition(x: Int, y: Int)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Encapsulates application state for the keyguard. */
|
||||||
|
@SysUISingleton
|
||||||
|
class KeyguardRepositoryImpl
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
|
statusBarStateController: StatusBarStateController,
|
||||||
|
) : KeyguardRepository {
|
||||||
|
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
|
||||||
|
override val animateBottomAreaDozingTransitions =
|
||||||
|
_animateBottomAreaDozingTransitions.asStateFlow()
|
||||||
|
|
||||||
|
private val _bottomAreaAlpha = MutableStateFlow(1f)
|
||||||
|
override val bottomAreaAlpha = _bottomAreaAlpha.asStateFlow()
|
||||||
|
|
||||||
|
private val _clockPosition = MutableStateFlow(Position(0, 0))
|
||||||
|
override val clockPosition = _clockPosition.asStateFlow()
|
||||||
|
|
||||||
|
override val isDozing: Flow<Boolean> = conflatedCallbackFlow {
|
||||||
|
val callback =
|
||||||
|
object : StatusBarStateController.StateListener {
|
||||||
|
override fun onDozingChanged(isDozing: Boolean) {
|
||||||
|
trySendWithFailureLogging(isDozing, TAG, "updated isDozing")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
statusBarStateController.addCallback(callback)
|
||||||
|
trySendWithFailureLogging(statusBarStateController.isDozing, TAG, "initial isDozing")
|
||||||
|
|
||||||
|
awaitClose { statusBarStateController.removeCallback(callback) }
|
||||||
|
}
|
||||||
|
override val dozeAmount: Flow<Float> = conflatedCallbackFlow {
|
||||||
|
val callback =
|
||||||
|
object : StatusBarStateController.StateListener {
|
||||||
|
override fun onDozeAmountChanged(linear: Float, eased: Float) {
|
||||||
|
trySendWithFailureLogging(eased, TAG, "updated dozeAmount")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
statusBarStateController.addCallback(callback)
|
||||||
|
trySendWithFailureLogging(statusBarStateController.dozeAmount, TAG, "initial dozeAmount")
|
||||||
|
|
||||||
|
awaitClose { statusBarStateController.removeCallback(callback) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setAnimateDozingTransitions(animate: Boolean) {
|
||||||
|
_animateBottomAreaDozingTransitions.value = animate
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setBottomAreaAlpha(alpha: Float) {
|
||||||
|
_bottomAreaAlpha.value = alpha
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setClockPosition(x: Int, y: Int) {
|
||||||
|
_clockPosition.value = Position(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "KeyguardRepositoryImpl"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
|
||||||
|
import com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigsImpl
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
|
||||||
|
@Module
|
||||||
|
interface KeyguardRepositoryModule {
|
||||||
|
@Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
fun keyguardQuickAffordanceRepository(
|
||||||
|
impl: KeyguardQuickAffordanceRepositoryImpl
|
||||||
|
): KeyguardQuickAffordanceRepository
|
||||||
|
|
||||||
|
@Binds fun keyguardQuickAffordanceConfigs(
|
||||||
|
impl: KeyguardQuickAffordanceConfigsImpl
|
||||||
|
): KeyguardQuickAffordanceConfigs
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* 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 androidx.annotation.StringRes
|
||||||
|
import com.android.systemui.containeddrawable.ContainedDrawable
|
||||||
|
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Models a "quick affordance" in the keyguard bottom area (for example, a button on the
|
||||||
|
* lock-screen).
|
||||||
|
*/
|
||||||
|
sealed class KeyguardQuickAffordanceModel {
|
||||||
|
|
||||||
|
/** No affordance should show up. */
|
||||||
|
object Hidden : KeyguardQuickAffordanceModel()
|
||||||
|
|
||||||
|
/** A affordance is visible. */
|
||||||
|
data class Visible(
|
||||||
|
/** Identifier for the affordance this is modeling. */
|
||||||
|
val configKey: KClass<out KeyguardQuickAffordanceConfig>,
|
||||||
|
/** An icon for the affordance. */
|
||||||
|
val icon: ContainedDrawable,
|
||||||
|
/**
|
||||||
|
* Resource ID for a string to use for the accessibility content description text of the
|
||||||
|
* affordance.
|
||||||
|
*/
|
||||||
|
@StringRes val contentDescriptionResourceId: Int,
|
||||||
|
) : KeyguardQuickAffordanceModel()
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
/** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
|
||||||
|
enum class KeyguardQuickAffordancePosition {
|
||||||
|
BOTTOM_START,
|
||||||
|
BOTTOM_END,
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* 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.statusbar.policy
|
||||||
|
|
||||||
|
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
|
||||||
|
import com.android.systemui.common.coroutine.ConflatedCallbackFlow
|
||||||
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
object KeyguardStateControllerExt {
|
||||||
|
/**
|
||||||
|
* Returns an observable for whether the keyguard is currently shown or not.
|
||||||
|
*/
|
||||||
|
fun KeyguardStateController.isKeyguardShowing(loggingTag: String): Flow<Boolean> {
|
||||||
|
return ConflatedCallbackFlow.conflatedCallbackFlow {
|
||||||
|
val callback =
|
||||||
|
object : KeyguardStateController.Callback {
|
||||||
|
override fun onKeyguardShowingChanged() {
|
||||||
|
trySendWithFailureLogging(
|
||||||
|
isShowing, loggingTag, "updated isKeyguardShowing")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addCallback(callback)
|
||||||
|
// Adding the callback does not send an initial update.
|
||||||
|
trySendWithFailureLogging(isShowing, loggingTag, "initial isKeyguardShowing")
|
||||||
|
|
||||||
|
awaitClose { removeCallback(callback) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.android.systemui.animation.ActivityLaunchAnimator
|
||||||
|
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
|
||||||
|
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.yield
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fake implementation of a quick affordance data source.
|
||||||
|
*
|
||||||
|
* This class is abstract to force tests to provide extensions of it as the system that references
|
||||||
|
* these configs uses each implementation's class type to refer to them.
|
||||||
|
*/
|
||||||
|
abstract class FakeKeyguardQuickAffordanceConfig : KeyguardQuickAffordanceConfig {
|
||||||
|
|
||||||
|
private val _onClickedInvocations = mutableListOf<ActivityLaunchAnimator.Controller?>()
|
||||||
|
val onClickedInvocations: List<ActivityLaunchAnimator.Controller?> = _onClickedInvocations
|
||||||
|
|
||||||
|
var onClickedResult: OnClickedResult = OnClickedResult.Handled
|
||||||
|
|
||||||
|
private val _state =
|
||||||
|
MutableStateFlow<KeyguardQuickAffordanceConfig.State>(
|
||||||
|
KeyguardQuickAffordanceConfig.State.Hidden
|
||||||
|
)
|
||||||
|
override val state: Flow<KeyguardQuickAffordanceConfig.State> = _state
|
||||||
|
|
||||||
|
override fun onQuickAffordanceClicked(
|
||||||
|
animationController: ActivityLaunchAnimator.Controller?,
|
||||||
|
): OnClickedResult {
|
||||||
|
_onClickedInvocations.add(animationController)
|
||||||
|
return onClickedResult
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setState(state: KeyguardQuickAffordanceConfig.State) {
|
||||||
|
_state.value = state
|
||||||
|
// 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.android.systemui.keyguard.data.config.KeyguardQuickAffordanceConfigs
|
||||||
|
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
|
||||||
|
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
/** Fake implementation of [KeyguardQuickAffordanceConfigs], for tests. */
|
||||||
|
class FakeKeyguardQuickAffordanceConfigs(
|
||||||
|
private val configsByPosition:
|
||||||
|
Map<KeyguardQuickAffordancePosition, List<KeyguardQuickAffordanceConfig>>,
|
||||||
|
) : KeyguardQuickAffordanceConfigs {
|
||||||
|
|
||||||
|
override fun getAll(
|
||||||
|
position: KeyguardQuickAffordancePosition
|
||||||
|
): List<KeyguardQuickAffordanceConfig> {
|
||||||
|
return configsByPosition.getValue(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun get(
|
||||||
|
configClass: KClass<out KeyguardQuickAffordanceConfig>
|
||||||
|
): KeyguardQuickAffordanceConfig {
|
||||||
|
return configsByPosition.values
|
||||||
|
.flatten()
|
||||||
|
.associateBy { config -> config::class }
|
||||||
|
.getValue(configClass)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
|
||||||
|
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.yield
|
||||||
|
|
||||||
|
/** Fake implementation of [KeyguardQuickAffordanceRepository], for tests. */
|
||||||
|
class FakeKeyguardQuickAffordanceRepository : KeyguardQuickAffordanceRepository {
|
||||||
|
|
||||||
|
private val modelByPosition =
|
||||||
|
mutableMapOf<
|
||||||
|
KeyguardQuickAffordancePosition, MutableStateFlow<KeyguardQuickAffordanceModel>>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
KeyguardQuickAffordancePosition.values().forEach { value ->
|
||||||
|
modelByPosition[value] = MutableStateFlow(KeyguardQuickAffordanceModel.Hidden)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun affordance(
|
||||||
|
position: KeyguardQuickAffordancePosition
|
||||||
|
): Flow<KeyguardQuickAffordanceModel> {
|
||||||
|
return modelByPosition.getValue(position)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setModel(
|
||||||
|
position: KeyguardQuickAffordancePosition,
|
||||||
|
model: KeyguardQuickAffordanceModel
|
||||||
|
) {
|
||||||
|
modelByPosition.getValue(position).value = model
|
||||||
|
// 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.android.systemui.common.data.model.Position
|
||||||
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
/** Fake implementation of [KeyguardRepository] */
|
||||||
|
class FakeKeyguardRepository : KeyguardRepository {
|
||||||
|
|
||||||
|
private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
|
||||||
|
override val animateBottomAreaDozingTransitions: StateFlow<Boolean> =
|
||||||
|
_animateBottomAreaDozingTransitions
|
||||||
|
|
||||||
|
private val _bottomAreaAlpha = MutableStateFlow(1f)
|
||||||
|
override val bottomAreaAlpha: StateFlow<Float> = _bottomAreaAlpha
|
||||||
|
|
||||||
|
private val _clockPosition = MutableStateFlow(Position(0, 0))
|
||||||
|
override val clockPosition: StateFlow<Position> = _clockPosition
|
||||||
|
|
||||||
|
private val _isDozing =
|
||||||
|
MutableSharedFlow<Boolean>(
|
||||||
|
replay = 1,
|
||||||
|
onBufferOverflow = BufferOverflow.DROP_OLDEST,
|
||||||
|
)
|
||||||
|
override val isDozing: Flow<Boolean> = _isDozing
|
||||||
|
|
||||||
|
private val _dozeAmount =
|
||||||
|
MutableSharedFlow<Float>(
|
||||||
|
replay = 1,
|
||||||
|
onBufferOverflow = BufferOverflow.DROP_OLDEST,
|
||||||
|
)
|
||||||
|
override val dozeAmount: Flow<Float> = _dozeAmount
|
||||||
|
|
||||||
|
init {
|
||||||
|
setDozeAmount(0f)
|
||||||
|
setDozing(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setAnimateDozingTransitions(animate: Boolean) {
|
||||||
|
_animateBottomAreaDozingTransitions.tryEmit(animate)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setBottomAreaAlpha(alpha: Float) {
|
||||||
|
_bottomAreaAlpha.value = alpha
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setClockPosition(x: Int, y: Int) {
|
||||||
|
_clockPosition.value = Position(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDozing(isDozing: Boolean) {
|
||||||
|
_isDozing.tryEmit(isDozing)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDozeAmount(dozeAmount: Float) {
|
||||||
|
_dozeAmount.tryEmit(dozeAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* 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 androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.controls.controller.ControlsController
|
||||||
|
import com.android.systemui.controls.dagger.ControlsComponent
|
||||||
|
import com.android.systemui.controls.management.ControlsListingController
|
||||||
|
import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
|
||||||
|
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
|
||||||
|
import com.android.systemui.util.mockito.mock
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import java.util.Optional
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
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.ArgumentCaptor
|
||||||
|
import org.mockito.Captor
|
||||||
|
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 HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@Parameters(
|
||||||
|
name =
|
||||||
|
"feature enabled = {0}, has favorites = {1}, has service infos = {2} - expected" +
|
||||||
|
" visible = {3}"
|
||||||
|
)
|
||||||
|
@JvmStatic
|
||||||
|
fun data() =
|
||||||
|
(0 until 8)
|
||||||
|
.map { combination ->
|
||||||
|
arrayOf(
|
||||||
|
/* isFeatureEnabled= */ combination and 0b100 != 0,
|
||||||
|
/* hasFavorites= */ combination and 0b010 != 0,
|
||||||
|
/* hasServiceInfos= */ combination and 0b001 != 0,
|
||||||
|
/* isVisible= */ combination == 0b111,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mock private lateinit var component: ControlsComponent
|
||||||
|
@Mock private lateinit var controlsController: ControlsController
|
||||||
|
@Mock private lateinit var controlsListingController: ControlsListingController
|
||||||
|
@Captor
|
||||||
|
private lateinit var callbackCaptor:
|
||||||
|
ArgumentCaptor<ControlsListingController.ControlsListingCallback>
|
||||||
|
|
||||||
|
private lateinit var underTest: HomeControlsKeyguardQuickAffordanceConfig
|
||||||
|
|
||||||
|
@JvmField @Parameter(0) var isFeatureEnabled: Boolean = false
|
||||||
|
@JvmField @Parameter(1) var hasFavorites: Boolean = false
|
||||||
|
@JvmField @Parameter(2) var hasServiceInfos: Boolean = false
|
||||||
|
@JvmField @Parameter(3) var isVisible: Boolean = false
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
whenever(component.getTileImageId()).thenReturn(R.drawable.controls_icon)
|
||||||
|
whenever(component.getTileTitleId()).thenReturn(R.string.quick_controls_title)
|
||||||
|
whenever(component.getControlsController()).thenReturn(Optional.of(controlsController))
|
||||||
|
whenever(component.getControlsListingController())
|
||||||
|
.thenReturn(Optional.of(controlsListingController))
|
||||||
|
|
||||||
|
underTest =
|
||||||
|
HomeControlsKeyguardQuickAffordanceConfig(
|
||||||
|
context = context,
|
||||||
|
component = component,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun state() = runBlockingTest {
|
||||||
|
whenever(component.isEnabled()).thenReturn(isFeatureEnabled)
|
||||||
|
whenever(controlsController.getFavorites())
|
||||||
|
.thenReturn(
|
||||||
|
if (hasFavorites) {
|
||||||
|
listOf(mock())
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
val values = mutableListOf<KeyguardQuickAffordanceConfig.State>()
|
||||||
|
val job = underTest.state.onEach(values::add).launchIn(this)
|
||||||
|
|
||||||
|
verify(controlsListingController).addCallback(callbackCaptor.capture())
|
||||||
|
callbackCaptor.value.onServicesUpdated(
|
||||||
|
if (hasServiceInfos) {
|
||||||
|
listOf(mock())
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(values.last())
|
||||||
|
.isInstanceOf(
|
||||||
|
if (isVisible) {
|
||||||
|
KeyguardQuickAffordanceConfig.State.Visible::class.java
|
||||||
|
} else {
|
||||||
|
KeyguardQuickAffordanceConfig.State.Hidden::class.java
|
||||||
|
}
|
||||||
|
)
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
* 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 androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.R
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.animation.ActivityLaunchAnimator
|
||||||
|
import com.android.systemui.controls.controller.ControlsController
|
||||||
|
import com.android.systemui.controls.dagger.ControlsComponent
|
||||||
|
import com.android.systemui.keyguard.data.quickaffordance.HomeControlsKeyguardQuickAffordanceConfig
|
||||||
|
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
|
||||||
|
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
|
||||||
|
import com.android.systemui.util.mockito.mock
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import java.util.Optional
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.test.runBlockingTest
|
||||||
|
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.`when` as whenever
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
@RunWith(JUnit4::class)
|
||||||
|
class HomeControlsKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
@Mock private lateinit var component: ControlsComponent
|
||||||
|
@Mock private lateinit var animationController: ActivityLaunchAnimator.Controller
|
||||||
|
|
||||||
|
private lateinit var underTest: HomeControlsKeyguardQuickAffordanceConfig
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
|
||||||
|
underTest =
|
||||||
|
HomeControlsKeyguardQuickAffordanceConfig(
|
||||||
|
context = context,
|
||||||
|
component = component,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `state - when listing controller is missing - returns None`() = runBlockingTest {
|
||||||
|
whenever(component.isEnabled()).thenReturn(true)
|
||||||
|
whenever(component.getTileImageId()).thenReturn(R.drawable.controls_icon)
|
||||||
|
whenever(component.getTileTitleId()).thenReturn(R.string.quick_controls_title)
|
||||||
|
val controlsController = mock<ControlsController>()
|
||||||
|
whenever(component.getControlsController()).thenReturn(Optional.of(controlsController))
|
||||||
|
whenever(component.getControlsListingController()).thenReturn(Optional.empty())
|
||||||
|
whenever(controlsController.getFavorites()).thenReturn(listOf(mock()))
|
||||||
|
|
||||||
|
val values = mutableListOf<KeyguardQuickAffordanceConfig.State>()
|
||||||
|
val job = underTest.state.onEach(values::add).launchIn(this)
|
||||||
|
|
||||||
|
assertThat(values.last())
|
||||||
|
.isInstanceOf(KeyguardQuickAffordanceConfig.State.Hidden::class.java)
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `onQuickAffordanceClicked - canShowWhileLockedSetting is true`() = runBlockingTest {
|
||||||
|
whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(true))
|
||||||
|
|
||||||
|
val onClickedResult = underTest.onQuickAffordanceClicked(animationController)
|
||||||
|
|
||||||
|
assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java)
|
||||||
|
assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `onQuickAffordanceClicked - canShowWhileLockedSetting is false`() = runBlockingTest {
|
||||||
|
whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(false))
|
||||||
|
|
||||||
|
val onClickedResult = underTest.onQuickAffordanceClicked(animationController)
|
||||||
|
|
||||||
|
assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java)
|
||||||
|
assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isFalse()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
/*
|
||||||
|
* 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 androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
|
||||||
|
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordanceModel
|
||||||
|
import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePosition
|
||||||
|
import com.android.systemui.util.mockito.mock
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.test.runBlockingTest
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.JUnit4
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
@RunWith(JUnit4::class)
|
||||||
|
class KeyguardQuickAffordanceRepositoryImplTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
private lateinit var underTest: KeyguardQuickAffordanceRepository
|
||||||
|
|
||||||
|
private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
|
||||||
|
private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig
|
||||||
|
private lateinit var qrCodeScanner: FakeKeyguardQuickAffordanceConfig
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
|
||||||
|
homeControls = object : FakeKeyguardQuickAffordanceConfig() {}
|
||||||
|
quickAccessWallet = object : FakeKeyguardQuickAffordanceConfig() {}
|
||||||
|
qrCodeScanner = object : FakeKeyguardQuickAffordanceConfig() {}
|
||||||
|
|
||||||
|
underTest =
|
||||||
|
KeyguardQuickAffordanceRepositoryImpl(
|
||||||
|
configs =
|
||||||
|
FakeKeyguardQuickAffordanceConfigs(
|
||||||
|
mapOf(
|
||||||
|
KeyguardQuickAffordancePosition.BOTTOM_START to
|
||||||
|
listOf(
|
||||||
|
homeControls,
|
||||||
|
),
|
||||||
|
KeyguardQuickAffordancePosition.BOTTOM_END to
|
||||||
|
listOf(
|
||||||
|
quickAccessWallet,
|
||||||
|
qrCodeScanner,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `bottom start affordance - none`() = runBlockingTest {
|
||||||
|
// TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
|
||||||
|
// https://developer.android.com/kotlin/flow/test#continuous-collection
|
||||||
|
var latest: KeyguardQuickAffordanceModel? = null
|
||||||
|
val job =
|
||||||
|
underTest
|
||||||
|
.affordance(KeyguardQuickAffordancePosition.BOTTOM_START)
|
||||||
|
.onEach { latest = it }
|
||||||
|
.launchIn(this)
|
||||||
|
|
||||||
|
assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `bottom start affordance - home controls`() = runBlockingTest {
|
||||||
|
// TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
|
||||||
|
// https://developer.android.com/kotlin/flow/test#continuous-collection
|
||||||
|
var latest: KeyguardQuickAffordanceModel? = null
|
||||||
|
val job =
|
||||||
|
underTest
|
||||||
|
.affordance(KeyguardQuickAffordancePosition.BOTTOM_START)
|
||||||
|
.onEach { latest = it }
|
||||||
|
.launchIn(this)
|
||||||
|
|
||||||
|
val state =
|
||||||
|
KeyguardQuickAffordanceConfig.State.Visible(
|
||||||
|
icon = mock(),
|
||||||
|
contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
|
||||||
|
)
|
||||||
|
homeControls.setState(state)
|
||||||
|
|
||||||
|
assertThat(latest).isEqualTo(state.toModel(homeControls::class))
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `bottom end affordance - none`() = runBlockingTest {
|
||||||
|
// TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
|
||||||
|
// https://developer.android.com/kotlin/flow/test#continuous-collection
|
||||||
|
var latest: KeyguardQuickAffordanceModel? = null
|
||||||
|
val job =
|
||||||
|
underTest
|
||||||
|
.affordance(KeyguardQuickAffordancePosition.BOTTOM_END)
|
||||||
|
.onEach { latest = it }
|
||||||
|
.launchIn(this)
|
||||||
|
|
||||||
|
assertThat(latest).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `bottom end affordance - quick access wallet`() = runBlockingTest {
|
||||||
|
// TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
|
||||||
|
// https://developer.android.com/kotlin/flow/test#continuous-collection
|
||||||
|
var latest: KeyguardQuickAffordanceModel? = null
|
||||||
|
val job =
|
||||||
|
underTest
|
||||||
|
.affordance(KeyguardQuickAffordancePosition.BOTTOM_END)
|
||||||
|
.onEach { latest = it }
|
||||||
|
.launchIn(this)
|
||||||
|
|
||||||
|
val quickAccessWalletState =
|
||||||
|
KeyguardQuickAffordanceConfig.State.Visible(
|
||||||
|
icon = mock(),
|
||||||
|
contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
|
||||||
|
)
|
||||||
|
quickAccessWallet.setState(quickAccessWalletState)
|
||||||
|
val qrCodeScannerState =
|
||||||
|
KeyguardQuickAffordanceConfig.State.Visible(
|
||||||
|
icon = mock(),
|
||||||
|
contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
|
||||||
|
)
|
||||||
|
qrCodeScanner.setState(qrCodeScannerState)
|
||||||
|
|
||||||
|
assertThat(latest).isEqualTo(quickAccessWalletState.toModel(quickAccessWallet::class))
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `bottom end affordance - qr code scanner`() = runBlockingTest {
|
||||||
|
// TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
|
||||||
|
// https://developer.android.com/kotlin/flow/test#continuous-collection
|
||||||
|
var latest: KeyguardQuickAffordanceModel? = null
|
||||||
|
val job =
|
||||||
|
underTest
|
||||||
|
.affordance(KeyguardQuickAffordancePosition.BOTTOM_END)
|
||||||
|
.onEach { latest = it }
|
||||||
|
.launchIn(this)
|
||||||
|
|
||||||
|
val state =
|
||||||
|
KeyguardQuickAffordanceConfig.State.Visible(
|
||||||
|
icon = mock(),
|
||||||
|
contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
|
||||||
|
)
|
||||||
|
qrCodeScanner.setState(state)
|
||||||
|
|
||||||
|
assertThat(latest).isEqualTo(state.toModel(qrCodeScanner::class))
|
||||||
|
job.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KeyguardQuickAffordanceConfig.State?.toModel(
|
||||||
|
configKey: KClass<out KeyguardQuickAffordanceConfig>,
|
||||||
|
): KeyguardQuickAffordanceModel? {
|
||||||
|
return when (this) {
|
||||||
|
is KeyguardQuickAffordanceConfig.State.Visible ->
|
||||||
|
KeyguardQuickAffordanceModel.Visible(
|
||||||
|
configKey = configKey,
|
||||||
|
icon = icon,
|
||||||
|
contentDescriptionResourceId = CONTENT_DESCRIPTION_RESOURCE_ID,
|
||||||
|
)
|
||||||
|
is KeyguardQuickAffordanceConfig.State.Hidden -> KeyguardQuickAffordanceModel.Hidden
|
||||||
|
null -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
* 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 androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.common.data.model.Position
|
||||||
|
import com.android.systemui.plugins.statusbar.StatusBarStateController
|
||||||
|
import com.android.systemui.util.mockito.argumentCaptor
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.test.runBlockingTest
|
||||||
|
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 KeyguardRepositoryImplTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
@Mock private lateinit var statusBarStateController: StatusBarStateController
|
||||||
|
|
||||||
|
private lateinit var underTest: KeyguardRepositoryImpl
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
|
||||||
|
underTest = KeyguardRepositoryImpl(statusBarStateController)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun animateBottomAreaDozingTransitions() = runBlockingTest {
|
||||||
|
assertThat(underTest.animateBottomAreaDozingTransitions.value).isEqualTo(false)
|
||||||
|
|
||||||
|
underTest.setAnimateDozingTransitions(true)
|
||||||
|
assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
|
||||||
|
|
||||||
|
underTest.setAnimateDozingTransitions(false)
|
||||||
|
assertThat(underTest.animateBottomAreaDozingTransitions.value).isFalse()
|
||||||
|
|
||||||
|
underTest.setAnimateDozingTransitions(true)
|
||||||
|
assertThat(underTest.animateBottomAreaDozingTransitions.value).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun bottomAreaAlpha() = runBlockingTest {
|
||||||
|
assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
|
||||||
|
|
||||||
|
underTest.setBottomAreaAlpha(0.1f)
|
||||||
|
assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.1f)
|
||||||
|
|
||||||
|
underTest.setBottomAreaAlpha(0.2f)
|
||||||
|
assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.2f)
|
||||||
|
|
||||||
|
underTest.setBottomAreaAlpha(0.3f)
|
||||||
|
assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.3f)
|
||||||
|
|
||||||
|
underTest.setBottomAreaAlpha(0.5f)
|
||||||
|
assertThat(underTest.bottomAreaAlpha.value).isEqualTo(0.5f)
|
||||||
|
|
||||||
|
underTest.setBottomAreaAlpha(1.0f)
|
||||||
|
assertThat(underTest.bottomAreaAlpha.value).isEqualTo(1f)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun clockPosition() = runBlockingTest {
|
||||||
|
assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0))
|
||||||
|
|
||||||
|
underTest.setClockPosition(0, 1)
|
||||||
|
assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 1))
|
||||||
|
|
||||||
|
underTest.setClockPosition(1, 9)
|
||||||
|
assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 9))
|
||||||
|
|
||||||
|
underTest.setClockPosition(1, 0)
|
||||||
|
assertThat(underTest.clockPosition.value).isEqualTo(Position(1, 0))
|
||||||
|
|
||||||
|
underTest.setClockPosition(3, 1)
|
||||||
|
assertThat(underTest.clockPosition.value).isEqualTo(Position(3, 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun isDozing() = runBlockingTest {
|
||||||
|
var latest: Boolean? = null
|
||||||
|
val job = underTest.isDozing.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
val captor = argumentCaptor<StatusBarStateController.StateListener>()
|
||||||
|
verify(statusBarStateController).addCallback(captor.capture())
|
||||||
|
|
||||||
|
captor.value.onDozingChanged(true)
|
||||||
|
assertThat(latest).isTrue()
|
||||||
|
|
||||||
|
captor.value.onDozingChanged(false)
|
||||||
|
assertThat(latest).isFalse()
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
verify(statusBarStateController).removeCallback(captor.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun dozeAmount() = runBlockingTest {
|
||||||
|
val values = mutableListOf<Float>()
|
||||||
|
val job = underTest.dozeAmount.onEach(values::add).launchIn(this)
|
||||||
|
|
||||||
|
val captor = argumentCaptor<StatusBarStateController.StateListener>()
|
||||||
|
verify(statusBarStateController).addCallback(captor.capture())
|
||||||
|
|
||||||
|
captor.value.onDozeAmountChanged(0.433f, 0.4f)
|
||||||
|
captor.value.onDozeAmountChanged(0.498f, 0.5f)
|
||||||
|
captor.value.onDozeAmountChanged(0.661f, 0.65f)
|
||||||
|
|
||||||
|
assertThat(values).isEqualTo(listOf(0f, 0.4f, 0.5f, 0.65f))
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
verify(statusBarStateController).removeCallback(captor.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
/*
|
||||||
|
* 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.content.Intent
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
|
||||||
|
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
|
||||||
|
import com.android.systemui.keyguard.data.quickaffordance.QrCodeScannerKeyguardQuickAffordanceConfig
|
||||||
|
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
|
||||||
|
import com.android.systemui.util.mockito.argumentCaptor
|
||||||
|
import com.android.systemui.util.mockito.mock
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.test.runBlockingTest
|
||||||
|
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.Mockito.`when` as whenever
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
@RunWith(JUnit4::class)
|
||||||
|
class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
@Mock private lateinit var controller: QRCodeScannerController
|
||||||
|
|
||||||
|
private lateinit var underTest: QrCodeScannerKeyguardQuickAffordanceConfig
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
whenever(controller.intent).thenReturn(INTENT_1)
|
||||||
|
|
||||||
|
underTest = QrCodeScannerKeyguardQuickAffordanceConfig(context, controller)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `affordance - sets up registration and delivers initial model`() = runBlockingTest {
|
||||||
|
whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
|
||||||
|
var latest: KeyguardQuickAffordanceConfig.State? = null
|
||||||
|
|
||||||
|
val job = underTest.state.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
|
||||||
|
verify(controller).addCallback(callbackCaptor.capture())
|
||||||
|
verify(controller)
|
||||||
|
.registerQRCodeScannerChangeObservers(
|
||||||
|
QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
|
||||||
|
QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
|
||||||
|
)
|
||||||
|
assertVisibleState(latest)
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
verify(controller).removeCallback(callbackCaptor.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `affordance - scanner activity changed - delivers model with updated intent`() =
|
||||||
|
runBlockingTest {
|
||||||
|
whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
|
||||||
|
var latest: KeyguardQuickAffordanceConfig.State? = null
|
||||||
|
val job = underTest.state.onEach { latest = it }.launchIn(this)
|
||||||
|
val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
|
||||||
|
verify(controller).addCallback(callbackCaptor.capture())
|
||||||
|
|
||||||
|
whenever(controller.intent).thenReturn(INTENT_2)
|
||||||
|
callbackCaptor.value.onQRCodeScannerActivityChanged()
|
||||||
|
|
||||||
|
assertVisibleState(latest)
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
verify(controller).removeCallback(callbackCaptor.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `affordance - scanner preference changed - delivers visible model`() = runBlockingTest {
|
||||||
|
var latest: KeyguardQuickAffordanceConfig.State? = null
|
||||||
|
val job = underTest.state.onEach { latest = it }.launchIn(this)
|
||||||
|
val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
|
||||||
|
verify(controller).addCallback(callbackCaptor.capture())
|
||||||
|
|
||||||
|
whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
|
||||||
|
callbackCaptor.value.onQRCodeScannerPreferenceChanged()
|
||||||
|
|
||||||
|
assertVisibleState(latest)
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
verify(controller).removeCallback(callbackCaptor.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `affordance - scanner preference changed - delivers none`() = runBlockingTest {
|
||||||
|
var latest: KeyguardQuickAffordanceConfig.State? = null
|
||||||
|
val job = underTest.state.onEach { latest = it }.launchIn(this)
|
||||||
|
val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
|
||||||
|
verify(controller).addCallback(callbackCaptor.capture())
|
||||||
|
|
||||||
|
whenever(controller.isEnabledForLockScreenButton).thenReturn(false)
|
||||||
|
callbackCaptor.value.onQRCodeScannerPreferenceChanged()
|
||||||
|
|
||||||
|
assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
verify(controller).removeCallback(callbackCaptor.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onQuickAffordanceClicked() {
|
||||||
|
assertThat(underTest.onQuickAffordanceClicked(mock()))
|
||||||
|
.isEqualTo(
|
||||||
|
OnClickedResult.StartActivity(
|
||||||
|
intent = INTENT_1,
|
||||||
|
canShowWhileLocked = true,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertVisibleState(latest: KeyguardQuickAffordanceConfig.State?) {
|
||||||
|
assertThat(latest).isInstanceOf(KeyguardQuickAffordanceConfig.State.Visible::class.java)
|
||||||
|
val visibleState = latest as KeyguardQuickAffordanceConfig.State.Visible
|
||||||
|
assertThat(visibleState.icon).isNotNull()
|
||||||
|
assertThat(visibleState.contentDescriptionResourceId).isNotNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val INTENT_1 = Intent("intent1")
|
||||||
|
private val INTENT_2 = Intent("intent2")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
/*
|
||||||
|
* 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.graphics.drawable.Drawable
|
||||||
|
import android.service.quickaccesswallet.GetWalletCardsResponse
|
||||||
|
import android.service.quickaccesswallet.QuickAccessWalletClient
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.animation.ActivityLaunchAnimator
|
||||||
|
import com.android.systemui.containeddrawable.ContainedDrawable
|
||||||
|
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
|
||||||
|
import com.android.systemui.keyguard.data.quickaffordance.QuickAccessWalletKeyguardQuickAffordanceConfig
|
||||||
|
import com.android.systemui.plugins.ActivityStarter
|
||||||
|
import com.android.systemui.statusbar.policy.KeyguardStateController
|
||||||
|
import com.android.systemui.util.mockito.any
|
||||||
|
import com.android.systemui.util.mockito.mock
|
||||||
|
import com.android.systemui.wallet.controller.QuickAccessWalletController
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.test.runBlockingTest
|
||||||
|
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.Mockito.`when` as whenever
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
@RunWith(JUnit4::class)
|
||||||
|
class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
@Mock private lateinit var walletController: QuickAccessWalletController
|
||||||
|
@Mock private lateinit var keyguardStateController: KeyguardStateController
|
||||||
|
@Mock private lateinit var activityStarter: ActivityStarter
|
||||||
|
|
||||||
|
private lateinit var underTest: QuickAccessWalletKeyguardQuickAffordanceConfig
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
|
||||||
|
underTest =
|
||||||
|
QuickAccessWalletKeyguardQuickAffordanceConfig(
|
||||||
|
keyguardStateController,
|
||||||
|
walletController,
|
||||||
|
activityStarter,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `affordance - keyguard showing - has wallet card - visible model`() = runBlockingTest {
|
||||||
|
val callback = setUpState()
|
||||||
|
var latest: KeyguardQuickAffordanceConfig.State? = null
|
||||||
|
|
||||||
|
val job = underTest.state.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
val visibleModel = latest as KeyguardQuickAffordanceConfig.State.Visible
|
||||||
|
assertThat(visibleModel.icon).isEqualTo(ContainedDrawable.WithDrawable(ICON))
|
||||||
|
assertThat(visibleModel.contentDescriptionResourceId).isNotNull()
|
||||||
|
job.cancel()
|
||||||
|
callback?.let { verify(keyguardStateController).removeCallback(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `affordance - keyguard not showing - model is none`() = runBlockingTest {
|
||||||
|
val callback = setUpState(isKeyguardShowing = false)
|
||||||
|
var latest: KeyguardQuickAffordanceConfig.State? = null
|
||||||
|
|
||||||
|
val job = underTest.state.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
callback?.let { verify(keyguardStateController).removeCallback(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `affordance - wallet not enabled - model is none`() = runBlockingTest {
|
||||||
|
val callback = setUpState(isWalletEnabled = false)
|
||||||
|
var latest: KeyguardQuickAffordanceConfig.State? = null
|
||||||
|
|
||||||
|
val job = underTest.state.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
callback?.let { verify(keyguardStateController).removeCallback(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `affordance - query not successful - model is none`() = runBlockingTest {
|
||||||
|
val callback = setUpState(isWalletQuerySuccessful = false)
|
||||||
|
var latest: KeyguardQuickAffordanceConfig.State? = null
|
||||||
|
|
||||||
|
val job = underTest.state.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
callback?.let { verify(keyguardStateController).removeCallback(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `affordance - missing icon - model is none`() = runBlockingTest {
|
||||||
|
val callback = setUpState(hasWalletIcon = false)
|
||||||
|
var latest: KeyguardQuickAffordanceConfig.State? = null
|
||||||
|
|
||||||
|
val job = underTest.state.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
callback?.let { verify(keyguardStateController).removeCallback(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `affordance - no selected card - model is none`() = runBlockingTest {
|
||||||
|
val callback = setUpState(hasWalletIcon = false)
|
||||||
|
var latest: KeyguardQuickAffordanceConfig.State? = null
|
||||||
|
|
||||||
|
val job = underTest.state.onEach { latest = it }.launchIn(this)
|
||||||
|
|
||||||
|
assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
|
||||||
|
|
||||||
|
job.cancel()
|
||||||
|
callback?.let { verify(keyguardStateController).removeCallback(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun onQuickAffordanceClicked() {
|
||||||
|
val animationController: ActivityLaunchAnimator.Controller = mock()
|
||||||
|
|
||||||
|
assertThat(underTest.onQuickAffordanceClicked(animationController))
|
||||||
|
.isEqualTo(KeyguardQuickAffordanceConfig.OnClickedResult.Handled)
|
||||||
|
verify(walletController)
|
||||||
|
.startQuickAccessUiIntent(
|
||||||
|
activityStarter,
|
||||||
|
animationController,
|
||||||
|
/* hasCard= */ true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setUpState(
|
||||||
|
isKeyguardShowing: Boolean = true,
|
||||||
|
isWalletEnabled: Boolean = true,
|
||||||
|
isWalletQuerySuccessful: Boolean = true,
|
||||||
|
hasWalletIcon: Boolean = true,
|
||||||
|
hasSelectedCard: Boolean = true,
|
||||||
|
): KeyguardStateController.Callback? {
|
||||||
|
var returnedCallback: KeyguardStateController.Callback? = null
|
||||||
|
whenever(keyguardStateController.isShowing).thenReturn(isKeyguardShowing)
|
||||||
|
whenever(keyguardStateController.addCallback(any())).thenAnswer { invocation ->
|
||||||
|
with(invocation.arguments[0] as KeyguardStateController.Callback) {
|
||||||
|
returnedCallback = this
|
||||||
|
onKeyguardShowingChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
whenever(walletController.isWalletEnabled).thenReturn(isWalletEnabled)
|
||||||
|
|
||||||
|
val walletClient: QuickAccessWalletClient = mock()
|
||||||
|
val icon: Drawable? =
|
||||||
|
if (hasWalletIcon) {
|
||||||
|
ICON
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
whenever(walletClient.tileIcon).thenReturn(icon)
|
||||||
|
whenever(walletController.walletClient).thenReturn(walletClient)
|
||||||
|
|
||||||
|
whenever(walletController.queryWalletCards(any())).thenAnswer { invocation ->
|
||||||
|
with(
|
||||||
|
invocation.arguments[0] as QuickAccessWalletClient.OnWalletCardsRetrievedCallback
|
||||||
|
) {
|
||||||
|
if (isWalletQuerySuccessful) {
|
||||||
|
onWalletCardsRetrieved(
|
||||||
|
if (hasSelectedCard) {
|
||||||
|
GetWalletCardsResponse(listOf(mock()), 0)
|
||||||
|
} else {
|
||||||
|
GetWalletCardsResponse(emptyList(), 0)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
onWalletCardRetrievalError(mock())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnedCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val ICON: Drawable = mock()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user