Merge "Merge "KeyguardBottomAreaView new impl. - data layer" into tm-qpr-dev am: d3a180b0fc" into tm-qpr-dev-plus-aosp am: c2dc0e2b89
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19330847 Change-Id: I8d800250eb7760ae1bc90574520283237bcaa59a Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -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.os.UserHandle
|
||||
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.ControlsTileResourceConfiguration
|
||||
import com.android.systemui.controls.controller.ControlsTileResourceConfigurationImpl
|
||||
import com.android.systemui.controls.management.ControlsListingController
|
||||
import com.android.systemui.controls.ui.ControlsUiController
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.settings.UserTracker
|
||||
import com.android.systemui.statusbar.policy.KeyguardStateController
|
||||
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 java.util.Optional
|
||||
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`.
|
||||
@@ -59,7 +61,8 @@ class ControlsComponent @Inject constructor(
|
||||
private val contentResolver: ContentResolver
|
||||
get() = context.contentResolver
|
||||
|
||||
private var canShowWhileLockedSetting = false
|
||||
private val _canShowWhileLockedSetting = MutableStateFlow(false)
|
||||
val canShowWhileLockedSetting = _canShowWhileLockedSetting.asStateFlow()
|
||||
|
||||
private val controlsTileResourceConfiguration: ControlsTileResourceConfiguration =
|
||||
optionalControlsTileResourceConfiguration.orElse(
|
||||
@@ -117,7 +120,7 @@ class ControlsComponent @Inject constructor(
|
||||
== STRONG_AUTH_REQUIRED_AFTER_BOOT) {
|
||||
return Visibility.AVAILABLE_AFTER_UNLOCK
|
||||
}
|
||||
if (!canShowWhileLockedSetting && !keyguardStateController.isUnlocked()) {
|
||||
if (!canShowWhileLockedSetting.value && !keyguardStateController.isUnlocked()) {
|
||||
return Visibility.AVAILABLE_AFTER_UNLOCK
|
||||
}
|
||||
|
||||
@@ -125,7 +128,7 @@ class ControlsComponent @Inject constructor(
|
||||
}
|
||||
|
||||
private fun updateShowWhileLocked() {
|
||||
canShowWhileLockedSetting = secureSettings.getIntForUser(
|
||||
_canShowWhileLockedSetting.value = secureSettings.getIntForUser(
|
||||
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.KeyguardUnlockAnimationController;
|
||||
import com.android.systemui.keyguard.KeyguardViewMediator;
|
||||
import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
|
||||
import com.android.systemui.navigationbar.NavigationModeController;
|
||||
import com.android.systemui.statusbar.NotificationShadeDepthController;
|
||||
import com.android.systemui.statusbar.NotificationShadeWindowController;
|
||||
@@ -66,7 +67,10 @@ import dagger.Provides;
|
||||
KeyguardStatusBarViewComponent.class,
|
||||
KeyguardStatusViewComponent.class,
|
||||
KeyguardUserSwitcherComponent.class},
|
||||
includes = {FalsingModule.class})
|
||||
includes = {
|
||||
FalsingModule.class,
|
||||
KeyguardRepositoryModule.class,
|
||||
})
|
||||
public class KeyguardModule {
|
||||
/**
|
||||
* 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