Support for unavailable and disabled affordances. am: af8fd1b3c5
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20441246 Change-Id: I6282dd626c1d84815aabf567ee144330954b417c Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
committed by
Automerger Merge Worker
commit
4459e7dcbc
@@ -2679,4 +2679,50 @@
|
|||||||
|
|
||||||
<!-- Time format for the Dream Time Complication for 24-hour time format [CHAR LIMIT=NONE] -->
|
<!-- Time format for the Dream Time Complication for 24-hour time format [CHAR LIMIT=NONE] -->
|
||||||
<string name="dream_time_complication_24_hr_time_format">kk:mm</string>
|
<string name="dream_time_complication_24_hr_time_format">kk:mm</string>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Template for an action that opens a specific app. [CHAR LIMIT=16]
|
||||||
|
-->
|
||||||
|
<string name="keyguard_affordance_enablement_dialog_action_template">Open <xliff:g id="appName" example="Wallet">%1$s</xliff:g></string>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Template for a message shown right before a list of instructions that tell the user what to do
|
||||||
|
in order to enable a shortcut to a specific app. [CHAR LIMIT=NONE]
|
||||||
|
-->
|
||||||
|
<string name="keyguard_affordance_enablement_dialog_message">To add the <xliff:g id="appName" example="Wallet">%1$s</xliff:g> app as a shortcut, make sure</string>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Requirement for the wallet app to be available for the user to use. This is shown as part of a
|
||||||
|
bulleted list of requirements. When all requirements are met, the app can be accessed through a
|
||||||
|
shortcut button on the lock screen. [CHAR LIMIT=NONE].
|
||||||
|
-->
|
||||||
|
<string name="keyguard_affordance_enablement_dialog_wallet_instruction_1">• The app is set up</string>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Requirement for the wallet app to be available for the user to use. This is shown as part of a
|
||||||
|
bulleted list of requirements. When all requirements are met, the app can be accessed through a
|
||||||
|
shortcut button on the lock screen. [CHAR LIMIT=NONE].
|
||||||
|
-->
|
||||||
|
<string name="keyguard_affordance_enablement_dialog_wallet_instruction_2">• At least one card has been added to Wallet</string>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Requirement for the QR code scanner functionality to be available for the user to use. This is
|
||||||
|
shown as part of a bulleted list of requirements. When all requirements are met, the piece of
|
||||||
|
functionality can be accessed through a shortcut button on the lock screen. [CHAR LIMIT=NONE].
|
||||||
|
-->
|
||||||
|
<string name="keyguard_affordance_enablement_dialog_qr_scanner_instruction">• Install a camera app</string>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Requirement for the home app to be available for the user to use. This is shown as part of a
|
||||||
|
bulleted list of requirements. When all requirements are met, the app can be accessed through a
|
||||||
|
shortcut button on the lock screen. [CHAR LIMIT=NONE].
|
||||||
|
-->
|
||||||
|
<string name="keyguard_affordance_enablement_dialog_home_instruction_1">• The app is set up</string>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Requirement for the home app to be available for the user to use. This is shown as part of a
|
||||||
|
bulleted list of requirements. When all requirements are met, the app can be accessed through a
|
||||||
|
shortcut button on the lock screen. [CHAR LIMIT=NONE].
|
||||||
|
-->
|
||||||
|
<string name="keyguard_affordance_enablement_dialog_home_instruction_2">• At least one device is available</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -67,6 +67,8 @@ object KeyguardQuickAffordanceProviderContract {
|
|||||||
object AffordanceTable {
|
object AffordanceTable {
|
||||||
const val TABLE_NAME = "affordances"
|
const val TABLE_NAME = "affordances"
|
||||||
val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build()
|
val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build()
|
||||||
|
const val ENABLEMENT_INSTRUCTIONS_DELIMITER = "]["
|
||||||
|
const val COMPONENT_NAME_SEPARATOR = "/"
|
||||||
|
|
||||||
object Columns {
|
object Columns {
|
||||||
/** String. Unique ID for this affordance. */
|
/** String. Unique ID for this affordance. */
|
||||||
@@ -78,6 +80,25 @@ object KeyguardQuickAffordanceProviderContract {
|
|||||||
* ID from the system UI package.
|
* ID from the system UI package.
|
||||||
*/
|
*/
|
||||||
const val ICON = "icon"
|
const val ICON = "icon"
|
||||||
|
/** Integer. `1` if the affordance is enabled or `0` if it disabled. */
|
||||||
|
const val IS_ENABLED = "is_enabled"
|
||||||
|
/**
|
||||||
|
* String. List of strings, delimited by [ENABLEMENT_INSTRUCTIONS_DELIMITER] to be shown
|
||||||
|
* to the user if the affordance is disabled and the user selects the affordance. The
|
||||||
|
* first one is a title while the rest are the steps needed to re-enable the affordance.
|
||||||
|
*/
|
||||||
|
const val ENABLEMENT_INSTRUCTIONS = "enablement_instructions"
|
||||||
|
/**
|
||||||
|
* String. Optional label for a button that, when clicked, opens a destination activity
|
||||||
|
* where the user can re-enable the disabled affordance.
|
||||||
|
*/
|
||||||
|
const val ENABLEMENT_ACTION_TEXT = "enablement_action_text"
|
||||||
|
/**
|
||||||
|
* String. Optional package name and activity action string, delimited by
|
||||||
|
* [COMPONENT_NAME_SEPARATOR] to use with an `Intent` to start an activity that opens a
|
||||||
|
* destination where the user can re-enable the disabled affordance.
|
||||||
|
*/
|
||||||
|
const val ENABLEMENT_COMPONENT_NAME = "enablement_action_intent"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCall
|
|||||||
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
|
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
|
||||||
import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
|
import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
class KeyguardQuickAffordanceProvider :
|
class KeyguardQuickAffordanceProvider :
|
||||||
ContentProvider(), SystemUIAppComponentFactoryBase.ContextInitializer {
|
ContentProvider(), SystemUIAppComponentFactoryBase.ContextInitializer {
|
||||||
@@ -118,9 +119,9 @@ class KeyguardQuickAffordanceProvider :
|
|||||||
sortOrder: String?,
|
sortOrder: String?,
|
||||||
): Cursor? {
|
): Cursor? {
|
||||||
return when (uriMatcher.match(uri)) {
|
return when (uriMatcher.match(uri)) {
|
||||||
MATCH_CODE_ALL_AFFORDANCES -> queryAffordances()
|
MATCH_CODE_ALL_AFFORDANCES -> runBlocking { queryAffordances() }
|
||||||
MATCH_CODE_ALL_SLOTS -> querySlots()
|
MATCH_CODE_ALL_SLOTS -> querySlots()
|
||||||
MATCH_CODE_ALL_SELECTIONS -> querySelections()
|
MATCH_CODE_ALL_SELECTIONS -> runBlocking { querySelections() }
|
||||||
MATCH_CODE_ALL_FLAGS -> queryFlags()
|
MATCH_CODE_ALL_FLAGS -> queryFlags()
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
@@ -194,7 +195,7 @@ class KeyguardQuickAffordanceProvider :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun querySelections(): Cursor {
|
private suspend fun querySelections(): Cursor {
|
||||||
return MatrixCursor(
|
return MatrixCursor(
|
||||||
arrayOf(
|
arrayOf(
|
||||||
Contract.SelectionTable.Columns.SLOT_ID,
|
Contract.SelectionTable.Columns.SLOT_ID,
|
||||||
@@ -219,12 +220,16 @@ class KeyguardQuickAffordanceProvider :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun queryAffordances(): Cursor {
|
private suspend fun queryAffordances(): Cursor {
|
||||||
return MatrixCursor(
|
return MatrixCursor(
|
||||||
arrayOf(
|
arrayOf(
|
||||||
Contract.AffordanceTable.Columns.ID,
|
Contract.AffordanceTable.Columns.ID,
|
||||||
Contract.AffordanceTable.Columns.NAME,
|
Contract.AffordanceTable.Columns.NAME,
|
||||||
Contract.AffordanceTable.Columns.ICON,
|
Contract.AffordanceTable.Columns.ICON,
|
||||||
|
Contract.AffordanceTable.Columns.IS_ENABLED,
|
||||||
|
Contract.AffordanceTable.Columns.ENABLEMENT_INSTRUCTIONS,
|
||||||
|
Contract.AffordanceTable.Columns.ENABLEMENT_ACTION_TEXT,
|
||||||
|
Contract.AffordanceTable.Columns.ENABLEMENT_COMPONENT_NAME,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.apply {
|
.apply {
|
||||||
@@ -234,6 +239,12 @@ class KeyguardQuickAffordanceProvider :
|
|||||||
representation.id,
|
representation.id,
|
||||||
representation.name,
|
representation.name,
|
||||||
representation.iconResourceId,
|
representation.iconResourceId,
|
||||||
|
if (representation.isEnabled) 1 else 0,
|
||||||
|
representation.instructions?.joinToString(
|
||||||
|
Contract.AffordanceTable.ENABLEMENT_INSTRUCTIONS_DELIMITER
|
||||||
|
),
|
||||||
|
representation.actionText,
|
||||||
|
representation.actionComponentName,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ package com.android.systemui.keyguard.data.quickaffordance
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
|
import com.android.systemui.R
|
||||||
import com.android.systemui.animation.Expandable
|
import com.android.systemui.animation.Expandable
|
||||||
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
|
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
|
||||||
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
||||||
@@ -45,7 +46,7 @@ import kotlinx.coroutines.flow.flowOf
|
|||||||
class HomeControlsKeyguardQuickAffordanceConfig
|
class HomeControlsKeyguardQuickAffordanceConfig
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
@Application context: Context,
|
@Application private val context: Context,
|
||||||
private val component: ControlsComponent,
|
private val component: ControlsComponent,
|
||||||
) : KeyguardQuickAffordanceConfig {
|
) : KeyguardQuickAffordanceConfig {
|
||||||
|
|
||||||
@@ -66,6 +67,36 @@ constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
|
||||||
|
if (!component.isEnabled()) {
|
||||||
|
return KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentServices =
|
||||||
|
component.getControlsListingController().getOrNull()?.getCurrentServices()
|
||||||
|
val hasFavorites =
|
||||||
|
component.getControlsController().getOrNull()?.getFavorites()?.isNotEmpty() == true
|
||||||
|
if (currentServices.isNullOrEmpty() || !hasFavorites) {
|
||||||
|
return KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
|
||||||
|
instructions =
|
||||||
|
listOf(
|
||||||
|
context.getString(
|
||||||
|
R.string.keyguard_affordance_enablement_dialog_message,
|
||||||
|
pickerName,
|
||||||
|
),
|
||||||
|
context.getString(
|
||||||
|
R.string.keyguard_affordance_enablement_dialog_home_instruction_1
|
||||||
|
),
|
||||||
|
context.getString(
|
||||||
|
R.string.keyguard_affordance_enablement_dialog_home_instruction_2
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return KeyguardQuickAffordanceConfig.PickerScreenState.Default
|
||||||
|
}
|
||||||
|
|
||||||
override fun onTriggered(
|
override fun onTriggered(
|
||||||
expandable: Expandable?,
|
expandable: Expandable?,
|
||||||
): KeyguardQuickAffordanceConfig.OnTriggeredResult {
|
): KeyguardQuickAffordanceConfig.OnTriggeredResult {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import android.content.Intent
|
|||||||
import com.android.systemui.animation.Expandable
|
import com.android.systemui.animation.Expandable
|
||||||
import com.android.systemui.common.shared.model.Icon
|
import com.android.systemui.common.shared.model.Icon
|
||||||
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
|
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
|
||||||
|
import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
/** Defines interface that can act as data source for a single quick affordance model. */
|
/** Defines interface that can act as data source for a single quick affordance model. */
|
||||||
@@ -40,6 +41,12 @@ interface KeyguardQuickAffordanceConfig {
|
|||||||
*/
|
*/
|
||||||
val lockScreenState: Flow<LockScreenState>
|
val lockScreenState: Flow<LockScreenState>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the [PickerScreenState] representing the affordance in the settings or selector
|
||||||
|
* experience.
|
||||||
|
*/
|
||||||
|
suspend fun getPickerScreenState(): PickerScreenState = PickerScreenState.Default
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifies that the affordance was clicked by the user.
|
* Notifies that the affordance was clicked by the user.
|
||||||
*
|
*
|
||||||
@@ -48,6 +55,58 @@ interface KeyguardQuickAffordanceConfig {
|
|||||||
*/
|
*/
|
||||||
fun onTriggered(expandable: Expandable?): OnTriggeredResult
|
fun onTriggered(expandable: Expandable?): OnTriggeredResult
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulates the state of a quick affordance within the context of the settings or selector
|
||||||
|
* experience.
|
||||||
|
*/
|
||||||
|
sealed class PickerScreenState {
|
||||||
|
|
||||||
|
/** The picker shows the item for selecting this affordance as it normally would. */
|
||||||
|
object Default : PickerScreenState()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The picker does not show an item for selecting this affordance as it is not supported on
|
||||||
|
* the device at all. For example, missing hardware requirements.
|
||||||
|
*/
|
||||||
|
object UnavailableOnDevice : PickerScreenState()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The picker shows the item for selecting this affordance as disabled. Clicking on it will
|
||||||
|
* show the given instructions to the user. If [actionText] and [actionComponentName] are
|
||||||
|
* provided (optional) a button will be shown to open an activity to help the user complete
|
||||||
|
* the steps described in the instructions.
|
||||||
|
*/
|
||||||
|
data class Disabled(
|
||||||
|
/** List of human-readable instructions for setting up the quick affordance. */
|
||||||
|
val instructions: List<String>,
|
||||||
|
/**
|
||||||
|
* Optional text to display on a button that the user can click to start a flow to go
|
||||||
|
* and set up the quick affordance and make it enabled.
|
||||||
|
*/
|
||||||
|
val actionText: String? = null,
|
||||||
|
/**
|
||||||
|
* Optional component name to be able to build an `Intent` that opens an `Activity` for
|
||||||
|
* the user to be able to set up the quick affordance and make it enabled.
|
||||||
|
*
|
||||||
|
* This is either just an action for the `Intent` or a package name and action,
|
||||||
|
* separated by [Contract.AffordanceTable.COMPONENT_NAME_SEPARATOR] for convenience, you
|
||||||
|
* can use the [componentName] function.
|
||||||
|
*/
|
||||||
|
val actionComponentName: String? = null,
|
||||||
|
) : PickerScreenState() {
|
||||||
|
init {
|
||||||
|
check(instructions.isNotEmpty()) { "Instructions must not be empty!" }
|
||||||
|
check(
|
||||||
|
(actionText.isNullOrEmpty() && actionComponentName.isNullOrEmpty()) ||
|
||||||
|
(!actionText.isNullOrEmpty() && !actionComponentName.isNullOrEmpty())
|
||||||
|
) {
|
||||||
|
"actionText and actionComponentName must either both be null/empty or both be" +
|
||||||
|
" non-empty!"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encapsulates the state of a "quick affordance" in the keyguard bottom area (for example, a
|
* Encapsulates the state of a "quick affordance" in the keyguard bottom area (for example, a
|
||||||
* button on the lock-screen).
|
* button on the lock-screen).
|
||||||
@@ -83,4 +142,18 @@ interface KeyguardQuickAffordanceConfig {
|
|||||||
val canShowWhileLocked: Boolean,
|
val canShowWhileLocked: Boolean,
|
||||||
) : OnTriggeredResult()
|
) : OnTriggeredResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun componentName(
|
||||||
|
packageName: String? = null,
|
||||||
|
action: String?,
|
||||||
|
): String? {
|
||||||
|
return when {
|
||||||
|
action.isNullOrEmpty() -> null
|
||||||
|
!packageName.isNullOrEmpty() ->
|
||||||
|
"$packageName${Contract.AffordanceTable.COMPONENT_NAME_SEPARATOR}$action"
|
||||||
|
else -> action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
class QrCodeScannerKeyguardQuickAffordanceConfig
|
class QrCodeScannerKeyguardQuickAffordanceConfig
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
@Application context: Context,
|
@Application private val context: Context,
|
||||||
private val controller: QRCodeScannerController,
|
private val controller: QRCodeScannerController,
|
||||||
) : KeyguardQuickAffordanceConfig {
|
) : KeyguardQuickAffordanceConfig {
|
||||||
|
|
||||||
@@ -75,6 +75,28 @@ constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
|
||||||
|
return when {
|
||||||
|
!controller.isAvailableOnDevice ->
|
||||||
|
KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
|
||||||
|
!controller.isAbleToOpenCameraApp ->
|
||||||
|
KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
|
||||||
|
instructions =
|
||||||
|
listOf(
|
||||||
|
context.getString(
|
||||||
|
R.string.keyguard_affordance_enablement_dialog_message,
|
||||||
|
pickerName,
|
||||||
|
),
|
||||||
|
context.getString(
|
||||||
|
R.string
|
||||||
|
.keyguard_affordance_enablement_dialog_qr_scanner_instruction
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onTriggered(
|
override fun onTriggered(
|
||||||
expandable: Expandable?,
|
expandable: Expandable?,
|
||||||
): KeyguardQuickAffordanceConfig.OnTriggeredResult {
|
): KeyguardQuickAffordanceConfig.OnTriggeredResult {
|
||||||
|
|||||||
@@ -18,10 +18,12 @@
|
|||||||
package com.android.systemui.keyguard.data.quickaffordance
|
package com.android.systemui.keyguard.data.quickaffordance
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.service.quickaccesswallet.GetWalletCardsError
|
import android.service.quickaccesswallet.GetWalletCardsError
|
||||||
import android.service.quickaccesswallet.GetWalletCardsResponse
|
import android.service.quickaccesswallet.GetWalletCardsResponse
|
||||||
import android.service.quickaccesswallet.QuickAccessWalletClient
|
import android.service.quickaccesswallet.QuickAccessWalletClient
|
||||||
|
import android.service.quickaccesswallet.WalletCard
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.animation.Expandable
|
import com.android.systemui.animation.Expandable
|
||||||
@@ -31,25 +33,27 @@ import com.android.systemui.common.shared.model.ContentDescription
|
|||||||
import com.android.systemui.common.shared.model.Icon
|
import com.android.systemui.common.shared.model.Icon
|
||||||
import com.android.systemui.dagger.SysUISingleton
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
import com.android.systemui.dagger.qualifiers.Application
|
import com.android.systemui.dagger.qualifiers.Application
|
||||||
|
import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.Companion.componentName
|
||||||
import com.android.systemui.plugins.ActivityStarter
|
import com.android.systemui.plugins.ActivityStarter
|
||||||
import com.android.systemui.wallet.controller.QuickAccessWalletController
|
import com.android.systemui.wallet.controller.QuickAccessWalletController
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
|
||||||
/** Quick access wallet quick affordance data source. */
|
/** Quick access wallet quick affordance data source. */
|
||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
class QuickAccessWalletKeyguardQuickAffordanceConfig
|
class QuickAccessWalletKeyguardQuickAffordanceConfig
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
@Application context: Context,
|
@Application private val context: Context,
|
||||||
private val walletController: QuickAccessWalletController,
|
private val walletController: QuickAccessWalletController,
|
||||||
private val activityStarter: ActivityStarter,
|
private val activityStarter: ActivityStarter,
|
||||||
) : KeyguardQuickAffordanceConfig {
|
) : KeyguardQuickAffordanceConfig {
|
||||||
|
|
||||||
override val key: String = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
|
override val key: String = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
|
||||||
|
|
||||||
override val pickerName = context.getString(R.string.accessibility_wallet_button)
|
override val pickerName: String = context.getString(R.string.accessibility_wallet_button)
|
||||||
|
|
||||||
override val pickerIconResourceId = R.drawable.ic_wallet_lockscreen
|
override val pickerIconResourceId = R.drawable.ic_wallet_lockscreen
|
||||||
|
|
||||||
@@ -58,10 +62,11 @@ constructor(
|
|||||||
val callback =
|
val callback =
|
||||||
object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
|
object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
|
||||||
override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
|
override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
|
||||||
|
val hasCards = response?.walletCards?.isNotEmpty() == true
|
||||||
trySendWithFailureLogging(
|
trySendWithFailureLogging(
|
||||||
state(
|
state(
|
||||||
isFeatureEnabled = walletController.isWalletEnabled,
|
isFeatureEnabled = walletController.isWalletEnabled,
|
||||||
hasCard = response?.walletCards?.isNotEmpty() == true,
|
hasCard = hasCards,
|
||||||
tileIcon = walletController.walletClient.tileIcon,
|
tileIcon = walletController.walletClient.tileIcon,
|
||||||
),
|
),
|
||||||
TAG,
|
TAG,
|
||||||
@@ -93,6 +98,44 @@ constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
|
||||||
|
return when {
|
||||||
|
!walletController.isWalletEnabled ->
|
||||||
|
KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
|
||||||
|
walletController.walletClient.tileIcon == null || queryCards().isEmpty() -> {
|
||||||
|
val componentName =
|
||||||
|
walletController.walletClient.createWalletSettingsIntent().toComponentName()
|
||||||
|
val actionText =
|
||||||
|
if (componentName != null) {
|
||||||
|
context.getString(
|
||||||
|
R.string.keyguard_affordance_enablement_dialog_action_template,
|
||||||
|
pickerName,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
|
||||||
|
instructions =
|
||||||
|
listOf(
|
||||||
|
context.getString(
|
||||||
|
R.string.keyguard_affordance_enablement_dialog_message,
|
||||||
|
pickerName,
|
||||||
|
),
|
||||||
|
context.getString(
|
||||||
|
R.string.keyguard_affordance_enablement_dialog_wallet_instruction_1
|
||||||
|
),
|
||||||
|
context.getString(
|
||||||
|
R.string.keyguard_affordance_enablement_dialog_wallet_instruction_2
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actionText = actionText,
|
||||||
|
actionComponentName = componentName,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onTriggered(
|
override fun onTriggered(
|
||||||
expandable: Expandable?,
|
expandable: Expandable?,
|
||||||
): KeyguardQuickAffordanceConfig.OnTriggeredResult {
|
): KeyguardQuickAffordanceConfig.OnTriggeredResult {
|
||||||
@@ -104,6 +147,24 @@ constructor(
|
|||||||
return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
|
return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun queryCards(): List<WalletCard> {
|
||||||
|
return suspendCancellableCoroutine { continuation ->
|
||||||
|
val callback =
|
||||||
|
object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
|
||||||
|
override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
|
||||||
|
continuation.resumeWith(
|
||||||
|
Result.success(response?.walletCards ?: emptyList())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
|
||||||
|
continuation.resumeWith(Result.success(emptyList()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
walletController.queryWalletCards(callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun state(
|
private fun state(
|
||||||
isFeatureEnabled: Boolean,
|
isFeatureEnabled: Boolean,
|
||||||
hasCard: Boolean,
|
hasCard: Boolean,
|
||||||
@@ -125,6 +186,14 @@ constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun Intent?.toComponentName(): String? {
|
||||||
|
if (this == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return componentName(packageName = `package`, action = action)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "QuickAccessWalletKeyguardQuickAffordanceConfig"
|
private const val TAG = "QuickAccessWalletKeyguardQuickAffordanceConfig"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,18 +121,32 @@ constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the list of representation objects for all known affordances, regardless of what is
|
* Returns the list of representation objects for all known, device-available affordances,
|
||||||
* selected. This is useful for building experiences like the picker/selector or user settings
|
* regardless of what is selected. This is useful for building experiences like the
|
||||||
* so the user can see everything that can be selected in a menu.
|
* picker/selector or user settings so the user can see everything that can be selected in a
|
||||||
|
* menu.
|
||||||
*/
|
*/
|
||||||
fun getAffordancePickerRepresentations(): List<KeyguardQuickAffordancePickerRepresentation> {
|
suspend fun getAffordancePickerRepresentations():
|
||||||
return configs.map { config ->
|
List<KeyguardQuickAffordancePickerRepresentation> {
|
||||||
KeyguardQuickAffordancePickerRepresentation(
|
return configs
|
||||||
id = config.key,
|
.associateWith { config -> config.getPickerScreenState() }
|
||||||
name = config.pickerName,
|
.filterNot { (_, pickerState) ->
|
||||||
iconResourceId = config.pickerIconResourceId,
|
pickerState is KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
|
||||||
)
|
}
|
||||||
}
|
.map { (config, pickerState) ->
|
||||||
|
val disabledPickerState =
|
||||||
|
pickerState as? KeyguardQuickAffordanceConfig.PickerScreenState.Disabled
|
||||||
|
KeyguardQuickAffordancePickerRepresentation(
|
||||||
|
id = config.key,
|
||||||
|
name = config.pickerName,
|
||||||
|
iconResourceId = config.pickerIconResourceId,
|
||||||
|
isEnabled =
|
||||||
|
pickerState is KeyguardQuickAffordanceConfig.PickerScreenState.Default,
|
||||||
|
instructions = disabledPickerState?.instructions,
|
||||||
|
actionText = disabledPickerState?.actionText,
|
||||||
|
actionComponentName = disabledPickerState?.actionComponentName,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Returns affordance IDs indexed by slot ID, for all known slots. */
|
/** Returns affordance IDs indexed by slot ID, for all known slots. */
|
||||||
fun getSelections(): Map<String, List<KeyguardQuickAffordancePickerRepresentation>> {
|
suspend fun getSelections(): Map<String, List<KeyguardQuickAffordancePickerRepresentation>> {
|
||||||
check(isUsingRepository)
|
check(isUsingRepository)
|
||||||
|
|
||||||
val slots = repository.get().getSlotPickerRepresentations()
|
val slots = repository.get().getSlotPickerRepresentations()
|
||||||
@@ -310,7 +310,8 @@ constructor(
|
|||||||
return Pair(splitUp[0], splitUp[1])
|
return Pair(splitUp[0], splitUp[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAffordancePickerRepresentations(): List<KeyguardQuickAffordancePickerRepresentation> {
|
suspend fun getAffordancePickerRepresentations():
|
||||||
|
List<KeyguardQuickAffordancePickerRepresentation> {
|
||||||
check(isUsingRepository)
|
check(isUsingRepository)
|
||||||
|
|
||||||
return repository.get().getAffordancePickerRepresentations()
|
return repository.get().getAffordancePickerRepresentations()
|
||||||
|
|||||||
@@ -27,4 +27,22 @@ data class KeyguardQuickAffordancePickerRepresentation(
|
|||||||
val id: String,
|
val id: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
@DrawableRes val iconResourceId: Int,
|
@DrawableRes val iconResourceId: Int,
|
||||||
|
|
||||||
|
/** Whether this quick affordance is enabled. */
|
||||||
|
val isEnabled: Boolean = true,
|
||||||
|
|
||||||
|
/** If not enabled, the list of user-visible steps to re-enable it. */
|
||||||
|
val instructions: List<String>? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If not enabled, an optional label for a button that takes the user to a destination where
|
||||||
|
* they can re-enable it.
|
||||||
|
*/
|
||||||
|
val actionText: String? = null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If not enabled, an optional component name (package and action) for a button that takes the
|
||||||
|
* user to a destination where they can re-enable it.
|
||||||
|
*/
|
||||||
|
val actionComponentName: String? = null,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -158,14 +158,18 @@ public class QRCodeScannerController implements
|
|||||||
* Returns true if lock screen entry point for QR Code Scanner is to be enabled.
|
* Returns true if lock screen entry point for QR Code Scanner is to be enabled.
|
||||||
*/
|
*/
|
||||||
public boolean isEnabledForLockScreenButton() {
|
public boolean isEnabledForLockScreenButton() {
|
||||||
return mQRCodeScannerEnabled && mIntent != null && mConfigEnableLockScreenButton
|
return mQRCodeScannerEnabled && isAbleToOpenCameraApp() && isAvailableOnDevice();
|
||||||
&& isActivityCallable(mIntent);
|
}
|
||||||
|
|
||||||
|
/** Returns whether the feature is available on the device. */
|
||||||
|
public boolean isAvailableOnDevice() {
|
||||||
|
return mConfigEnableLockScreenButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if quick settings entry point for QR Code Scanner is to be enabled.
|
* Returns true if the feature can open a camera app on the device.
|
||||||
*/
|
*/
|
||||||
public boolean isEnabledForQuickSettings() {
|
public boolean isAbleToOpenCameraApp() {
|
||||||
return mIntent != null && isActivityCallable(mIntent);
|
return mIntent != null && isActivityCallable(mIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ public class QRCodeScannerTile extends QSTileImpl<QSTile.State> {
|
|||||||
state.label = mContext.getString(R.string.qr_code_scanner_title);
|
state.label = mContext.getString(R.string.qr_code_scanner_title);
|
||||||
state.contentDescription = state.label;
|
state.contentDescription = state.label;
|
||||||
state.icon = ResourceIcon.get(R.drawable.ic_qr_code_scanner);
|
state.icon = ResourceIcon.get(R.drawable.ic_qr_code_scanner);
|
||||||
state.state = mQRCodeScannerController.isEnabledForQuickSettings() ? Tile.STATE_INACTIVE
|
state.state = mQRCodeScannerController.isAbleToOpenCameraApp() ? Tile.STATE_INACTIVE
|
||||||
: Tile.STATE_UNAVAILABLE;
|
: Tile.STATE_UNAVAILABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,9 @@ import com.android.systemui.controls.controller.ControlsController
|
|||||||
import com.android.systemui.controls.dagger.ControlsComponent
|
import com.android.systemui.controls.dagger.ControlsComponent
|
||||||
import com.android.systemui.controls.management.ControlsListingController
|
import com.android.systemui.controls.management.ControlsListingController
|
||||||
import com.android.systemui.util.mockito.mock
|
import com.android.systemui.util.mockito.mock
|
||||||
|
import com.android.systemui.util.mockito.whenever
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import java.util.Optional
|
import java.util.*
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
@@ -40,7 +41,6 @@ import org.mockito.ArgumentCaptor
|
|||||||
import org.mockito.Captor
|
import org.mockito.Captor
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito.verify
|
||||||
import org.mockito.Mockito.`when` as whenever
|
|
||||||
import org.mockito.MockitoAnnotations
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@@ -93,6 +93,14 @@ class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTes
|
|||||||
whenever(component.getControlsController()).thenReturn(Optional.of(controlsController))
|
whenever(component.getControlsController()).thenReturn(Optional.of(controlsController))
|
||||||
whenever(component.getControlsListingController())
|
whenever(component.getControlsListingController())
|
||||||
.thenReturn(Optional.of(controlsListingController))
|
.thenReturn(Optional.of(controlsListingController))
|
||||||
|
whenever(controlsListingController.getCurrentServices())
|
||||||
|
.thenReturn(
|
||||||
|
if (hasServiceInfos) {
|
||||||
|
listOf(mock(), mock())
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
)
|
||||||
whenever(component.canShowWhileLockedSetting)
|
whenever(component.canShowWhileLockedSetting)
|
||||||
.thenReturn(MutableStateFlow(canShowWhileLocked))
|
.thenReturn(MutableStateFlow(canShowWhileLocked))
|
||||||
whenever(component.getVisibility())
|
whenever(component.getVisibility())
|
||||||
@@ -144,6 +152,17 @@ class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTes
|
|||||||
KeyguardQuickAffordanceConfig.LockScreenState.Hidden::class.java
|
KeyguardQuickAffordanceConfig.LockScreenState.Hidden::class.java
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
assertThat(underTest.getPickerScreenState())
|
||||||
|
.isInstanceOf(
|
||||||
|
when {
|
||||||
|
!isFeatureEnabled ->
|
||||||
|
KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice::class
|
||||||
|
.java
|
||||||
|
hasServiceInfos && hasFavorites ->
|
||||||
|
KeyguardQuickAffordanceConfig.PickerScreenState.Default::class.java
|
||||||
|
else -> KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java
|
||||||
|
}
|
||||||
|
)
|
||||||
job.cancel()
|
job.cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,17 +24,18 @@ import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanc
|
|||||||
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
|
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
|
||||||
import com.android.systemui.util.mockito.argumentCaptor
|
import com.android.systemui.util.mockito.argumentCaptor
|
||||||
import com.android.systemui.util.mockito.mock
|
import com.android.systemui.util.mockito.mock
|
||||||
|
import com.android.systemui.util.mockito.whenever
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.test.runBlockingTest
|
import kotlinx.coroutines.test.runBlockingTest
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.junit.runners.JUnit4
|
import org.junit.runners.JUnit4
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito.verify
|
||||||
import org.mockito.Mockito.`when` as whenever
|
|
||||||
import org.mockito.MockitoAnnotations
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@@ -134,6 +135,33 @@ class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getPickerScreenState - enabled if configured on device - can open camera`() = runTest {
|
||||||
|
whenever(controller.isAvailableOnDevice).thenReturn(true)
|
||||||
|
whenever(controller.isAbleToOpenCameraApp).thenReturn(true)
|
||||||
|
|
||||||
|
assertThat(underTest.getPickerScreenState())
|
||||||
|
.isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getPickerScreenState - disabled if configured on device - cannot open camera`() = runTest {
|
||||||
|
whenever(controller.isAvailableOnDevice).thenReturn(true)
|
||||||
|
whenever(controller.isAbleToOpenCameraApp).thenReturn(false)
|
||||||
|
|
||||||
|
assertThat(underTest.getPickerScreenState())
|
||||||
|
.isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getPickerScreenState - unavailable if not configured on device`() = runTest {
|
||||||
|
whenever(controller.isAvailableOnDevice).thenReturn(false)
|
||||||
|
whenever(controller.isAbleToOpenCameraApp).thenReturn(true)
|
||||||
|
|
||||||
|
assertThat(underTest.getPickerScreenState())
|
||||||
|
.isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
|
||||||
|
}
|
||||||
|
|
||||||
private fun assertVisibleState(latest: KeyguardQuickAffordanceConfig.LockScreenState?) {
|
private fun assertVisibleState(latest: KeyguardQuickAffordanceConfig.LockScreenState?) {
|
||||||
assertThat(latest)
|
assertThat(latest)
|
||||||
.isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java)
|
.isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java)
|
||||||
|
|||||||
@@ -33,9 +33,11 @@ import com.android.systemui.util.mockito.mock
|
|||||||
import com.android.systemui.util.mockito.whenever
|
import com.android.systemui.util.mockito.whenever
|
||||||
import com.android.systemui.wallet.controller.QuickAccessWalletController
|
import com.android.systemui.wallet.controller.QuickAccessWalletController
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.test.runBlockingTest
|
import kotlinx.coroutines.test.runBlockingTest
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
@@ -44,6 +46,7 @@ import org.mockito.Mock
|
|||||||
import org.mockito.Mockito.verify
|
import org.mockito.Mockito.verify
|
||||||
import org.mockito.MockitoAnnotations
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@RunWith(JUnit4::class)
|
@RunWith(JUnit4::class)
|
||||||
class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
|
class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
|
||||||
@@ -59,7 +62,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
|
|||||||
|
|
||||||
underTest =
|
underTest =
|
||||||
QuickAccessWalletKeyguardQuickAffordanceConfig(
|
QuickAccessWalletKeyguardQuickAffordanceConfig(
|
||||||
mock(),
|
context,
|
||||||
walletController,
|
walletController,
|
||||||
activityStarter,
|
activityStarter,
|
||||||
)
|
)
|
||||||
@@ -151,6 +154,44 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getPickerScreenState - default`() = runTest {
|
||||||
|
setUpState()
|
||||||
|
|
||||||
|
assertThat(underTest.getPickerScreenState())
|
||||||
|
.isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getPickerScreenState - unavailable`() = runTest {
|
||||||
|
setUpState(
|
||||||
|
isWalletEnabled = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(underTest.getPickerScreenState())
|
||||||
|
.isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getPickerScreenState - disabled when there is no icon`() = runTest {
|
||||||
|
setUpState(
|
||||||
|
hasWalletIcon = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(underTest.getPickerScreenState())
|
||||||
|
.isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getPickerScreenState - disabled when there is no card`() = runTest {
|
||||||
|
setUpState(
|
||||||
|
hasSelectedCard = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(underTest.getPickerScreenState())
|
||||||
|
.isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
private fun setUpState(
|
private fun setUpState(
|
||||||
isWalletEnabled: Boolean = true,
|
isWalletEnabled: Boolean = true,
|
||||||
isWalletQuerySuccessful: Boolean = true,
|
isWalletQuerySuccessful: Boolean = true,
|
||||||
|
|||||||
@@ -133,23 +133,24 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun getAffordancePickerRepresentations() {
|
fun getAffordancePickerRepresentations() =
|
||||||
assertThat(underTest.getAffordancePickerRepresentations())
|
runBlocking(IMMEDIATE) {
|
||||||
.isEqualTo(
|
assertThat(underTest.getAffordancePickerRepresentations())
|
||||||
listOf(
|
.isEqualTo(
|
||||||
KeyguardQuickAffordancePickerRepresentation(
|
listOf(
|
||||||
id = config1.key,
|
KeyguardQuickAffordancePickerRepresentation(
|
||||||
name = config1.pickerName,
|
id = config1.key,
|
||||||
iconResourceId = config1.pickerIconResourceId,
|
name = config1.pickerName,
|
||||||
),
|
iconResourceId = config1.pickerIconResourceId,
|
||||||
KeyguardQuickAffordancePickerRepresentation(
|
),
|
||||||
id = config2.key,
|
KeyguardQuickAffordancePickerRepresentation(
|
||||||
name = config2.pickerName,
|
id = config2.key,
|
||||||
iconResourceId = config2.pickerIconResourceId,
|
name = config2.pickerName,
|
||||||
),
|
iconResourceId = config2.pickerIconResourceId,
|
||||||
|
),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun getSlotPickerRepresentations() {
|
fun getSlotPickerRepresentations() {
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ import static org.mockito.Mockito.when;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.ResolveInfo;
|
import android.content.pm.ResolveInfo;
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.provider.DeviceConfig;
|
import android.provider.DeviceConfig;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
@@ -133,7 +132,7 @@ public class QRCodeScannerControllerTest extends SysuiTestCase {
|
|||||||
/* enableOnLockScreen */ true);
|
/* enableOnLockScreen */ true);
|
||||||
verifyActivityDetails(null);
|
verifyActivityDetails(null);
|
||||||
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
|
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
|
||||||
assertThat(mController.isEnabledForQuickSettings()).isFalse();
|
assertThat(mController.isAbleToOpenCameraApp()).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -152,7 +151,7 @@ public class QRCodeScannerControllerTest extends SysuiTestCase {
|
|||||||
/* enableOnLockScreen */ true);
|
/* enableOnLockScreen */ true);
|
||||||
verifyActivityDetails("abc/.def");
|
verifyActivityDetails("abc/.def");
|
||||||
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
|
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
|
||||||
assertThat(mController.isEnabledForQuickSettings()).isTrue();
|
assertThat(mController.isAbleToOpenCameraApp()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -162,7 +161,7 @@ public class QRCodeScannerControllerTest extends SysuiTestCase {
|
|||||||
/* enableOnLockScreen */ true);
|
/* enableOnLockScreen */ true);
|
||||||
verifyActivityDetails("abc/.def");
|
verifyActivityDetails("abc/.def");
|
||||||
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
|
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
|
||||||
assertThat(mController.isEnabledForQuickSettings()).isTrue();
|
assertThat(mController.isAbleToOpenCameraApp()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -172,7 +171,7 @@ public class QRCodeScannerControllerTest extends SysuiTestCase {
|
|||||||
/* enableOnLockScreen */ true);
|
/* enableOnLockScreen */ true);
|
||||||
verifyActivityDetails("abc/.def");
|
verifyActivityDetails("abc/.def");
|
||||||
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
|
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
|
||||||
assertThat(mController.isEnabledForQuickSettings()).isTrue();
|
assertThat(mController.isAbleToOpenCameraApp()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -182,7 +181,7 @@ public class QRCodeScannerControllerTest extends SysuiTestCase {
|
|||||||
/* enableOnLockScreen */ true);
|
/* enableOnLockScreen */ true);
|
||||||
verifyActivityDetails("abc/abc.def");
|
verifyActivityDetails("abc/abc.def");
|
||||||
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
|
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
|
||||||
assertThat(mController.isEnabledForQuickSettings()).isTrue();
|
assertThat(mController.isAbleToOpenCameraApp()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -192,7 +191,7 @@ public class QRCodeScannerControllerTest extends SysuiTestCase {
|
|||||||
/* enableOnLockScreen */ true);
|
/* enableOnLockScreen */ true);
|
||||||
verifyActivityDetails(null);
|
verifyActivityDetails(null);
|
||||||
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
|
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
|
||||||
assertThat(mController.isEnabledForQuickSettings()).isFalse();
|
assertThat(mController.isAbleToOpenCameraApp()).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -202,21 +201,21 @@ public class QRCodeScannerControllerTest extends SysuiTestCase {
|
|||||||
/* enableOnLockScreen */ true);
|
/* enableOnLockScreen */ true);
|
||||||
verifyActivityDetails("abc/.def");
|
verifyActivityDetails("abc/.def");
|
||||||
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
|
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
|
||||||
assertThat(mController.isEnabledForQuickSettings()).isTrue();
|
assertThat(mController.isAbleToOpenCameraApp()).isTrue();
|
||||||
|
|
||||||
mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
|
mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||||
SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
|
SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
|
||||||
"def/.ijk", false);
|
"def/.ijk", false);
|
||||||
verifyActivityDetails("def/.ijk");
|
verifyActivityDetails("def/.ijk");
|
||||||
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
|
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
|
||||||
assertThat(mController.isEnabledForQuickSettings()).isTrue();
|
assertThat(mController.isAbleToOpenCameraApp()).isTrue();
|
||||||
|
|
||||||
mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
|
mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||||
SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
|
SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
|
||||||
null, false);
|
null, false);
|
||||||
verifyActivityDetails("abc/.def");
|
verifyActivityDetails("abc/.def");
|
||||||
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
|
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
|
||||||
assertThat(mController.isEnabledForQuickSettings()).isTrue();
|
assertThat(mController.isAbleToOpenCameraApp()).isTrue();
|
||||||
|
|
||||||
// Once from setup + twice from this function
|
// Once from setup + twice from this function
|
||||||
verify(mCallback, times(3)).onQRCodeScannerActivityChanged();
|
verify(mCallback, times(3)).onQRCodeScannerActivityChanged();
|
||||||
@@ -229,7 +228,7 @@ public class QRCodeScannerControllerTest extends SysuiTestCase {
|
|||||||
/* enableOnLockScreen */ true);
|
/* enableOnLockScreen */ true);
|
||||||
verifyActivityDetails(null);
|
verifyActivityDetails(null);
|
||||||
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
|
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
|
||||||
assertThat(mController.isEnabledForQuickSettings()).isFalse();
|
assertThat(mController.isAbleToOpenCameraApp()).isFalse();
|
||||||
|
|
||||||
mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
|
mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||||
SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
|
SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
|
||||||
@@ -237,14 +236,14 @@ public class QRCodeScannerControllerTest extends SysuiTestCase {
|
|||||||
|
|
||||||
verifyActivityDetails("def/.ijk");
|
verifyActivityDetails("def/.ijk");
|
||||||
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
|
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
|
||||||
assertThat(mController.isEnabledForQuickSettings()).isTrue();
|
assertThat(mController.isAbleToOpenCameraApp()).isTrue();
|
||||||
|
|
||||||
mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
|
mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
|
||||||
SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
|
SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
|
||||||
null, false);
|
null, false);
|
||||||
verifyActivityDetails(null);
|
verifyActivityDetails(null);
|
||||||
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
|
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
|
||||||
assertThat(mController.isEnabledForQuickSettings()).isFalse();
|
assertThat(mController.isAbleToOpenCameraApp()).isFalse();
|
||||||
verify(mCallback, times(2)).onQRCodeScannerActivityChanged();
|
verify(mCallback, times(2)).onQRCodeScannerActivityChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,19 +295,19 @@ public class QRCodeScannerControllerTest extends SysuiTestCase {
|
|||||||
/* enableOnLockScreen */ true);
|
/* enableOnLockScreen */ true);
|
||||||
verifyActivityDetails("abc/.def");
|
verifyActivityDetails("abc/.def");
|
||||||
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
|
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
|
||||||
assertThat(mController.isEnabledForQuickSettings()).isTrue();
|
assertThat(mController.isAbleToOpenCameraApp()).isTrue();
|
||||||
|
|
||||||
mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0",
|
mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0",
|
||||||
UserHandle.USER_CURRENT);
|
UserHandle.USER_CURRENT);
|
||||||
verifyActivityDetails("abc/.def");
|
verifyActivityDetails("abc/.def");
|
||||||
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
|
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
|
||||||
assertThat(mController.isEnabledForQuickSettings()).isTrue();
|
assertThat(mController.isAbleToOpenCameraApp()).isTrue();
|
||||||
|
|
||||||
mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "1",
|
mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "1",
|
||||||
UserHandle.USER_CURRENT);
|
UserHandle.USER_CURRENT);
|
||||||
verifyActivityDetails("abc/.def");
|
verifyActivityDetails("abc/.def");
|
||||||
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
|
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
|
||||||
assertThat(mController.isEnabledForQuickSettings()).isTrue();
|
assertThat(mController.isAbleToOpenCameraApp()).isTrue();
|
||||||
// Once from setup + twice from this function
|
// Once from setup + twice from this function
|
||||||
verify(mCallback, times(3)).onQRCodeScannerPreferenceChanged();
|
verify(mCallback, times(3)).onQRCodeScannerPreferenceChanged();
|
||||||
}
|
}
|
||||||
@@ -320,13 +319,13 @@ public class QRCodeScannerControllerTest extends SysuiTestCase {
|
|||||||
/* enableOnLockScreen */ true);
|
/* enableOnLockScreen */ true);
|
||||||
verifyActivityDetails("abc/.def");
|
verifyActivityDetails("abc/.def");
|
||||||
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
|
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
|
||||||
assertThat(mController.isEnabledForQuickSettings()).isTrue();
|
assertThat(mController.isAbleToOpenCameraApp()).isTrue();
|
||||||
|
|
||||||
mController.unregisterQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE,
|
mController.unregisterQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE,
|
||||||
QR_CODE_SCANNER_PREFERENCE_CHANGE);
|
QR_CODE_SCANNER_PREFERENCE_CHANGE);
|
||||||
verifyActivityDetails(null);
|
verifyActivityDetails(null);
|
||||||
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
|
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
|
||||||
assertThat(mController.isEnabledForQuickSettings()).isFalse();
|
assertThat(mController.isAbleToOpenCameraApp()).isFalse();
|
||||||
|
|
||||||
// Unregister once again and make sure it affects the next register event
|
// Unregister once again and make sure it affects the next register event
|
||||||
mController.unregisterQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE,
|
mController.unregisterQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE,
|
||||||
@@ -335,7 +334,7 @@ public class QRCodeScannerControllerTest extends SysuiTestCase {
|
|||||||
QR_CODE_SCANNER_PREFERENCE_CHANGE);
|
QR_CODE_SCANNER_PREFERENCE_CHANGE);
|
||||||
verifyActivityDetails("abc/.def");
|
verifyActivityDetails("abc/.def");
|
||||||
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
|
assertThat(mController.isEnabledForLockScreenButton()).isTrue();
|
||||||
assertThat(mController.isEnabledForQuickSettings()).isTrue();
|
assertThat(mController.isAbleToOpenCameraApp()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -345,7 +344,7 @@ public class QRCodeScannerControllerTest extends SysuiTestCase {
|
|||||||
/* enableOnLockScreen */ false);
|
/* enableOnLockScreen */ false);
|
||||||
assertThat(mController.getIntent()).isNotNull();
|
assertThat(mController.getIntent()).isNotNull();
|
||||||
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
|
assertThat(mController.isEnabledForLockScreenButton()).isFalse();
|
||||||
assertThat(mController.isEnabledForQuickSettings()).isTrue();
|
assertThat(mController.isAbleToOpenCameraApp()).isTrue();
|
||||||
assertThat(getSettingsQRCodeDefaultComponent()).isNull();
|
assertThat(getSettingsQRCodeDefaultComponent()).isNull();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ public class QRCodeScannerTileTest extends SysuiTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testQRCodeTileUnavailable() {
|
public void testQRCodeTileUnavailable() {
|
||||||
when(mController.isEnabledForQuickSettings()).thenReturn(false);
|
when(mController.isAbleToOpenCameraApp()).thenReturn(false);
|
||||||
QSTile.State state = new QSTile.State();
|
QSTile.State state = new QSTile.State();
|
||||||
mTile.handleUpdateState(state, null);
|
mTile.handleUpdateState(state, null);
|
||||||
assertEquals(state.state, Tile.STATE_UNAVAILABLE);
|
assertEquals(state.state, Tile.STATE_UNAVAILABLE);
|
||||||
@@ -116,7 +116,7 @@ public class QRCodeScannerTileTest extends SysuiTestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testQRCodeTileAvailable() {
|
public void testQRCodeTileAvailable() {
|
||||||
when(mController.isEnabledForQuickSettings()).thenReturn(true);
|
when(mController.isAbleToOpenCameraApp()).thenReturn(true);
|
||||||
QSTile.State state = new QSTile.State();
|
QSTile.State state = new QSTile.State();
|
||||||
mTile.handleUpdateState(state, null);
|
mTile.handleUpdateState(state, null);
|
||||||
assertEquals(state.state, Tile.STATE_INACTIVE);
|
assertEquals(state.state, Tile.STATE_INACTIVE);
|
||||||
|
|||||||
Reference in New Issue
Block a user