Merge "Improved re-enablement dialog text." into udc-dev

This commit is contained in:
Ale Nijamkin
2023-06-06 21:14:43 +00:00
committed by Android (Google) Code Review
13 changed files with 108 additions and 112 deletions

View File

@@ -148,11 +148,11 @@ interface CustomizationProviderClient {
*/
val isEnabled: Boolean = true,
/**
* If the affordance is disabled, this is a set of instruction messages to be shown to the
* user when the disabled affordance is selected. The instructions should help the user
* figure out what to do in order to re-neable this affordance.
* If the affordance is disabled, this is the explanation to be shown to the user when the
* disabled affordance is selected. The instructions should help the user figure out what to
* do in order to re-neable this affordance.
*/
val enablementInstructions: List<String>? = null,
val enablementExplanation: String? = null,
/**
* If the affordance is disabled, this is a label for a button shown together with the set
* of instruction messages when the disabled affordance is selected. The button should help
@@ -326,10 +326,10 @@ class CustomizationProviderClientImpl(
Contract.LockScreenQuickAffordances.AffordanceTable.Columns
.IS_ENABLED
)
val enablementInstructionsColumnIndex =
val enablementExplanationColumnIndex =
cursor.getColumnIndex(
Contract.LockScreenQuickAffordances.AffordanceTable.Columns
.ENABLEMENT_INSTRUCTIONS
.ENABLEMENT_EXPLANATION
)
val enablementActionTextColumnIndex =
cursor.getColumnIndex(
@@ -351,7 +351,7 @@ class CustomizationProviderClientImpl(
nameColumnIndex == -1 ||
iconColumnIndex == -1 ||
isEnabledColumnIndex == -1 ||
enablementInstructionsColumnIndex == -1 ||
enablementExplanationColumnIndex == -1 ||
enablementActionTextColumnIndex == -1 ||
enablementActionIntentColumnIndex == -1 ||
configureIntentColumnIndex == -1
@@ -367,13 +367,8 @@ class CustomizationProviderClientImpl(
name = cursor.getString(nameColumnIndex),
iconResourceId = cursor.getInt(iconColumnIndex),
isEnabled = cursor.getInt(isEnabledColumnIndex) == 1,
enablementInstructions =
cursor
.getString(enablementInstructionsColumnIndex)
?.split(
Contract.LockScreenQuickAffordances.AffordanceTable
.ENABLEMENT_INSTRUCTIONS_DELIMITER
),
enablementExplanation =
cursor.getString(enablementExplanationColumnIndex),
enablementActionText =
cursor.getString(enablementActionTextColumnIndex),
enablementActionIntent =

View File

@@ -81,7 +81,6 @@ object CustomizationProviderContract {
const val TABLE_NAME = "affordances"
val URI: Uri =
LOCK_SCREEN_QUICK_AFFORDANCE_BASE_URI.buildUpon().appendPath(TABLE_NAME).build()
const val ENABLEMENT_INSTRUCTIONS_DELIMITER = "]["
object Columns {
/** String. Unique ID for this affordance. */
@@ -96,11 +95,10 @@ object CustomizationProviderContract {
/** 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.
* String. Text to be shown to the user if the affordance is disabled and the user
* selects the affordance.
*/
const val ENABLEMENT_INSTRUCTIONS = "enablement_instructions"
const val ENABLEMENT_EXPLANATION = "enablement_explanation"
/**
* String. Optional label for a button that, when clicked, opens a destination
* activity where the user can re-enable the disabled affordance.

View File

@@ -3016,52 +3016,54 @@
-->
<string name="keyguard_affordance_enablement_dialog_action_template">Open <xliff:g id="appName" example="Wallet">%1$s</xliff:g></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].
<!---
Explains that the wallet app is not available because it is not installed. This is shown as part
of a dialog that explains to the user why they cannot select this shortcut for their lock screen
right now.
[CHAR LIMIT=NONE].
-->
<string name="keyguard_affordance_enablement_dialog_wallet_instruction_1">&#8226; The app is set up</string>
<string name="wallet_quick_affordance_unavailable_install_the_app">To add the Wallet app as a shortcut, make sure the app is installed</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].
<!---
Explains that the wallet app is not available because it is not installed. This is shown as part
of a dialog that explains to the user why they cannot select this shortcut for their lock screen
right now.
[CHAR LIMIT=NONE].
-->
<string name="keyguard_affordance_enablement_dialog_wallet_instruction_2">&#8226; At least one card has been added to Wallet</string>
<string name="wallet_quick_affordance_unavailable_configure_the_app">To add the Wallet app as a shortcut, make sure at least one card has been added</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">&#8226; Install a camera app</string>
<string name="qr_scanner_quick_affordance_unavailable_explanation">To add the QR code scanner as a shortcut, make sure a camera app is installed</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].
Explains that the lock screen shortcut for the "home" app is not available because the app isn't
installed. This is shown as part of a dialog that explains to the user why they cannot select
this shortcut for their lock screen right now. [CHAR LIMIT=NONE].
-->
<string name="keyguard_affordance_enablement_dialog_home_instruction_1">&#8226; The app is set up</string>
<string name="home_quick_affordance_unavailable_install_the_app">To add the Home app as a shortcut, make sure the app is installed</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].
Explains that the lock screen shortcut for the "home" app is not available because the app isn't
configured. This is shown as part of a dialog that explains to the user why they cannot select
this shortcut for their lock screen right now. [CHAR LIMIT=NONE].
-->
<string name="keyguard_affordance_enablement_dialog_home_instruction_2">&#8226; At least one device is available</string>
<string name="home_quick_affordance_unavailable_configure_the_app">&#8226; At least one device is available</string>
<!---
Requirement for the notes 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_notes_app_instruction">Select a default notes app to use the notetaking shortcut</string>
Explains that the notes app is not available. This is shown as part of a dialog that explains to
the user why they cannot select this shortcut for their lock screen right now.
[CHAR LIMIT=NONE].
-->
<string name="notes_app_quick_affordance_unavailable_explanation">Select a default notes app to use the notetaking shortcut</string>
<!---
The action to make the lock screen shortcut for the notes app to be available for the user to
use. This is shown as the action button in the dialog listing the requirements. When all
requirements are met, the app can be accessed through a shortcut button on the lock screen.
[CHAR LIMIT=NONE] -->
use. This is shown as the action button in the dialog explaining why the shortcut isn't
available. [CHAR LIMIT=NONE] -->
<string name="keyguard_affordance_enablement_dialog_notes_app_action">Select app</string>
<!--

View File

@@ -283,7 +283,7 @@ class CustomizationProvider :
Contract.LockScreenQuickAffordances.AffordanceTable.Columns.ICON,
Contract.LockScreenQuickAffordances.AffordanceTable.Columns.IS_ENABLED,
Contract.LockScreenQuickAffordances.AffordanceTable.Columns
.ENABLEMENT_INSTRUCTIONS,
.ENABLEMENT_EXPLANATION,
Contract.LockScreenQuickAffordances.AffordanceTable.Columns
.ENABLEMENT_ACTION_TEXT,
Contract.LockScreenQuickAffordances.AffordanceTable.Columns
@@ -299,10 +299,7 @@ class CustomizationProvider :
representation.name,
representation.iconResourceId,
if (representation.isEnabled) 1 else 0,
representation.instructions?.joinToString(
Contract.LockScreenQuickAffordances.AffordanceTable
.ENABLEMENT_INSTRUCTIONS_DELIMITER
),
representation.explanation,
representation.actionText,
representation.actionIntent?.toUri(Intent.URI_INTENT_SCHEME),
representation.configureIntent?.toUri(Intent.URI_INTENT_SCHEME),

View File

@@ -83,18 +83,31 @@ constructor(
currentServices.isNullOrEmpty() && !componentPackageName.isNullOrEmpty() -> {
// No home app installed but we know which app we want to install.
return disabledPickerState(
explanation =
context.getString(
R.string.home_quick_affordance_unavailable_install_the_app
),
actionText = context.getString(R.string.install_app),
actionIntent = appStoreIntent(context, componentPackageName),
)
}
currentServices.isNullOrEmpty() && componentPackageName.isNullOrEmpty() -> {
// No home app installed and we don't know which app we want to install.
return disabledPickerState()
return disabledPickerState(
explanation =
context.getString(
R.string.home_quick_affordance_unavailable_install_the_app
),
)
}
!hasFavorites -> {
// Home app installed but no favorites selected.
val activityClass = component.getControlsUiController().get().resolveActivity()
return disabledPickerState(
explanation =
context.getString(
R.string.home_quick_affordance_unavailable_configure_the_app
),
actionText = context.getString(R.string.controls_open_app),
actionIntent =
Intent().apply {
@@ -188,21 +201,14 @@ constructor(
}
private fun disabledPickerState(
explanation: String,
actionText: String? = null,
actionIntent: Intent? = null,
): KeyguardQuickAffordanceConfig.PickerScreenState.Disabled {
check(actionIntent == null || actionText != null)
return KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
instructions =
listOf(
context.getString(
R.string.keyguard_affordance_enablement_dialog_home_instruction_1
),
context.getString(
R.string.keyguard_affordance_enablement_dialog_home_instruction_2
),
),
explanation = explanation,
actionText = actionText,
actionIntent = actionIntent,
)

View File

@@ -87,8 +87,8 @@ interface KeyguardQuickAffordanceConfig {
* described in the instructions.
*/
data class Disabled(
/** List of human-readable instructions for setting up the quick affordance. */
val instructions: List<String>,
/** Human-readable explanation as to why the quick affordance is current disabled. */
val explanation: 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.
@@ -101,7 +101,7 @@ interface KeyguardQuickAffordanceConfig {
val actionIntent: Intent? = null,
) : PickerScreenState() {
init {
check(instructions.isNotEmpty()) { "Instructions must not be empty!" }
check(explanation.isNotEmpty()) { "Explanation must not be empty!" }
check(
(actionText.isNullOrEmpty() && actionIntent == null) ||
(!actionText.isNullOrEmpty() && actionIntent != null)

View File

@@ -80,16 +80,14 @@ constructor(
return when {
!controller.isAvailableOnDevice ->
KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
!controller.isAbleToOpenCameraApp ->
!controller.isAbleToOpenCameraApp -> {
KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
instructions =
listOf(
context.getString(
R.string
.keyguard_affordance_enablement_dialog_qr_scanner_instruction
),
explanation =
context.getString(
R.string.qr_scanner_quick_affordance_unavailable_explanation
),
)
}
else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default()
}
}

View File

@@ -100,19 +100,20 @@ constructor(
return when {
!walletController.walletClient.isWalletServiceAvailable ->
KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
!isWalletAvailable() || queryCards().isEmpty() -> {
!isWalletAvailable() ->
KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
instructions =
listOf(
context.getString(
R.string.keyguard_affordance_enablement_dialog_wallet_instruction_1
),
context.getString(
R.string.keyguard_affordance_enablement_dialog_wallet_instruction_2
),
explanation =
context.getString(
R.string.wallet_quick_affordance_unavailable_install_the_app
),
)
queryCards().isEmpty() ->
KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
explanation =
context.getString(
R.string.wallet_quick_affordance_unavailable_configure_the_app
),
)
}
else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default()
}
}
@@ -147,9 +148,7 @@ constructor(
}
private fun isWalletAvailable() =
with(walletController.walletClient) {
isWalletServiceAvailable && isWalletFeatureAvailable
}
with(walletController.walletClient) { isWalletServiceAvailable && isWalletFeatureAvailable }
private fun state(
isFeatureEnabled: Boolean,

View File

@@ -175,7 +175,7 @@ constructor(
iconResourceId = config.pickerIconResourceId,
isEnabled =
pickerState is KeyguardQuickAffordanceConfig.PickerScreenState.Default,
instructions = disabledPickerState?.instructions,
explanation = disabledPickerState?.explanation,
actionText = disabledPickerState?.actionText,
actionIntent =
disabledPickerState?.actionIntent?.apply {
@@ -191,6 +191,7 @@ constructor(
},
)
}
.sortedBy { it.name }
}
/**

View File

@@ -32,8 +32,8 @@ data class KeyguardQuickAffordancePickerRepresentation(
/** 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, a user-visible explanation as to why. */
val explanation: String? = null,
/**
* If not enabled, an optional label for a button that takes the user to a destination where

View File

@@ -122,17 +122,18 @@ constructor(
isEnabled && isDefaultNotesAppSet -> PickerScreenState.Default()
isEnabled -> {
PickerScreenState.Disabled(
listOf(
explanation =
context.getString(
R.string.keyguard_affordance_enablement_dialog_notes_app_instruction
)
),
context.getString(
R.string.keyguard_affordance_enablement_dialog_notes_app_action
),
Intent(ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE).apply {
setPackage(context.packageName)
}
R.string.notes_app_quick_affordance_unavailable_explanation
),
actionText =
context.getString(
R.string.keyguard_affordance_enablement_dialog_notes_app_action
),
actionIntent =
Intent(ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE).apply {
setPackage(context.packageName)
},
)
}
else -> PickerScreenState.UnavailableOnDevice

View File

@@ -53,7 +53,7 @@ class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
whenever(controller.intent).thenReturn(INTENT_1)
underTest = QrCodeScannerKeyguardQuickAffordanceConfig(mock(), controller)
underTest = QrCodeScannerKeyguardQuickAffordanceConfig(context, controller)
}
@Test
@@ -77,22 +77,21 @@ class QrCodeScannerKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
}
@Test
fun affordance_scannerActivityChanged_deliversModelWithUpdatedIntent() =
runBlockingTest {
whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
verify(controller).addCallback(callbackCaptor.capture())
fun affordance_scannerActivityChanged_deliversModelWithUpdatedIntent() = runBlockingTest {
whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
verify(controller).addCallback(callbackCaptor.capture())
whenever(controller.intent).thenReturn(INTENT_2)
callbackCaptor.value.onQRCodeScannerActivityChanged()
whenever(controller.intent).thenReturn(INTENT_2)
callbackCaptor.value.onQRCodeScannerActivityChanged()
assertVisibleState(latest)
assertVisibleState(latest)
job.cancel()
verify(controller).removeCallback(callbackCaptor.value)
}
job.cancel()
verify(controller).removeCallback(callbackCaptor.value)
}
@Test
fun affordance_scannerPreferenceChanged_deliversVisibleModel() = runBlockingTest {

View File

@@ -296,8 +296,8 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() {
assertThat(pickerScreenState is KeyguardQuickAffordanceConfig.PickerScreenState.Disabled)
.isTrue()
val disabled = pickerScreenState as KeyguardQuickAffordanceConfig.PickerScreenState.Disabled
assertThat(disabled.instructions)
.isEqualTo(listOf("Select a default notes app to use the notetaking shortcut"))
assertThat(disabled.explanation)
.isEqualTo("Select a default notes app to use the notetaking shortcut")
assertThat(disabled.actionText).isEqualTo("Select app")
assertThat(disabled.actionIntent?.action)
.isEqualTo(ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE)