Support for unavailable and disabled affordances.
End-to-end implementation (across all layers of both System UI and the shared library) for supporting a picker state for affordances, allowing config implementations on the system UI to easily convey whether their affordance should be hidden on the device or should be marked as disabled and show a dialog with instructions for re-enablement. Bug: 256695447,256695925,256695924 Test: unit tests still pass. Was able to see on a tablet that doesn't support NFC that wallet is gone as an option and, when I deselected all home devices, the home affordance was disabled with the correct instruction dialog when picked. Then, marking at least one home device as favorite, the picker allowed me to select "home" again. Change-Id: I77637f28cdacecaa02ab33603bbc008a501d03d1
This commit is contained in:
committed by
Ale Nijamkin
parent
a5a9546399
commit
2bc579152d
@@ -2798,4 +2798,50 @@
|
|||||||
|
|
||||||
<!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]-->
|
<!-- Learn more URL for the log access confirmation dialog. [DO NOT TRANSLATE]-->
|
||||||
<string name="log_access_confirmation_learn_more" translatable="false"><a href="https://support.google.com/android?p=system_logs#topic=7313011">Learn more</a></string>
|
<string name="log_access_confirmation_learn_more" translatable="false"><a href="https://support.google.com/android?p=system_logs#topic=7313011">Learn more</a></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