Improved re-enablement dialog text.
Instead of a list of instructions, use a single explanation text. Bug: 282079529 Test: Manually verified. See comment#3 for screenshots. Change-Id: Ib8ac23783dbe3713fce7c6925a7101fc99f6a259
This commit is contained in:
@@ -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 =
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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">• 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">• 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">• 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">• 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">• At least one device is available</string>
|
||||
<string name="home_quick_affordance_unavailable_configure_the_app">• 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>
|
||||
|
||||
<!--
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user