diff options
| author | 2023-06-06 04:08:36 +0000 | |
|---|---|---|
| committer | 2023-06-06 04:08:36 +0000 | |
| commit | 48c068b41ef4c5dafef8f4cff63406b21e66b54f (patch) | |
| tree | 5ecd4c5f8327041b7576b00c060797497a0cb3d7 | |
| parent | a2f5366fcdcec43d5df13c90a28c4ab63f9f90ad (diff) | |
| parent | 00ce757dbfd81be0a48ba877e34271133bda2119 (diff) | |
Merge "More actionable reenablement dialogs for quick affordances." into udc-dev am: 00ce757dbf
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23566198
Change-Id: I59a8e5322024e347d2b4b029fbb15b18b0a32a87
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
19 files changed, 339 insertions, 146 deletions
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index fe90caf2646c..0a9a1842dd77 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -913,8 +913,9 @@ android:excludeFromRecents="true" android:launchMode="singleInstance" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden" - android:visibleToInstantApps="true"> - </activity> + android:visibleToInstantApps="true" + android:exported="true" + /> <activity android:name=".controls.management.ControlsEditingActivity" android:label="@string/controls_menu_edit" @@ -947,8 +948,9 @@ android:finishOnTaskLaunch="true" android:launchMode="singleInstance" android:configChanges="screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden|orientation" - android:visibleToInstantApps="true"> - </activity> + android:visibleToInstantApps="true" + android:exported="true" + /> <activity android:name=".wallet.ui.WalletActivity" android:label="@string/wallet_title" diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt index cd9fb886a4e6..519ae0a59702 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt @@ -163,15 +163,14 @@ interface CustomizationProviderClient { */ val enablementActionText: String? = null, /** - * If the affordance is disabled, this is a "component name" of the format - * `packageName/action` to be used as an `Intent` for `startActivity` when the action button - * (shown together with the set of instruction messages when the disabled affordance is - * selected) is clicked by the user. The button should help send the user to a flow that - * would help them achieve the instructions and re-enable this affordance. + * If the affordance is disabled, this is an [Intent] to be used with `startActivity` when + * the action button (shown together with the set of instruction messages when the disabled + * affordance is selected) is clicked by the user. The button should help send the user to a + * flow that would help them achieve the instructions and re-enable this affordance. * * If `null`, the button should not be shown. */ - val enablementActionComponentName: String? = null, + val enablementActionIntent: Intent? = null, /** Optional [Intent] to use to start an activity to configure this affordance. */ val configureIntent: Intent? = null, ) @@ -337,10 +336,10 @@ class CustomizationProviderClientImpl( Contract.LockScreenQuickAffordances.AffordanceTable.Columns .ENABLEMENT_ACTION_TEXT ) - val enablementComponentNameColumnIndex = + val enablementActionIntentColumnIndex = cursor.getColumnIndex( Contract.LockScreenQuickAffordances.AffordanceTable.Columns - .ENABLEMENT_COMPONENT_NAME + .ENABLEMENT_ACTION_INTENT ) val configureIntentColumnIndex = cursor.getColumnIndex( @@ -354,7 +353,7 @@ class CustomizationProviderClientImpl( isEnabledColumnIndex == -1 || enablementInstructionsColumnIndex == -1 || enablementActionTextColumnIndex == -1 || - enablementComponentNameColumnIndex == -1 || + enablementActionIntentColumnIndex == -1 || configureIntentColumnIndex == -1 ) { return@buildList @@ -377,12 +376,18 @@ class CustomizationProviderClientImpl( ), enablementActionText = cursor.getString(enablementActionTextColumnIndex), - enablementActionComponentName = - cursor.getString(enablementComponentNameColumnIndex), + enablementActionIntent = + cursor + .getString(enablementActionIntentColumnIndex) + ?.toIntent( + affordanceId = affordanceId, + ), configureIntent = cursor .getString(configureIntentColumnIndex) - ?.toIntent(affordanceId = affordanceId), + ?.toIntent( + affordanceId = affordanceId, + ), ) ) } @@ -524,7 +529,7 @@ class CustomizationProviderClientImpl( affordanceId: String, ): Intent? { return try { - Intent.parseUri(this, 0) + Intent.parseUri(this, Intent.URI_INTENT_SCHEME) } catch (e: URISyntaxException) { Log.w(TAG, "Cannot parse Uri into Intent for affordance with ID \"$affordanceId\"!") null diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt index f9e8aafcfa47..7f5fb2580a5b 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt @@ -82,7 +82,6 @@ object CustomizationProviderContract { val URI: Uri = LOCK_SCREEN_QUICK_AFFORDANCE_BASE_URI.buildUpon().appendPath(TABLE_NAME).build() const val ENABLEMENT_INSTRUCTIONS_DELIMITER = "][" - const val COMPONENT_NAME_SEPARATOR = "/" object Columns { /** String. Unique ID for this affordance. */ @@ -108,11 +107,11 @@ object CustomizationProviderContract { */ 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. + * String. Optional URI-formatted `Intent` (formatted using + * `Intent#toUri(Intent.URI_INTENT_SCHEME)` used 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" + const val ENABLEMENT_ACTION_INTENT = "enablement_action_intent" /** * Byte array. Optional parcelled `Intent` to use to start an activity that can be * used to configure the affordance. diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index c3651cfa36e7..166bd2ac4439 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -902,4 +902,16 @@ <!-- Time (in ms) to delay the bouncer views from showing when passive auth may be used for device entry. --> <integer name="primary_bouncer_passive_auth_delay">500</integer> + + <!-- + The package name of the app store app. If empty, features using this should be gracefully + disabled. + --> + <string name="config_appStorePackageName" translatable="false"></string> + + <!-- Template for a link that leads to an app page in the relevant app store. If empty, + features using this should be gracefully disabled. If not empty, it must include a + "$packageName" part that will be replaced by the code with the package name of the target app. + --> + <string name="config_appStoreAppLinkTemplate" translatable="false"></string> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 58be3e9d4e2f..e7796727992b 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3153,4 +3153,11 @@ <!--- Content of toast triggered when the notes app entry point is triggered without setting a default notes app. [CHAR LIMIT=NONE] --> <string name="set_default_notes_app_toast_content">Set default notes app in Settings</string> + + <!-- + Label for a button that, when clicked, sends the user to the app store to install an app. + + [CHAR LIMIT=64]. + --> + <string name="install_app">Install app</string> </resources> diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfiguration.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfiguration.kt index d7d17006168e..c92180647ab3 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfiguration.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfiguration.kt @@ -17,6 +17,7 @@ package com.android.systemui.controls.controller interface ControlsTileResourceConfiguration { + fun getPackageName(): String? fun getTileTitleId(): Int fun getTileImageId(): Int -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImpl.kt index c96d3d4a2602..02490605fbb8 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImpl.kt @@ -20,12 +20,14 @@ import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import javax.inject.Inject -/** - * Default Instance for ControlsTileResourceConfiguration. - */ +/** Default Instance for ControlsTileResourceConfiguration. */ @SysUISingleton -class ControlsTileResourceConfigurationImpl @Inject constructor() - : ControlsTileResourceConfiguration { +class ControlsTileResourceConfigurationImpl @Inject constructor() : + ControlsTileResourceConfiguration { + override fun getPackageName(): String? { + return null + } + override fun getTileTitleId(): Int { return R.string.quick_controls_title } @@ -33,4 +35,4 @@ class ControlsTileResourceConfigurationImpl @Inject constructor() override fun getTileImageId(): Int { return R.drawable.controls_icon } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt index 7509a8ad0c88..94e563319524 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt @@ -29,9 +29,9 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.KeyguardStateController import dagger.Lazy -import kotlinx.coroutines.flow.StateFlow import java.util.Optional import javax.inject.Inject +import kotlinx.coroutines.flow.StateFlow /** * Pseudo-component to inject into classes outside `com.android.systemui.controls`. @@ -40,26 +40,26 @@ import javax.inject.Inject * instantiated if `featureEnabled` is true. Can also be queried for the availability of controls. */ @SysUISingleton -class ControlsComponent @Inject constructor( - @ControlsFeatureEnabled private val featureEnabled: Boolean, - private val context: Context, - private val lazyControlsController: Lazy<ControlsController>, - private val lazyControlsUiController: Lazy<ControlsUiController>, - private val lazyControlsListingController: Lazy<ControlsListingController>, - private val lockPatternUtils: LockPatternUtils, - private val keyguardStateController: KeyguardStateController, - private val userTracker: UserTracker, - controlsSettingsRepository: ControlsSettingsRepository, - optionalControlsTileResourceConfiguration: Optional<ControlsTileResourceConfiguration> +class ControlsComponent +@Inject +constructor( + @ControlsFeatureEnabled private val featureEnabled: Boolean, + private val context: Context, + private val lazyControlsController: Lazy<ControlsController>, + private val lazyControlsUiController: Lazy<ControlsUiController>, + private val lazyControlsListingController: Lazy<ControlsListingController>, + private val lockPatternUtils: LockPatternUtils, + private val keyguardStateController: KeyguardStateController, + private val userTracker: UserTracker, + controlsSettingsRepository: ControlsSettingsRepository, + optionalControlsTileResourceConfiguration: Optional<ControlsTileResourceConfiguration> ) { val canShowWhileLockedSetting: StateFlow<Boolean> = - controlsSettingsRepository.canShowControlsInLockscreen + controlsSettingsRepository.canShowControlsInLockscreen private val controlsTileResourceConfiguration: ControlsTileResourceConfiguration = - optionalControlsTileResourceConfiguration.orElse( - ControlsTileResourceConfigurationImpl() - ) + optionalControlsTileResourceConfiguration.orElse(ControlsTileResourceConfigurationImpl()) fun getControlsController(): Optional<ControlsController> { return if (featureEnabled) Optional.of(lazyControlsController.get()) else Optional.empty() @@ -77,9 +77,7 @@ class ControlsComponent @Inject constructor( } } - /** - * @return true if controls are feature-enabled and the user has the setting enabled - */ + /** @return true if controls are feature-enabled and the user has the setting enabled */ fun isEnabled() = featureEnabled /** @@ -90,8 +88,10 @@ class ControlsComponent @Inject constructor( */ fun getVisibility(): Visibility { if (!isEnabled()) return Visibility.UNAVAILABLE - if (lockPatternUtils.getStrongAuthForUser(userTracker.userHandle.identifier) - == STRONG_AUTH_REQUIRED_AFTER_BOOT) { + if ( + lockPatternUtils.getStrongAuthForUser(userTracker.userHandle.identifier) == + STRONG_AUTH_REQUIRED_AFTER_BOOT + ) { return Visibility.AVAILABLE_AFTER_UNLOCK } if (!canShowWhileLockedSetting.value && !keyguardStateController.isUnlocked()) { @@ -102,7 +102,13 @@ class ControlsComponent @Inject constructor( } enum class Visibility { - AVAILABLE, AVAILABLE_AFTER_UNLOCK, UNAVAILABLE + AVAILABLE, + AVAILABLE_AFTER_UNLOCK, + UNAVAILABLE + } + + fun getPackageName(): String? { + return controlsTileResourceConfiguration.getPackageName() } fun getTileTitleId(): Int { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt index 27a5974e6299..064a44a29d31 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt @@ -20,6 +20,7 @@ package com.android.systemui.keyguard import android.content.ContentProvider import android.content.ContentValues import android.content.Context +import android.content.Intent import android.content.UriMatcher import android.content.pm.PackageManager import android.content.pm.ProviderInfo @@ -286,7 +287,7 @@ class CustomizationProvider : Contract.LockScreenQuickAffordances.AffordanceTable.Columns .ENABLEMENT_ACTION_TEXT, Contract.LockScreenQuickAffordances.AffordanceTable.Columns - .ENABLEMENT_COMPONENT_NAME, + .ENABLEMENT_ACTION_INTENT, Contract.LockScreenQuickAffordances.AffordanceTable.Columns.CONFIGURE_INTENT, ) ) @@ -303,8 +304,8 @@ class CustomizationProvider : .ENABLEMENT_INSTRUCTIONS_DELIMITER ), representation.actionText, - representation.actionComponentName, - representation.configureIntent?.toUri(0), + representation.actionIntent?.toUri(Intent.URI_INTENT_SCHEME), + representation.configureIntent?.toUri(Intent.URI_INTENT_SCHEME), ) ) } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt index abb63c4d34ce..0991b5f149db 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.data.quickaffordance +import android.content.ComponentName import android.content.Context import android.content.Intent import androidx.annotation.DrawableRes @@ -34,6 +35,7 @@ import com.android.systemui.controls.ui.ControlsActivity import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.Companion.appStoreIntent import com.android.systemui.util.kotlin.getOrNull import javax.inject.Inject import kotlinx.coroutines.channels.awaitClose @@ -76,18 +78,31 @@ constructor( 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_home_instruction_1 - ), - context.getString( - R.string.keyguard_affordance_enablement_dialog_home_instruction_2 - ), - ), - ) + val componentPackageName = component.getPackageName() + when { + currentServices.isNullOrEmpty() && !componentPackageName.isNullOrEmpty() -> { + // No home app installed but we know which app we want to install. + return disabledPickerState( + 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() + } + !hasFavorites -> { + // Home app installed but no favorites selected. + val activityClass = component.getControlsUiController().get().resolveActivity() + return disabledPickerState( + actionText = context.getString(R.string.controls_open_app), + actionIntent = + Intent().apply { + component = ComponentName(context, activityClass) + putExtra(ControlsUiController.EXTRA_ANIMATE, true) + }, + ) + } } return KeyguardQuickAffordanceConfig.PickerScreenState.Default() @@ -172,6 +187,27 @@ constructor( } } + private fun disabledPickerState( + 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 + ), + ), + actionText = actionText, + actionIntent = actionIntent, + ) + } + companion object { private const val TAG = "HomeControlsKeyguardQuickAffordanceConfig" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt index 28dc5bdcc45f..42fe9bcf8ec4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt @@ -18,11 +18,13 @@ package com.android.systemui.keyguard.data.quickaffordance import android.app.AlertDialog +import android.content.Context import android.content.Intent +import android.net.Uri +import com.android.systemui.R import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon import com.android.systemui.keyguard.shared.quickaffordance.ActivationState -import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract import kotlinx.coroutines.flow.Flow /** Defines interface that can act as data source for a single quick affordance model. */ @@ -80,9 +82,9 @@ interface KeyguardQuickAffordanceConfig { /** * 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. + * show the given instructions to the user. If [actionText] and [actionIntent] 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. */ @@ -93,24 +95,22 @@ interface KeyguardQuickAffordanceConfig { */ 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.LockScreenQuickAffordances.AffordanceTable.COMPONENT_NAME_SEPARATOR] for - * convenience, you can use the [componentName] function. + * Optional [Intent] that opens an `Activity` for the user to be able to set up the + * quick affordance and make it enabled. */ - val actionComponentName: String? = null, + val actionIntent: Intent? = null, ) : PickerScreenState() { init { check(instructions.isNotEmpty()) { "Instructions must not be empty!" } check( - (actionText.isNullOrEmpty() && actionComponentName.isNullOrEmpty()) || - (!actionText.isNullOrEmpty() && !actionComponentName.isNullOrEmpty()) + (actionText.isNullOrEmpty() && actionIntent == null) || + (!actionText.isNullOrEmpty() && actionIntent != null) ) { - "actionText and actionComponentName must either both be null/empty or both be" + - " non-empty!" + """ + actionText and actionIntent must either both be null/empty or both be + non-null and non-empty! + """ + .trimIndent() } } } @@ -163,17 +163,33 @@ interface KeyguardQuickAffordanceConfig { } companion object { - fun componentName( - packageName: String? = null, - action: String?, - ): String? { - return when { - action.isNullOrEmpty() -> null - !packageName.isNullOrEmpty() -> - "$packageName${Contract.LockScreenQuickAffordances.AffordanceTable - .COMPONENT_NAME_SEPARATOR}$action" - else -> action + + /** + * Returns an [Intent] that can be used to start an activity that opens the app store app to + * a page showing the app with the passed-in [packageName]. + * + * If the feature isn't enabled on this device/variant/configuration, a `null` will be + * returned. + */ + fun appStoreIntent(context: Context, packageName: String?): Intent? { + if (packageName.isNullOrEmpty()) { + return null + } + + val appStorePackageName = context.getString(R.string.config_appStorePackageName) + val linkTemplate = context.getString(R.string.config_appStoreAppLinkTemplate) + if (appStorePackageName.isEmpty() || linkTemplate.isEmpty()) { + return null + } + + check(linkTemplate.contains(APP_PACKAGE_NAME_PLACEHOLDER)) + + return Intent(Intent.ACTION_VIEW).apply { + setPackage(appStorePackageName) + data = Uri.parse(linkTemplate.replace(APP_PACKAGE_NAME_PLACEHOLDER, packageName)) } } + + private const val APP_PACKAGE_NAME_PLACEHOLDER = "\$packageName" } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt index 96c94d7d64b6..bd3b83c00f57 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt @@ -18,6 +18,7 @@ package com.android.systemui.keyguard.data.repository import android.content.Context +import android.content.Intent import android.os.UserHandle import android.util.LayoutDirection import com.android.systemui.Dumpable @@ -176,8 +177,18 @@ constructor( pickerState is KeyguardQuickAffordanceConfig.PickerScreenState.Default, instructions = disabledPickerState?.instructions, actionText = disabledPickerState?.actionText, - actionComponentName = disabledPickerState?.actionComponentName, - configureIntent = defaultPickerState?.configureIntent, + actionIntent = + disabledPickerState?.actionIntent?.apply { + addFlags( + Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK + ) + }, + configureIntent = + defaultPickerState?.configureIntent?.apply { + addFlags( + Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK + ) + }, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt index e7e915940290..3c96eafe867a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt @@ -41,11 +41,8 @@ data class KeyguardQuickAffordancePickerRepresentation( */ 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, + /** Optional [Intent] to use to start an activity to re-enable this affordance. */ + val actionIntent: Intent? = null, /** Optional [Intent] to use to start an activity to configure this affordance. */ val configureIntent: Intent? = null, diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt index 0ce20cdbe63d..8da7cec41dd7 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt @@ -19,6 +19,7 @@ package com.android.systemui.notetask.quickaffordance import android.app.role.OnRoleHoldersChangedListener import android.app.role.RoleManager import android.content.Context +import android.content.Intent import android.hardware.input.InputSettings import android.os.Build import android.os.UserHandle @@ -42,7 +43,6 @@ import com.android.systemui.notetask.NoteTaskController import com.android.systemui.notetask.NoteTaskEnabledKey import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE import com.android.systemui.notetask.NoteTaskInfoResolver -import com.android.systemui.shared.customization.data.content.CustomizationProviderContract.LockScreenQuickAffordances.AffordanceTable.COMPONENT_NAME_SEPARATOR import com.android.systemui.stylus.StylusManager import dagger.Lazy import java.util.concurrent.Executor @@ -130,8 +130,9 @@ constructor( context.getString( R.string.keyguard_affordance_enablement_dialog_notes_app_action ), - "${context.packageName}$COMPONENT_NAME_SEPARATOR" + - "$ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE", + Intent(ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE).apply { + setPackage(context.packageName) + } ) } else -> PickerScreenState.UnavailableOnDevice diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt index 7fb088eb783f..bd4e8da6b326 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt @@ -20,6 +20,7 @@ import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith @@ -27,16 +28,20 @@ import org.junit.runner.RunWith @RunWith(AndroidTestingRunner::class) @SmallTest class ControlsTileResourceConfigurationImplTest : SysuiTestCase() { + + @Test + fun getPackageName() { + assertThat(ControlsTileResourceConfigurationImpl().getPackageName()).isNull() + } + @Test fun getTileImageId() { val instance = ControlsTileResourceConfigurationImpl() - assertEquals(instance.getTileImageId(), - R.drawable.controls_icon) + assertEquals(instance.getTileImageId(), R.drawable.controls_icon) } @Test fun getTileTitleId() { val instance = ControlsTileResourceConfigurationImpl() - assertEquals(instance.getTileTitleId(), - R.string.quick_controls_title) + assertEquals(instance.getTileTitleId(), R.string.quick_controls_title) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt index 9144b13c7f3e..0b27bc9c7dbd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt @@ -22,10 +22,10 @@ import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT import com.android.systemui.SysuiTestCase -import com.android.systemui.controls.settings.FakeControlsSettingsRepository import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.controller.ControlsTileResourceConfiguration import com.android.systemui.controls.management.ControlsListingController +import com.android.systemui.controls.settings.FakeControlsSettingsRepository import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.KeyguardStateController @@ -40,32 +40,25 @@ import org.junit.runner.RunWith import org.mockito.Answers import org.mockito.Mock import org.mockito.Mockito +import org.mockito.Mockito.any import org.mockito.Mockito.anyInt import org.mockito.Mockito.`when` -import org.mockito.Mockito.any import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidTestingRunner::class) class ControlsComponentTest : SysuiTestCase() { - @Mock - private lateinit var controller: ControlsController - @Mock - private lateinit var uiController: ControlsUiController - @Mock - private lateinit var listingController: ControlsListingController - @Mock - private lateinit var keyguardStateController: KeyguardStateController - @Mock(answer = Answers.RETURNS_DEEP_STUBS) - private lateinit var userTracker: UserTracker - @Mock - private lateinit var lockPatternUtils: LockPatternUtils + @Mock private lateinit var controller: ControlsController + @Mock private lateinit var uiController: ControlsUiController + @Mock private lateinit var listingController: ControlsListingController + @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var userTracker: UserTracker + @Mock private lateinit var lockPatternUtils: LockPatternUtils @Mock private lateinit var optionalControlsTileResourceConfiguration: - Optional<ControlsTileResourceConfiguration> - @Mock - private lateinit var controlsTileResourceConfiguration: ControlsTileResourceConfiguration + Optional<ControlsTileResourceConfiguration> + @Mock private lateinit var controlsTileResourceConfiguration: ControlsTileResourceConfiguration private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository @@ -123,8 +116,7 @@ class ControlsComponentTest : SysuiTestCase() { @Test fun testFeatureEnabledAndCannotShowOnLockScreenVisibility() { - `when`(lockPatternUtils.getStrongAuthForUser(anyInt())) - .thenReturn(STRONG_AUTH_NOT_REQUIRED) + `when`(lockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED) `when`(keyguardStateController.isUnlocked()).thenReturn(false) controlsSettingsRepository.setCanShowControlsInLockscreen(false) val component = setupComponent(true) @@ -134,8 +126,7 @@ class ControlsComponentTest : SysuiTestCase() { @Test fun testFeatureEnabledAndCanShowOnLockScreenVisibility() { - `when`(lockPatternUtils.getStrongAuthForUser(anyInt())) - .thenReturn(STRONG_AUTH_NOT_REQUIRED) + `when`(lockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED) `when`(keyguardStateController.isUnlocked()).thenReturn(false) controlsSettingsRepository.setCanShowControlsInLockscreen(true) val component = setupComponent(true) @@ -146,8 +137,7 @@ class ControlsComponentTest : SysuiTestCase() { @Test fun testFeatureEnabledAndCanShowWhileUnlockedVisibility() { controlsSettingsRepository.setCanShowControlsInLockscreen(false) - `when`(lockPatternUtils.getStrongAuthForUser(anyInt())) - .thenReturn(STRONG_AUTH_NOT_REQUIRED) + `when`(lockPatternUtils.getStrongAuthForUser(anyInt())).thenReturn(STRONG_AUTH_NOT_REQUIRED) `when`(keyguardStateController.isUnlocked()).thenReturn(true) val component = setupComponent(true) @@ -158,8 +148,7 @@ class ControlsComponentTest : SysuiTestCase() { fun testGetTileImageId() { val tileImageId = 0 - `when`(controlsTileResourceConfiguration.getTileImageId()) - .thenReturn(tileImageId) + `when`(controlsTileResourceConfiguration.getTileImageId()).thenReturn(tileImageId) val component = setupComponent(true) assertEquals(component.getTileImageId(), tileImageId) } @@ -168,12 +157,19 @@ class ControlsComponentTest : SysuiTestCase() { fun testGetTileTitleId() { val tileTitleId = 0 - `when`(controlsTileResourceConfiguration.getTileTitleId()) - .thenReturn(tileTitleId) + `when`(controlsTileResourceConfiguration.getTileTitleId()).thenReturn(tileTitleId) val component = setupComponent(true) assertEquals(component.getTileTitleId(), tileTitleId) } + @Test + fun getPackageName() { + val packageName = "packageName" + `when`(controlsTileResourceConfiguration.getPackageName()).thenReturn(packageName) + val component = setupComponent(true) + assertEquals(component.getPackageName(), packageName) + } + private fun setupComponent(enabled: Boolean): ControlsComponent { return ControlsComponent( enabled, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt index f8cb40885d21..1815ea9530e2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.data.quickaffordance +import android.app.Activity import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase @@ -24,6 +25,7 @@ import com.android.systemui.controls.ControlsServiceInfo import com.android.systemui.controls.controller.ControlsController import com.android.systemui.controls.dagger.ControlsComponent import com.android.systemui.controls.management.ControlsListingController +import com.android.systemui.controls.ui.ControlsUiController import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat @@ -75,6 +77,7 @@ class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTes @Mock private lateinit var component: ControlsComponent @Mock private lateinit var controlsController: ControlsController @Mock private lateinit var controlsListingController: ControlsListingController + @Mock private lateinit var controlsUiController: ControlsUiController @Mock private lateinit var controlsServiceInfo: ControlsServiceInfo @Captor private lateinit var callbackCaptor: @@ -98,6 +101,8 @@ class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTes whenever(component.getControlsController()).thenReturn(Optional.of(controlsController)) whenever(component.getControlsListingController()) .thenReturn(Optional.of(controlsListingController)) + whenever(controlsUiController.resolveActivity()).thenReturn(FakeActivity::class.java) + whenever(component.getControlsUiController()).thenReturn(Optional.of(controlsUiController)) if (hasPanels) { whenever(controlsServiceInfo.panelActivity).thenReturn(mock()) } @@ -178,4 +183,6 @@ class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTes ) job.cancel() } + + private class FakeActivity : Activity() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfigTest.kt new file mode 100644 index 000000000000..7941a23fe30d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfigTest.kt @@ -0,0 +1,90 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.data.quickaffordance + +import android.content.Intent +import android.net.Uri +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@SmallTest +@RunWith(JUnit4::class) +class KeyguardQuickAffordanceConfigTest : SysuiTestCase() { + + @Test + fun appStoreIntent() { + overrideResource(R.string.config_appStorePackageName, "app.store.package.name") + overrideResource(R.string.config_appStoreAppLinkTemplate, "prefix?id=\$packageName") + val packageName = "com.app.package.name" + + val intent = KeyguardQuickAffordanceConfig.appStoreIntent(context, packageName) + + assertThat(intent).isNotNull() + assertThat(intent?.`package`).isEqualTo("app.store.package.name") + assertThat(intent?.action).isEqualTo(Intent.ACTION_VIEW) + assertThat(intent?.data).isEqualTo(Uri.parse("prefix?id=$packageName")) + } + + @Test + fun appStoreIntent_packageNameNotConfigured_returnNull() { + overrideResource(R.string.config_appStorePackageName, "") + overrideResource(R.string.config_appStoreAppLinkTemplate, "prefix?id=\$packageName") + val packageName = "com.app.package.name" + + val intent = KeyguardQuickAffordanceConfig.appStoreIntent(context, packageName) + + assertThat(intent).isNull() + } + + @Test(expected = IllegalStateException::class) + fun appStoreIntent_packageNameMisconfigured_throwsIllegalStateException() { + overrideResource(R.string.config_appStorePackageName, "app.store.package.name") + overrideResource( + R.string.config_appStoreAppLinkTemplate, + "prefix?id=\$misconfiguredPackageName" + ) + val packageName = "com.app.package.name" + + KeyguardQuickAffordanceConfig.appStoreIntent(context, packageName) + } + + @Test + fun appStoreIntent_linkTemplateNotConfigured_returnNull() { + overrideResource(R.string.config_appStorePackageName, "app.store.package.name") + overrideResource(R.string.config_appStoreAppLinkTemplate, "") + val packageName = "com.app.package.name" + + val intent = KeyguardQuickAffordanceConfig.appStoreIntent(context, packageName) + + assertThat(intent).isNull() + } + + @Test + fun appStoreIntent_appPackageNameNull_returnNull() { + overrideResource(R.string.config_appStorePackageName, "app.store.package.name") + overrideResource(R.string.config_appStoreAppLinkTemplate, "prefix?id=\$packageName") + + val intent = KeyguardQuickAffordanceConfig.appStoreIntent(context, null) + + assertThat(intent).isNull() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt index 452658004733..cc64cc270b97 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt @@ -40,7 +40,6 @@ import com.android.systemui.notetask.LaunchNotesRoleSettingsTrampolineActivity.C import com.android.systemui.notetask.NoteTaskController import com.android.systemui.notetask.NoteTaskEntryPoint import com.android.systemui.notetask.NoteTaskInfoResolver -import com.android.systemui.shared.customization.data.content.CustomizationProviderContract.LockScreenQuickAffordances.AffordanceTable.COMPONENT_NAME_SEPARATOR import com.android.systemui.stylus.StylusManager import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any @@ -283,7 +282,7 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() { } @Test - fun getPickerScreenState_nodefaultNoteAppSet_shouldReturnDisable() = runTest { + fun getPickerScreenState_noDefaultNoteAppSet_shouldReturnDisabled() = runTest { val underTest = createUnderTest(isEnabled = true) whenever( roleManager.getRoleHoldersAsUser( @@ -293,16 +292,16 @@ internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() { ) .thenReturn(emptyList()) - assertThat(underTest.getPickerScreenState()) - .isEqualTo( - KeyguardQuickAffordanceConfig.PickerScreenState.Disabled( - listOf("Select a default notes app to use the notetaking shortcut"), - actionText = "Select app", - actionComponentName = - "${context.packageName}$COMPONENT_NAME_SEPARATOR" + - "$ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE" - ) - ) + val pickerScreenState = underTest.getPickerScreenState() + 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.actionText).isEqualTo("Select app") + assertThat(disabled.actionIntent?.action) + .isEqualTo(ACTION_MANAGE_NOTES_ROLE_FROM_QUICK_AFFORDANCE) + assertThat(disabled.actionIntent?.`package`).isEqualTo(context.packageName) } // endregion |