From 102b4d676981bb85f3bc6f92b879ad3b40186cf7 Mon Sep 17 00:00:00 2001 From: Alejandro Nijamkin Date: Sat, 24 Dec 2022 07:57:12 -0800 Subject: Renames customization content provider (1/7). KeyguardQuickAffordanceProvider has started to be used for more than just lock screen shortcuts. This collection of CLs updates its name, authority, and table schema to become more generically about "system UI customization". Fix: 262879277 Test: manually verified Settings > Display > Lock screen shows the "shortcuts" item Test: manually verified that the Wallpaper picker properly renders the preview and can change the quick affordances Test: manually verified that system UI displays the right lock screen shortcuts Change-Id: Ib1e33d0ddc0be8e0cd3533301cee340f95a27ddd --- packages/SystemUI/AndroidManifest.xml | 8 +- .../compose/features/tests/AndroidManifest.xml | 2 +- .../data/content/CustomizationProviderClient.kt | 510 +++++++++++++++++++ .../data/content/CustomizationProviderContract.kt | 183 +++++++ .../content/FakeCustomizationProviderClient.kt | 195 ++++++++ .../FakeKeyguardQuickAffordanceProviderClient.kt | 200 -------- .../KeyguardQuickAffordanceProviderClient.kt | 479 ------------------ .../KeyguardQuickAffordanceProviderContract.kt | 166 ------- .../SystemUI/docs/device-entry/quickaffordance.md | 2 +- .../systemui/dagger/ReferenceSysUIComponent.java | 4 +- .../systemui/keyguard/CustomizationProvider.kt | 393 +++++++++++++++ .../keyguard/KeyguardQuickAffordanceProvider.kt | 360 -------------- .../KeyguardQuickAffordanceConfig.kt | 14 +- ...KeyguardQuickAffordanceProviderClientFactory.kt | 10 +- ...ardQuickAffordanceRemoteUserSelectionManager.kt | 4 +- .../KeyguardQuickAffordanceInteractor.kt | 2 +- packages/SystemUI/tests/AndroidManifest.xml | 2 +- .../systemui/keyguard/CustomizationProviderTest.kt | 547 +++++++++++++++++++++ .../KeyguardQuickAffordanceProviderTest.kt | 509 ------------------- ...uickAffordanceRemoteUserSelectionManagerTest.kt | 24 +- .../KeyguardQuickAffordanceRepositoryTest.kt | 22 +- ...KeyguardQuickAffordanceProviderClientFactory.kt | 10 +- 22 files changed, 1878 insertions(+), 1768 deletions(-) create mode 100644 packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderClient.kt create mode 100644 packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt create mode 100644 packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt delete mode 100644 packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt delete mode 100644 packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt delete mode 100644 packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt delete mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt delete mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 2f5b42ffe641..810dd33ae17a 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -193,7 +193,7 @@ - @@ -1003,10 +1003,10 @@ diff --git a/packages/SystemUI/compose/features/tests/AndroidManifest.xml b/packages/SystemUI/compose/features/tests/AndroidManifest.xml index 2fa475d63607..2f41ea92b30f 100644 --- a/packages/SystemUI/compose/features/tests/AndroidManifest.xml +++ b/packages/SystemUI/compose/features/tests/AndroidManifest.xml @@ -35,7 +35,7 @@ android:enabled="false" tools:replace="android:authorities" tools:node="remove" /> - + + /** Returns the list of flags. */ + suspend fun queryFlags(): List + + /** + * Returns [Flow] for observing the collection of slots. + * + * @see [querySlots] + */ + fun observeSlots(): Flow> + + /** + * Returns [Flow] for observing the collection of flags. + * + * @see [queryFlags] + */ + fun observeFlags(): Flow> + + /** + * Returns all available affordances supported by the device, regardless of current slot + * placement. + */ + suspend fun queryAffordances(): List + + /** + * Returns [Flow] for observing the collection of affordances. + * + * @see [queryAffordances] + */ + fun observeAffordances(): Flow> + + /** Returns the current slot-affordance selections. */ + suspend fun querySelections(): List + + /** + * Returns [Flow] for observing the collection of selections. + * + * @see [querySelections] + */ + fun observeSelections(): Flow> + + /** Unselects an affordance with the given ID from the slot with the given ID. */ + suspend fun deleteSelection( + slotId: String, + affordanceId: String, + ) + + /** Unselects all affordances from the slot with the given ID. */ + suspend fun deleteAllSelections( + slotId: String, + ) + + /** Returns a [Drawable] with the given ID, loaded from the system UI package. */ + suspend fun getAffordanceIcon( + @DrawableRes iconResourceId: Int, + tintColor: Int = Color.WHITE, + ): Drawable + + /** Models a slot. A position that quick affordances can be positioned in. */ + data class Slot( + /** Unique ID of the slot. */ + val id: String, + /** + * The maximum number of quick affordances that are allowed to be positioned in this slot. + */ + val capacity: Int, + ) + + /** + * Models a quick affordance. An action that can be selected by the user to appear in one or + * more slots on the lock screen. + */ + data class Affordance( + /** Unique ID of the quick affordance. */ + val id: String, + /** User-facing label for this affordance. */ + val name: String, + /** + * Resource ID for the user-facing icon for this affordance. This resource is hosted by the + * System UI process so it must be used with + * `PackageManager.getResourcesForApplication(String)`. + */ + val iconResourceId: Int, + /** + * Whether the affordance is enabled. Disabled affordances should be shown on the picker but + * should be rendered as "disabled". When tapped, the enablement properties should be used + * to populate UI that would explain to the user what to do in order to re-enable this + * affordance. + */ + 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. + */ + val enablementInstructions: List? = 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 + * 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 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 `null`, the button should not be shown. + */ + val enablementActionComponentName: String? = null, + ) + + /** Models a selection of a quick affordance on a slot. */ + data class Selection( + /** The unique ID of the slot. */ + val slotId: String, + /** The unique ID of the quick affordance. */ + val affordanceId: String, + /** The user-visible label for the quick affordance. */ + val affordanceName: String, + ) + + /** Models a System UI flag. */ + data class Flag( + /** The name of the flag. */ + val name: String, + /** The value of the flag. */ + val value: Boolean, + ) +} + +class CustomizationProviderClientImpl( + private val context: Context, + private val backgroundDispatcher: CoroutineDispatcher, +) : CustomizationProviderClient { + + override suspend fun insertSelection( + slotId: String, + affordanceId: String, + ) { + withContext(backgroundDispatcher) { + context.contentResolver.insert( + Contract.LockScreenQuickAffordances.SelectionTable.URI, + ContentValues().apply { + put(Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID, slotId) + put( + Contract.LockScreenQuickAffordances.SelectionTable.Columns.AFFORDANCE_ID, + affordanceId + ) + } + ) + } + } + + override suspend fun querySlots(): List { + return withContext(backgroundDispatcher) { + context.contentResolver + .query( + Contract.LockScreenQuickAffordances.SlotTable.URI, + null, + null, + null, + null, + ) + ?.use { cursor -> + buildList { + val idColumnIndex = + cursor.getColumnIndex( + Contract.LockScreenQuickAffordances.SlotTable.Columns.ID + ) + val capacityColumnIndex = + cursor.getColumnIndex( + Contract.LockScreenQuickAffordances.SlotTable.Columns.CAPACITY + ) + if (idColumnIndex == -1 || capacityColumnIndex == -1) { + return@buildList + } + + while (cursor.moveToNext()) { + add( + CustomizationProviderClient.Slot( + id = cursor.getString(idColumnIndex), + capacity = cursor.getInt(capacityColumnIndex), + ) + ) + } + } + } + } + ?: emptyList() + } + + override suspend fun queryFlags(): List { + return withContext(backgroundDispatcher) { + context.contentResolver + .query( + Contract.FlagsTable.URI, + null, + null, + null, + null, + ) + ?.use { cursor -> + buildList { + val nameColumnIndex = + cursor.getColumnIndex(Contract.FlagsTable.Columns.NAME) + val valueColumnIndex = + cursor.getColumnIndex(Contract.FlagsTable.Columns.VALUE) + if (nameColumnIndex == -1 || valueColumnIndex == -1) { + return@buildList + } + + while (cursor.moveToNext()) { + add( + CustomizationProviderClient.Flag( + name = cursor.getString(nameColumnIndex), + value = cursor.getInt(valueColumnIndex) == 1, + ) + ) + } + } + } + } + ?: emptyList() + } + + override fun observeSlots(): Flow> { + return observeUri(Contract.LockScreenQuickAffordances.SlotTable.URI).map { querySlots() } + } + + override fun observeFlags(): Flow> { + return observeUri(Contract.FlagsTable.URI).map { queryFlags() } + } + + override suspend fun queryAffordances(): List { + return withContext(backgroundDispatcher) { + context.contentResolver + .query( + Contract.LockScreenQuickAffordances.AffordanceTable.URI, + null, + null, + null, + null, + ) + ?.use { cursor -> + buildList { + val idColumnIndex = + cursor.getColumnIndex( + Contract.LockScreenQuickAffordances.AffordanceTable.Columns.ID + ) + val nameColumnIndex = + cursor.getColumnIndex( + Contract.LockScreenQuickAffordances.AffordanceTable.Columns.NAME + ) + val iconColumnIndex = + cursor.getColumnIndex( + Contract.LockScreenQuickAffordances.AffordanceTable.Columns.ICON + ) + val isEnabledColumnIndex = + cursor.getColumnIndex( + Contract.LockScreenQuickAffordances.AffordanceTable.Columns + .IS_ENABLED + ) + val enablementInstructionsColumnIndex = + cursor.getColumnIndex( + Contract.LockScreenQuickAffordances.AffordanceTable.Columns + .ENABLEMENT_INSTRUCTIONS + ) + val enablementActionTextColumnIndex = + cursor.getColumnIndex( + Contract.LockScreenQuickAffordances.AffordanceTable.Columns + .ENABLEMENT_ACTION_TEXT + ) + val enablementComponentNameColumnIndex = + cursor.getColumnIndex( + Contract.LockScreenQuickAffordances.AffordanceTable.Columns + .ENABLEMENT_COMPONENT_NAME + ) + if ( + idColumnIndex == -1 || + nameColumnIndex == -1 || + iconColumnIndex == -1 || + isEnabledColumnIndex == -1 || + enablementInstructionsColumnIndex == -1 || + enablementActionTextColumnIndex == -1 || + enablementComponentNameColumnIndex == -1 + ) { + return@buildList + } + + while (cursor.moveToNext()) { + add( + CustomizationProviderClient.Affordance( + id = cursor.getString(idColumnIndex), + name = cursor.getString(nameColumnIndex), + iconResourceId = cursor.getInt(iconColumnIndex), + isEnabled = cursor.getInt(isEnabledColumnIndex) == 1, + enablementInstructions = + cursor + .getString(enablementInstructionsColumnIndex) + ?.split( + Contract.LockScreenQuickAffordances.AffordanceTable + .ENABLEMENT_INSTRUCTIONS_DELIMITER + ), + enablementActionText = + cursor.getString(enablementActionTextColumnIndex), + enablementActionComponentName = + cursor.getString(enablementComponentNameColumnIndex), + ) + ) + } + } + } + } + ?: emptyList() + } + + override fun observeAffordances(): Flow> { + return observeUri(Contract.LockScreenQuickAffordances.AffordanceTable.URI).map { + queryAffordances() + } + } + + override suspend fun querySelections(): List { + return withContext(backgroundDispatcher) { + context.contentResolver + .query( + Contract.LockScreenQuickAffordances.SelectionTable.URI, + null, + null, + null, + null, + ) + ?.use { cursor -> + buildList { + val slotIdColumnIndex = + cursor.getColumnIndex( + Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID + ) + val affordanceIdColumnIndex = + cursor.getColumnIndex( + Contract.LockScreenQuickAffordances.SelectionTable.Columns + .AFFORDANCE_ID + ) + val affordanceNameColumnIndex = + cursor.getColumnIndex( + Contract.LockScreenQuickAffordances.SelectionTable.Columns + .AFFORDANCE_NAME + ) + if ( + slotIdColumnIndex == -1 || + affordanceIdColumnIndex == -1 || + affordanceNameColumnIndex == -1 + ) { + return@buildList + } + + while (cursor.moveToNext()) { + add( + CustomizationProviderClient.Selection( + slotId = cursor.getString(slotIdColumnIndex), + affordanceId = cursor.getString(affordanceIdColumnIndex), + affordanceName = cursor.getString(affordanceNameColumnIndex), + ) + ) + } + } + } + } + ?: emptyList() + } + + override fun observeSelections(): Flow> { + return observeUri(Contract.LockScreenQuickAffordances.SelectionTable.URI).map { + querySelections() + } + } + + override suspend fun deleteSelection( + slotId: String, + affordanceId: String, + ) { + withContext(backgroundDispatcher) { + context.contentResolver.delete( + Contract.LockScreenQuickAffordances.SelectionTable.URI, + "${Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID} = ? AND" + + " ${Contract.LockScreenQuickAffordances.SelectionTable.Columns.AFFORDANCE_ID}" + + " = ?", + arrayOf( + slotId, + affordanceId, + ), + ) + } + } + + override suspend fun deleteAllSelections( + slotId: String, + ) { + withContext(backgroundDispatcher) { + context.contentResolver.delete( + Contract.LockScreenQuickAffordances.SelectionTable.URI, + Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID, + arrayOf( + slotId, + ), + ) + } + } + + @SuppressLint("UseCompatLoadingForDrawables") + override suspend fun getAffordanceIcon( + @DrawableRes iconResourceId: Int, + tintColor: Int, + ): Drawable { + return withContext(backgroundDispatcher) { + context.packageManager + .getResourcesForApplication(SYSTEM_UI_PACKAGE_NAME) + .getDrawable(iconResourceId, context.theme) + .apply { setTint(tintColor) } + } + } + + private fun observeUri( + uri: Uri, + ): Flow { + return callbackFlow { + val observer = + object : ContentObserver(null) { + override fun onChange(selfChange: Boolean) { + trySend(Unit) + } + } + + context.contentResolver.registerContentObserver( + uri, + /* notifyForDescendants= */ true, + observer, + ) + + awaitClose { context.contentResolver.unregisterContentObserver(observer) } + } + .onStart { emit(Unit) } + } + + companion object { + private const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui" + } +} 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 new file mode 100644 index 000000000000..1e2e7d2595ac --- /dev/null +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2022 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.shared.customization.data.content + +import android.content.ContentResolver +import android.net.Uri + +/** Contract definitions for querying content about keyguard quick affordances. */ +object CustomizationProviderContract { + + const val AUTHORITY = "com.android.systemui.customization" + const val PERMISSION = "android.permission.CUSTOMIZE_SYSTEM_UI" + + private val BASE_URI: Uri = + Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build() + + /** Namespace for lock screen shortcut (quick affordance) tables. */ + object LockScreenQuickAffordances { + + const val NAMESPACE = "lockscreen_quickaffordance" + + private val LOCK_SCREEN_QUICK_AFFORDANCE_BASE_URI: Uri = + BASE_URI.buildUpon().path(NAMESPACE).build() + + fun qualifiedTablePath(tableName: String): String { + return "$NAMESPACE/$tableName" + } + + /** + * Table for slots. + * + * Slots are positions where affordances can be placed on the lock screen. Affordances that + * are placed on slots are said to be "selected". The system supports the idea of multiple + * affordances per slot, though the implementation may limit the number of affordances on + * each slot. + * + * Supported operations: + * - Query - to know which slots are available, query the [SlotTable.URI] [Uri]. The result + * set will contain rows with the [SlotTable.Columns] columns. + */ + object SlotTable { + const val TABLE_NAME = "slots" + val URI: Uri = + LOCK_SCREEN_QUICK_AFFORDANCE_BASE_URI.buildUpon().appendPath(TABLE_NAME).build() + + object Columns { + /** String. Unique ID for this slot. */ + const val ID = "id" + /** Integer. The maximum number of affordances that can be placed in the slot. */ + const val CAPACITY = "capacity" + } + } + + /** + * Table for affordances. + * + * Affordances are actions/buttons that the user can execute. They are placed on slots on + * the lock screen. + * + * Supported operations: + * - Query - to know about all the affordances that are available on the device, regardless + * of which ones are currently selected, query the [AffordanceTable.URI] [Uri]. The result + * set will contain rows, each with the columns specified in [AffordanceTable.Columns]. + */ + object AffordanceTable { + const val TABLE_NAME = "affordances" + 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. */ + const val ID = "id" + /** String. User-visible name for this affordance. */ + const val NAME = "name" + /** + * Integer. Resource ID for the drawable to load for this affordance. This is a + * resource ID from the system UI package. + */ + 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. + */ + 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" + } + } + + /** + * Table for selections. + * + * Selections are pairs of slot and affordance IDs. + * + * Supported operations: + * - Insert - to insert an affordance and place it in a slot, insert values for the columns + * into the [SelectionTable.URI] [Uri]. The maximum capacity rule is enforced by the system. + * Selecting a new affordance for a slot that is already full will automatically remove the + * oldest affordance from the slot. + * - Query - to know which affordances are set on which slots, query the + * [SelectionTable.URI] [Uri]. The result set will contain rows, each of which with the + * columns from [SelectionTable.Columns]. + * - Delete - to unselect an affordance, removing it from a slot, delete from the + * [SelectionTable.URI] [Uri], passing in values for each column. + */ + object SelectionTable { + const val TABLE_NAME = "selections" + val URI: Uri = + LOCK_SCREEN_QUICK_AFFORDANCE_BASE_URI.buildUpon().appendPath(TABLE_NAME).build() + + object Columns { + /** String. Unique ID for the slot. */ + const val SLOT_ID = "slot_id" + /** String. Unique ID for the selected affordance. */ + const val AFFORDANCE_ID = "affordance_id" + /** String. Human-readable name for the affordance. */ + const val AFFORDANCE_NAME = "affordance_name" + } + } + } + + /** + * Table for flags. + * + * Flags are key-value pairs. + * + * Supported operations: + * - Query - to know the values of flags, query the [FlagsTable.URI] [Uri]. The result set will + * contain rows, each of which with the columns from [FlagsTable.Columns]. + */ + object FlagsTable { + const val TABLE_NAME = "flags" + val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build() + + /** Flag denoting whether the Wallpaper Picker should use the new, revamped UI. */ + const val FLAG_NAME_REVAMPED_WALLPAPER_UI = "revamped_wallpaper_ui" + + /** + * Flag denoting whether the customizable lock screen quick affordances feature is enabled. + */ + const val FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED = + "is_custom_lock_screen_quick_affordances_feature_enabled" + + /** Flag denoting whether the customizable clocks feature is enabled. */ + const val FLAG_NAME_CUSTOM_CLOCKS_ENABLED = "is_custom_clocks_feature_enabled" + + object Columns { + /** String. Unique ID for the flag. */ + const val NAME = "name" + /** Int. Value of the flag. `1` means `true` and `0` means `false`. */ + const val VALUE = "value" + } + } +} diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt new file mode 100644 index 000000000000..f5a955d450e3 --- /dev/null +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/FakeCustomizationProviderClient.kt @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2022 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.shared.customization.data.content + +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.combine + +class FakeCustomizationProviderClient( + slots: List = + listOf( + CustomizationProviderClient.Slot( + id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, + capacity = 1, + ), + CustomizationProviderClient.Slot( + id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, + capacity = 1, + ), + ), + affordances: List = + listOf( + CustomizationProviderClient.Affordance( + id = AFFORDANCE_1, + name = AFFORDANCE_1, + iconResourceId = 1, + ), + CustomizationProviderClient.Affordance( + id = AFFORDANCE_2, + name = AFFORDANCE_2, + iconResourceId = 2, + ), + CustomizationProviderClient.Affordance( + id = AFFORDANCE_3, + name = AFFORDANCE_3, + iconResourceId = 3, + ), + ), + flags: List = + listOf( + CustomizationProviderClient.Flag( + name = + CustomizationProviderContract.FlagsTable + .FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED, + value = true, + ) + ), +) : CustomizationProviderClient { + + private val slots = MutableStateFlow(slots) + private val affordances = MutableStateFlow(affordances) + private val flags = MutableStateFlow(flags) + + private val selections = MutableStateFlow>>(emptyMap()) + + override suspend fun insertSelection(slotId: String, affordanceId: String) { + val slotCapacity = + querySlots().find { it.id == slotId }?.capacity + ?: error("Slot with ID \"$slotId\" not found!") + val affordances = selections.value.getOrDefault(slotId, mutableListOf()).toMutableList() + while (affordances.size + 1 > slotCapacity) { + affordances.removeAt(0) + } + affordances.remove(affordanceId) + affordances.add(affordanceId) + selections.value = selections.value.toMutableMap().apply { this[slotId] = affordances } + } + + override suspend fun querySlots(): List { + return slots.value + } + + override suspend fun queryFlags(): List { + return flags.value + } + + override fun observeSlots(): Flow> { + return slots.asStateFlow() + } + + override fun observeFlags(): Flow> { + return flags.asStateFlow() + } + + override suspend fun queryAffordances(): List { + return affordances.value + } + + override fun observeAffordances(): Flow> { + return affordances.asStateFlow() + } + + override suspend fun querySelections(): List { + return toSelectionList(selections.value, affordances.value) + } + + override fun observeSelections(): Flow> { + return combine(selections, affordances) { selections, affordances -> + toSelectionList(selections, affordances) + } + } + + override suspend fun deleteSelection(slotId: String, affordanceId: String) { + val affordances = selections.value.getOrDefault(slotId, mutableListOf()).toMutableList() + affordances.remove(affordanceId) + + selections.value = selections.value.toMutableMap().apply { this[slotId] = affordances } + } + + override suspend fun deleteAllSelections(slotId: String) { + selections.value = selections.value.toMutableMap().apply { this[slotId] = emptyList() } + } + + override suspend fun getAffordanceIcon(iconResourceId: Int, tintColor: Int): Drawable { + return when (iconResourceId) { + 1 -> ICON_1 + 2 -> ICON_2 + 3 -> ICON_3 + else -> BitmapDrawable() + } + } + + fun setFlag( + name: String, + value: Boolean, + ) { + flags.value = + flags.value.toMutableList().apply { + removeIf { it.name == name } + add(CustomizationProviderClient.Flag(name = name, value = value)) + } + } + + fun setSlotCapacity(slotId: String, capacity: Int) { + slots.value = + slots.value.toMutableList().apply { + val index = indexOfFirst { it.id == slotId } + check(index != -1) { "Slot with ID \"$slotId\" doesn't exist!" } + set(index, CustomizationProviderClient.Slot(id = slotId, capacity = capacity)) + } + } + + fun addAffordance(affordance: CustomizationProviderClient.Affordance): Int { + affordances.value = affordances.value + listOf(affordance) + return affordances.value.size - 1 + } + + private fun toSelectionList( + selections: Map>, + affordances: List, + ): List { + return selections + .map { (slotId, affordanceIds) -> + affordanceIds.map { affordanceId -> + val affordanceName = + affordances.find { it.id == affordanceId }?.name + ?: error("No affordance with ID of \"$affordanceId\"!") + CustomizationProviderClient.Selection( + slotId = slotId, + affordanceId = affordanceId, + affordanceName = affordanceName, + ) + } + } + .flatten() + } + + companion object { + const val AFFORDANCE_1 = "affordance_1" + const val AFFORDANCE_2 = "affordance_2" + const val AFFORDANCE_3 = "affordance_3" + val ICON_1 = BitmapDrawable() + val ICON_2 = BitmapDrawable() + val ICON_3 = BitmapDrawable() + } +} diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt deleted file mode 100644 index ec5e70383eb3..000000000000 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/FakeKeyguardQuickAffordanceProviderClient.kt +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright (C) 2022 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.shared.quickaffordance.data.content - -import android.graphics.drawable.BitmapDrawable -import android.graphics.drawable.Drawable -import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.combine - -class FakeKeyguardQuickAffordanceProviderClient( - slots: List = - listOf( - KeyguardQuickAffordanceProviderClient.Slot( - id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, - capacity = 1, - ), - KeyguardQuickAffordanceProviderClient.Slot( - id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, - capacity = 1, - ), - ), - affordances: List = - listOf( - KeyguardQuickAffordanceProviderClient.Affordance( - id = AFFORDANCE_1, - name = AFFORDANCE_1, - iconResourceId = 1, - ), - KeyguardQuickAffordanceProviderClient.Affordance( - id = AFFORDANCE_2, - name = AFFORDANCE_2, - iconResourceId = 2, - ), - KeyguardQuickAffordanceProviderClient.Affordance( - id = AFFORDANCE_3, - name = AFFORDANCE_3, - iconResourceId = 3, - ), - ), - flags: List = - listOf( - KeyguardQuickAffordanceProviderClient.Flag( - name = - KeyguardQuickAffordanceProviderContract.FlagsTable - .FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED, - value = true, - ) - ), -) : KeyguardQuickAffordanceProviderClient { - - private val slots = MutableStateFlow(slots) - private val affordances = MutableStateFlow(affordances) - private val flags = MutableStateFlow(flags) - - private val selections = MutableStateFlow>>(emptyMap()) - - override suspend fun insertSelection(slotId: String, affordanceId: String) { - val slotCapacity = - querySlots().find { it.id == slotId }?.capacity - ?: error("Slot with ID \"$slotId\" not found!") - val affordances = selections.value.getOrDefault(slotId, mutableListOf()).toMutableList() - while (affordances.size + 1 > slotCapacity) { - affordances.removeAt(0) - } - affordances.remove(affordanceId) - affordances.add(affordanceId) - selections.value = selections.value.toMutableMap().apply { this[slotId] = affordances } - } - - override suspend fun querySlots(): List { - return slots.value - } - - override suspend fun queryFlags(): List { - return flags.value - } - - override fun observeSlots(): Flow> { - return slots.asStateFlow() - } - - override fun observeFlags(): Flow> { - return flags.asStateFlow() - } - - override suspend fun queryAffordances(): - List { - return affordances.value - } - - override fun observeAffordances(): - Flow> { - return affordances.asStateFlow() - } - - override suspend fun querySelections(): List { - return toSelectionList(selections.value, affordances.value) - } - - override fun observeSelections(): Flow> { - return combine(selections, affordances) { selections, affordances -> - toSelectionList(selections, affordances) - } - } - - override suspend fun deleteSelection(slotId: String, affordanceId: String) { - val affordances = selections.value.getOrDefault(slotId, mutableListOf()).toMutableList() - affordances.remove(affordanceId) - - selections.value = selections.value.toMutableMap().apply { this[slotId] = affordances } - } - - override suspend fun deleteAllSelections(slotId: String) { - selections.value = selections.value.toMutableMap().apply { this[slotId] = emptyList() } - } - - override suspend fun getAffordanceIcon(iconResourceId: Int, tintColor: Int): Drawable { - return when (iconResourceId) { - 1 -> ICON_1 - 2 -> ICON_2 - 3 -> ICON_3 - else -> BitmapDrawable() - } - } - - fun setFlag( - name: String, - value: Boolean, - ) { - flags.value = - flags.value.toMutableList().apply { - removeIf { it.name == name } - add(KeyguardQuickAffordanceProviderClient.Flag(name = name, value = value)) - } - } - - fun setSlotCapacity(slotId: String, capacity: Int) { - slots.value = - slots.value.toMutableList().apply { - val index = indexOfFirst { it.id == slotId } - check(index != -1) { "Slot with ID \"$slotId\" doesn't exist!" } - set( - index, - KeyguardQuickAffordanceProviderClient.Slot(id = slotId, capacity = capacity) - ) - } - } - - fun addAffordance(affordance: KeyguardQuickAffordanceProviderClient.Affordance): Int { - affordances.value = affordances.value + listOf(affordance) - return affordances.value.size - 1 - } - - private fun toSelectionList( - selections: Map>, - affordances: List, - ): List { - return selections - .map { (slotId, affordanceIds) -> - affordanceIds.map { affordanceId -> - val affordanceName = - affordances.find { it.id == affordanceId }?.name - ?: error("No affordance with ID of \"$affordanceId\"!") - KeyguardQuickAffordanceProviderClient.Selection( - slotId = slotId, - affordanceId = affordanceId, - affordanceName = affordanceName, - ) - } - } - .flatten() - } - - companion object { - const val AFFORDANCE_1 = "affordance_1" - const val AFFORDANCE_2 = "affordance_2" - const val AFFORDANCE_3 = "affordance_3" - val ICON_1 = BitmapDrawable() - val ICON_2 = BitmapDrawable() - val ICON_3 = BitmapDrawable() - } -} diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt deleted file mode 100644 index 3213b2e97ac9..000000000000 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderClient.kt +++ /dev/null @@ -1,479 +0,0 @@ -/* - * Copyright (C) 2022 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.shared.quickaffordance.data.content - -import android.annotation.SuppressLint -import android.content.ContentValues -import android.content.Context -import android.database.ContentObserver -import android.graphics.Color -import android.graphics.drawable.Drawable -import android.net.Uri -import androidx.annotation.DrawableRes -import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onStart -import kotlinx.coroutines.withContext - -/** Client for using a content provider implementing the [Contract]. */ -interface KeyguardQuickAffordanceProviderClient { - - /** - * Selects an affordance with the given ID for a slot on the lock screen with the given ID. - * - * Note that the maximum number of selected affordances on this slot is automatically enforced. - * Selecting a slot that is already full (e.g. already has a number of selected affordances at - * its maximum capacity) will automatically remove the oldest selected affordance before adding - * the one passed in this call. Additionally, selecting an affordance that's already one of the - * selected affordances on the slot will move the selected affordance to the newest location in - * the slot. - */ - suspend fun insertSelection( - slotId: String, - affordanceId: String, - ) - - /** Returns all available slots supported by the device. */ - suspend fun querySlots(): List - - /** Returns the list of flags. */ - suspend fun queryFlags(): List - - /** - * Returns [Flow] for observing the collection of slots. - * - * @see [querySlots] - */ - fun observeSlots(): Flow> - - /** - * Returns [Flow] for observing the collection of flags. - * - * @see [queryFlags] - */ - fun observeFlags(): Flow> - - /** - * Returns all available affordances supported by the device, regardless of current slot - * placement. - */ - suspend fun queryAffordances(): List - - /** - * Returns [Flow] for observing the collection of affordances. - * - * @see [queryAffordances] - */ - fun observeAffordances(): Flow> - - /** Returns the current slot-affordance selections. */ - suspend fun querySelections(): List - - /** - * Returns [Flow] for observing the collection of selections. - * - * @see [querySelections] - */ - fun observeSelections(): Flow> - - /** Unselects an affordance with the given ID from the slot with the given ID. */ - suspend fun deleteSelection( - slotId: String, - affordanceId: String, - ) - - /** Unselects all affordances from the slot with the given ID. */ - suspend fun deleteAllSelections( - slotId: String, - ) - - /** Returns a [Drawable] with the given ID, loaded from the system UI package. */ - suspend fun getAffordanceIcon( - @DrawableRes iconResourceId: Int, - tintColor: Int = Color.WHITE, - ): Drawable - - /** Models a slot. A position that quick affordances can be positioned in. */ - data class Slot( - /** Unique ID of the slot. */ - val id: String, - /** - * The maximum number of quick affordances that are allowed to be positioned in this slot. - */ - val capacity: Int, - ) - - /** - * Models a quick affordance. An action that can be selected by the user to appear in one or - * more slots on the lock screen. - */ - data class Affordance( - /** Unique ID of the quick affordance. */ - val id: String, - /** User-facing label for this affordance. */ - val name: String, - /** - * Resource ID for the user-facing icon for this affordance. This resource is hosted by the - * System UI process so it must be used with - * `PackageManager.getResourcesForApplication(String)`. - */ - val iconResourceId: Int, - /** - * Whether the affordance is enabled. Disabled affordances should be shown on the picker but - * should be rendered as "disabled". When tapped, the enablement properties should be used - * to populate UI that would explain to the user what to do in order to re-enable this - * affordance. - */ - 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. - */ - val enablementInstructions: List? = 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 - * 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 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 `null`, the button should not be shown. - */ - val enablementActionComponentName: String? = null, - ) - - /** Models a selection of a quick affordance on a slot. */ - data class Selection( - /** The unique ID of the slot. */ - val slotId: String, - /** The unique ID of the quick affordance. */ - val affordanceId: String, - /** The user-visible label for the quick affordance. */ - val affordanceName: String, - ) - - /** Models a System UI flag. */ - data class Flag( - /** The name of the flag. */ - val name: String, - /** The value of the flag. */ - val value: Boolean, - ) -} - -class KeyguardQuickAffordanceProviderClientImpl( - private val context: Context, - private val backgroundDispatcher: CoroutineDispatcher, -) : KeyguardQuickAffordanceProviderClient { - - override suspend fun insertSelection( - slotId: String, - affordanceId: String, - ) { - withContext(backgroundDispatcher) { - context.contentResolver.insert( - Contract.SelectionTable.URI, - ContentValues().apply { - put(Contract.SelectionTable.Columns.SLOT_ID, slotId) - put(Contract.SelectionTable.Columns.AFFORDANCE_ID, affordanceId) - } - ) - } - } - - override suspend fun querySlots(): List { - return withContext(backgroundDispatcher) { - context.contentResolver - .query( - Contract.SlotTable.URI, - null, - null, - null, - null, - ) - ?.use { cursor -> - buildList { - val idColumnIndex = cursor.getColumnIndex(Contract.SlotTable.Columns.ID) - val capacityColumnIndex = - cursor.getColumnIndex(Contract.SlotTable.Columns.CAPACITY) - if (idColumnIndex == -1 || capacityColumnIndex == -1) { - return@buildList - } - - while (cursor.moveToNext()) { - add( - KeyguardQuickAffordanceProviderClient.Slot( - id = cursor.getString(idColumnIndex), - capacity = cursor.getInt(capacityColumnIndex), - ) - ) - } - } - } - } - ?: emptyList() - } - - override suspend fun queryFlags(): List { - return withContext(backgroundDispatcher) { - context.contentResolver - .query( - Contract.FlagsTable.URI, - null, - null, - null, - null, - ) - ?.use { cursor -> - buildList { - val nameColumnIndex = - cursor.getColumnIndex(Contract.FlagsTable.Columns.NAME) - val valueColumnIndex = - cursor.getColumnIndex(Contract.FlagsTable.Columns.VALUE) - if (nameColumnIndex == -1 || valueColumnIndex == -1) { - return@buildList - } - - while (cursor.moveToNext()) { - add( - KeyguardQuickAffordanceProviderClient.Flag( - name = cursor.getString(nameColumnIndex), - value = cursor.getInt(valueColumnIndex) == 1, - ) - ) - } - } - } - } - ?: emptyList() - } - - override fun observeSlots(): Flow> { - return observeUri(Contract.SlotTable.URI).map { querySlots() } - } - - override fun observeFlags(): Flow> { - return observeUri(Contract.FlagsTable.URI).map { queryFlags() } - } - - override suspend fun queryAffordances(): - List { - return withContext(backgroundDispatcher) { - context.contentResolver - .query( - Contract.AffordanceTable.URI, - null, - null, - null, - null, - ) - ?.use { cursor -> - buildList { - val idColumnIndex = - cursor.getColumnIndex(Contract.AffordanceTable.Columns.ID) - val nameColumnIndex = - cursor.getColumnIndex(Contract.AffordanceTable.Columns.NAME) - val iconColumnIndex = - cursor.getColumnIndex(Contract.AffordanceTable.Columns.ICON) - val isEnabledColumnIndex = - cursor.getColumnIndex(Contract.AffordanceTable.Columns.IS_ENABLED) - val enablementInstructionsColumnIndex = - cursor.getColumnIndex( - Contract.AffordanceTable.Columns.ENABLEMENT_INSTRUCTIONS - ) - val enablementActionTextColumnIndex = - cursor.getColumnIndex( - Contract.AffordanceTable.Columns.ENABLEMENT_ACTION_TEXT - ) - val enablementComponentNameColumnIndex = - cursor.getColumnIndex( - Contract.AffordanceTable.Columns.ENABLEMENT_COMPONENT_NAME - ) - if ( - idColumnIndex == -1 || - nameColumnIndex == -1 || - iconColumnIndex == -1 || - isEnabledColumnIndex == -1 || - enablementInstructionsColumnIndex == -1 || - enablementActionTextColumnIndex == -1 || - enablementComponentNameColumnIndex == -1 - ) { - return@buildList - } - - while (cursor.moveToNext()) { - add( - KeyguardQuickAffordanceProviderClient.Affordance( - id = cursor.getString(idColumnIndex), - name = cursor.getString(nameColumnIndex), - iconResourceId = cursor.getInt(iconColumnIndex), - isEnabled = cursor.getInt(isEnabledColumnIndex) == 1, - enablementInstructions = - cursor - .getString(enablementInstructionsColumnIndex) - ?.split( - Contract.AffordanceTable - .ENABLEMENT_INSTRUCTIONS_DELIMITER - ), - enablementActionText = - cursor.getString(enablementActionTextColumnIndex), - enablementActionComponentName = - cursor.getString(enablementComponentNameColumnIndex), - ) - ) - } - } - } - } - ?: emptyList() - } - - override fun observeAffordances(): - Flow> { - return observeUri(Contract.AffordanceTable.URI).map { queryAffordances() } - } - - override suspend fun querySelections(): List { - return withContext(backgroundDispatcher) { - context.contentResolver - .query( - Contract.SelectionTable.URI, - null, - null, - null, - null, - ) - ?.use { cursor -> - buildList { - val slotIdColumnIndex = - cursor.getColumnIndex(Contract.SelectionTable.Columns.SLOT_ID) - val affordanceIdColumnIndex = - cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_ID) - val affordanceNameColumnIndex = - cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_NAME) - if ( - slotIdColumnIndex == -1 || - affordanceIdColumnIndex == -1 || - affordanceNameColumnIndex == -1 - ) { - return@buildList - } - - while (cursor.moveToNext()) { - add( - KeyguardQuickAffordanceProviderClient.Selection( - slotId = cursor.getString(slotIdColumnIndex), - affordanceId = cursor.getString(affordanceIdColumnIndex), - affordanceName = cursor.getString(affordanceNameColumnIndex), - ) - ) - } - } - } - } - ?: emptyList() - } - - override fun observeSelections(): Flow> { - return observeUri(Contract.SelectionTable.URI).map { querySelections() } - } - - override suspend fun deleteSelection( - slotId: String, - affordanceId: String, - ) { - withContext(backgroundDispatcher) { - context.contentResolver.delete( - Contract.SelectionTable.URI, - "${Contract.SelectionTable.Columns.SLOT_ID} = ? AND" + - " ${Contract.SelectionTable.Columns.AFFORDANCE_ID} = ?", - arrayOf( - slotId, - affordanceId, - ), - ) - } - } - - override suspend fun deleteAllSelections( - slotId: String, - ) { - withContext(backgroundDispatcher) { - context.contentResolver.delete( - Contract.SelectionTable.URI, - Contract.SelectionTable.Columns.SLOT_ID, - arrayOf( - slotId, - ), - ) - } - } - - @SuppressLint("UseCompatLoadingForDrawables") - override suspend fun getAffordanceIcon( - @DrawableRes iconResourceId: Int, - tintColor: Int, - ): Drawable { - return withContext(backgroundDispatcher) { - context.packageManager - .getResourcesForApplication(SYSTEM_UI_PACKAGE_NAME) - .getDrawable(iconResourceId, context.theme) - .apply { setTint(tintColor) } - } - } - - private fun observeUri( - uri: Uri, - ): Flow { - return callbackFlow { - val observer = - object : ContentObserver(null) { - override fun onChange(selfChange: Boolean) { - trySend(Unit) - } - } - - context.contentResolver.registerContentObserver( - uri, - /* notifyForDescendants= */ true, - observer, - ) - - awaitClose { context.contentResolver.unregisterContentObserver(observer) } - } - .onStart { emit(Unit) } - } - - companion object { - private const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui" - } -} diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt deleted file mode 100644 index 9491816243bf..000000000000 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (C) 2022 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.shared.quickaffordance.data.content - -import android.content.ContentResolver -import android.net.Uri - -/** Contract definitions for querying content about keyguard quick affordances. */ -object KeyguardQuickAffordanceProviderContract { - - const val AUTHORITY = "com.android.systemui.keyguard.quickaffordance" - const val PERMISSION = "android.permission.ACCESS_KEYGUARD_QUICK_AFFORDANCES" - - private val BASE_URI: Uri = - Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY).build() - - /** - * Table for slots. - * - * Slots are positions where affordances can be placed on the lock screen. Affordances that are - * placed on slots are said to be "selected". The system supports the idea of multiple - * affordances per slot, though the implementation may limit the number of affordances on each - * slot. - * - * Supported operations: - * - Query - to know which slots are available, query the [SlotTable.URI] [Uri]. The result set - * will contain rows with the [SlotTable.Columns] columns. - */ - object SlotTable { - const val TABLE_NAME = "slots" - val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build() - - object Columns { - /** String. Unique ID for this slot. */ - const val ID = "id" - /** Integer. The maximum number of affordances that can be placed in the slot. */ - const val CAPACITY = "capacity" - } - } - - /** - * Table for affordances. - * - * Affordances are actions/buttons that the user can execute. They are placed on slots on the - * lock screen. - * - * Supported operations: - * - Query - to know about all the affordances that are available on the device, regardless of - * which ones are currently selected, query the [AffordanceTable.URI] [Uri]. The result set will - * contain rows, each with the columns specified in [AffordanceTable.Columns]. - */ - object AffordanceTable { - const val TABLE_NAME = "affordances" - val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build() - const val ENABLEMENT_INSTRUCTIONS_DELIMITER = "][" - const val COMPONENT_NAME_SEPARATOR = "/" - - object Columns { - /** String. Unique ID for this affordance. */ - const val ID = "id" - /** String. User-visible name for this affordance. */ - const val NAME = "name" - /** - * Integer. Resource ID for the drawable to load for this affordance. This is a resource - * ID from the system UI package. - */ - 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. - */ - 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" - } - } - - /** - * Table for selections. - * - * Selections are pairs of slot and affordance IDs. - * - * Supported operations: - * - Insert - to insert an affordance and place it in a slot, insert values for the columns into - * the [SelectionTable.URI] [Uri]. The maximum capacity rule is enforced by the system. - * Selecting a new affordance for a slot that is already full will automatically remove the - * oldest affordance from the slot. - * - Query - to know which affordances are set on which slots, query the [SelectionTable.URI] - * [Uri]. The result set will contain rows, each of which with the columns from - * [SelectionTable.Columns]. - * - Delete - to unselect an affordance, removing it from a slot, delete from the - * [SelectionTable.URI] [Uri], passing in values for each column. - */ - object SelectionTable { - const val TABLE_NAME = "selections" - val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build() - - object Columns { - /** String. Unique ID for the slot. */ - const val SLOT_ID = "slot_id" - /** String. Unique ID for the selected affordance. */ - const val AFFORDANCE_ID = "affordance_id" - /** String. Human-readable name for the affordance. */ - const val AFFORDANCE_NAME = "affordance_name" - } - } - - /** - * Table for flags. - * - * Flags are key-value pairs. - * - * Supported operations: - * - Query - to know the values of flags, query the [FlagsTable.URI] [Uri]. The result set will - * contain rows, each of which with the columns from [FlagsTable.Columns]. - */ - object FlagsTable { - const val TABLE_NAME = "flags" - val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build() - - /** Flag denoting whether the Wallpaper Picker should use the new, revamped UI. */ - const val FLAG_NAME_REVAMPED_WALLPAPER_UI = "revamped_wallpaper_ui" - - /** - * Flag denoting whether the customizable lock screen quick affordances feature is enabled. - */ - const val FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED = - "is_custom_lock_screen_quick_affordances_feature_enabled" - - /** Flag denoting whether the customizable clocks feature is enabled. */ - const val FLAG_NAME_CUSTOM_CLOCKS_ENABLED = "is_custom_clocks_feature_enabled" - - object Columns { - /** String. Unique ID for the flag. */ - const val NAME = "name" - /** Int. Value of the flag. `1` means `true` and `0` means `false`. */ - const val VALUE = "value" - } - } -} diff --git a/packages/SystemUI/docs/device-entry/quickaffordance.md b/packages/SystemUI/docs/device-entry/quickaffordance.md index 01d4f00467c4..ccb35fac36f2 100644 --- a/packages/SystemUI/docs/device-entry/quickaffordance.md +++ b/packages/SystemUI/docs/device-entry/quickaffordance.md @@ -31,7 +31,7 @@ This section describes how to implement a potential picker, selector, or configu ### Accessing Quick Affordance Data Quick Affordances structured data are exposed to other applications through the `KeyguardQuickAffordanceProvider` content provider which is owned by the System UI process. -To access this content provider, applications must have the `android.permission.ACCESS_KEYGUARD_QUICK_AFFORDANCES` permission which is a signature and privileged permission, limiting access to system apps or apps signed by the same signature as System UI. The `KeyguardQuickAffordanceProviderContract` file defines the content provider schema for consumers. +To access this content provider, applications must have the `android.permission.CUSTOMIZE_SYSTEM_UI` permission which is a signature and privileged permission, limiting access to system apps or apps signed by the same signature as System UI. The `KeyguardQuickAffordanceProviderContract` file defines the content provider schema for consumers. Generally speaking, there are three important tables served by the content provider: `slots`, `affordances`, and `selections`. There is also a `flags` table, but that's not important and may be ignored. diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java index b30e0c22e566..a431a59fcef6 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java @@ -16,7 +16,7 @@ package com.android.systemui.dagger; -import com.android.systemui.keyguard.KeyguardQuickAffordanceProvider; +import com.android.systemui.keyguard.CustomizationProvider; import com.android.systemui.statusbar.NotificationInsetsModule; import com.android.systemui.statusbar.QsFrameTranslateModule; @@ -49,5 +49,5 @@ public interface ReferenceSysUIComponent extends SysUIComponent { /** * Member injection into the supplied argument. */ - void inject(KeyguardQuickAffordanceProvider keyguardQuickAffordanceProvider); + void inject(CustomizationProvider customizationProvider); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt new file mode 100644 index 000000000000..eaf1081a374a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt @@ -0,0 +1,393 @@ +/* + * Copyright (C) 2022 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 + +import android.content.ContentProvider +import android.content.ContentValues +import android.content.Context +import android.content.UriMatcher +import android.content.pm.PackageManager +import android.content.pm.ProviderInfo +import android.database.Cursor +import android.database.MatrixCursor +import android.net.Uri +import android.os.Binder +import android.os.Bundle +import android.util.Log +import com.android.systemui.SystemUIAppComponentFactoryBase +import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback +import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor +import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager +import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract +import javax.inject.Inject +import kotlinx.coroutines.runBlocking + +class CustomizationProvider : + ContentProvider(), SystemUIAppComponentFactoryBase.ContextInitializer { + + @Inject lateinit var interactor: KeyguardQuickAffordanceInteractor + @Inject lateinit var previewManager: KeyguardRemotePreviewManager + + private lateinit var contextAvailableCallback: ContextAvailableCallback + + private val uriMatcher = + UriMatcher(UriMatcher.NO_MATCH).apply { + addURI( + Contract.AUTHORITY, + Contract.LockScreenQuickAffordances.qualifiedTablePath( + Contract.LockScreenQuickAffordances.SlotTable.TABLE_NAME, + ), + MATCH_CODE_ALL_SLOTS, + ) + addURI( + Contract.AUTHORITY, + Contract.LockScreenQuickAffordances.qualifiedTablePath( + Contract.LockScreenQuickAffordances.AffordanceTable.TABLE_NAME, + ), + MATCH_CODE_ALL_AFFORDANCES, + ) + addURI( + Contract.AUTHORITY, + Contract.LockScreenQuickAffordances.qualifiedTablePath( + Contract.LockScreenQuickAffordances.SelectionTable.TABLE_NAME, + ), + MATCH_CODE_ALL_SELECTIONS, + ) + addURI( + Contract.AUTHORITY, + Contract.FlagsTable.TABLE_NAME, + MATCH_CODE_ALL_FLAGS, + ) + } + + override fun onCreate(): Boolean { + return true + } + + override fun attachInfo(context: Context?, info: ProviderInfo?) { + contextAvailableCallback.onContextAvailable(checkNotNull(context)) + super.attachInfo(context, info) + } + + override fun setContextAvailableCallback(callback: ContextAvailableCallback) { + contextAvailableCallback = callback + } + + override fun getType(uri: Uri): String? { + val prefix = + when (uriMatcher.match(uri)) { + MATCH_CODE_ALL_SLOTS, + MATCH_CODE_ALL_AFFORDANCES, + MATCH_CODE_ALL_FLAGS, + MATCH_CODE_ALL_SELECTIONS -> "vnd.android.cursor.dir/vnd." + else -> null + } + + val tableName = + when (uriMatcher.match(uri)) { + MATCH_CODE_ALL_SLOTS -> + Contract.LockScreenQuickAffordances.qualifiedTablePath( + Contract.LockScreenQuickAffordances.SlotTable.TABLE_NAME, + ) + MATCH_CODE_ALL_AFFORDANCES -> + Contract.LockScreenQuickAffordances.qualifiedTablePath( + Contract.LockScreenQuickAffordances.AffordanceTable.TABLE_NAME, + ) + MATCH_CODE_ALL_SELECTIONS -> + Contract.LockScreenQuickAffordances.qualifiedTablePath( + Contract.LockScreenQuickAffordances.SelectionTable.TABLE_NAME, + ) + MATCH_CODE_ALL_FLAGS -> Contract.FlagsTable.TABLE_NAME + else -> null + } + + if (prefix == null || tableName == null) { + return null + } + + return "$prefix${Contract.AUTHORITY}.$tableName" + } + + override fun insert(uri: Uri, values: ContentValues?): Uri? { + if (uriMatcher.match(uri) != MATCH_CODE_ALL_SELECTIONS) { + throw UnsupportedOperationException() + } + + return insertSelection(values) + } + + override fun query( + uri: Uri, + projection: Array?, + selection: String?, + selectionArgs: Array?, + sortOrder: String?, + ): Cursor? { + return when (uriMatcher.match(uri)) { + MATCH_CODE_ALL_AFFORDANCES -> runBlocking { queryAffordances() } + MATCH_CODE_ALL_SLOTS -> querySlots() + MATCH_CODE_ALL_SELECTIONS -> runBlocking { querySelections() } + MATCH_CODE_ALL_FLAGS -> queryFlags() + else -> null + } + } + + override fun update( + uri: Uri, + values: ContentValues?, + selection: String?, + selectionArgs: Array?, + ): Int { + Log.e(TAG, "Update is not supported!") + return 0 + } + + override fun delete( + uri: Uri, + selection: String?, + selectionArgs: Array?, + ): Int { + if (uriMatcher.match(uri) != MATCH_CODE_ALL_SELECTIONS) { + throw UnsupportedOperationException() + } + + return deleteSelection(uri, selectionArgs) + } + + override fun call(method: String, arg: String?, extras: Bundle?): Bundle? { + return if ( + requireContext() + .checkPermission( + android.Manifest.permission.BIND_WALLPAPER, + Binder.getCallingPid(), + Binder.getCallingUid(), + ) == PackageManager.PERMISSION_GRANTED + ) { + previewManager.preview(extras) + } else { + null + } + } + + private fun insertSelection(values: ContentValues?): Uri? { + if (values == null) { + throw IllegalArgumentException("Cannot insert selection, no values passed in!") + } + + if ( + !values.containsKey(Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID) + ) { + throw IllegalArgumentException( + "Cannot insert selection, " + + "\"${Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID}\"" + + " not specified!" + ) + } + + if ( + !values.containsKey( + Contract.LockScreenQuickAffordances.SelectionTable.Columns.AFFORDANCE_ID + ) + ) { + throw IllegalArgumentException( + "Cannot insert selection, " + + "\"${Contract.LockScreenQuickAffordances + .SelectionTable.Columns.AFFORDANCE_ID}\" not specified!" + ) + } + + val slotId = + values.getAsString(Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID) + val affordanceId = + values.getAsString( + Contract.LockScreenQuickAffordances.SelectionTable.Columns.AFFORDANCE_ID + ) + + if (slotId.isNullOrEmpty()) { + throw IllegalArgumentException("Cannot insert selection, slot ID was empty!") + } + + if (affordanceId.isNullOrEmpty()) { + throw IllegalArgumentException("Cannot insert selection, affordance ID was empty!") + } + + val success = + interactor.select( + slotId = slotId, + affordanceId = affordanceId, + ) + + return if (success) { + Log.d(TAG, "Successfully selected $affordanceId for slot $slotId") + context + ?.contentResolver + ?.notifyChange(Contract.LockScreenQuickAffordances.SelectionTable.URI, null) + Contract.LockScreenQuickAffordances.SelectionTable.URI + } else { + Log.d(TAG, "Failed to select $affordanceId for slot $slotId") + null + } + } + + private suspend fun querySelections(): Cursor { + return MatrixCursor( + arrayOf( + Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID, + Contract.LockScreenQuickAffordances.SelectionTable.Columns.AFFORDANCE_ID, + Contract.LockScreenQuickAffordances.SelectionTable.Columns.AFFORDANCE_NAME, + ) + ) + .apply { + val affordanceRepresentationsBySlotId = interactor.getSelections() + affordanceRepresentationsBySlotId.entries.forEach { + (slotId, affordanceRepresentations) -> + affordanceRepresentations.forEach { affordanceRepresentation -> + addRow( + arrayOf( + slotId, + affordanceRepresentation.id, + affordanceRepresentation.name, + ) + ) + } + } + } + } + + private suspend fun queryAffordances(): Cursor { + return MatrixCursor( + arrayOf( + Contract.LockScreenQuickAffordances.AffordanceTable.Columns.ID, + Contract.LockScreenQuickAffordances.AffordanceTable.Columns.NAME, + Contract.LockScreenQuickAffordances.AffordanceTable.Columns.ICON, + Contract.LockScreenQuickAffordances.AffordanceTable.Columns.IS_ENABLED, + Contract.LockScreenQuickAffordances.AffordanceTable.Columns + .ENABLEMENT_INSTRUCTIONS, + Contract.LockScreenQuickAffordances.AffordanceTable.Columns + .ENABLEMENT_ACTION_TEXT, + Contract.LockScreenQuickAffordances.AffordanceTable.Columns + .ENABLEMENT_COMPONENT_NAME, + ) + ) + .apply { + interactor.getAffordancePickerRepresentations().forEach { representation -> + addRow( + arrayOf( + representation.id, + representation.name, + representation.iconResourceId, + if (representation.isEnabled) 1 else 0, + representation.instructions?.joinToString( + Contract.LockScreenQuickAffordances.AffordanceTable + .ENABLEMENT_INSTRUCTIONS_DELIMITER + ), + representation.actionText, + representation.actionComponentName, + ) + ) + } + } + } + + private fun querySlots(): Cursor { + return MatrixCursor( + arrayOf( + Contract.LockScreenQuickAffordances.SlotTable.Columns.ID, + Contract.LockScreenQuickAffordances.SlotTable.Columns.CAPACITY, + ) + ) + .apply { + interactor.getSlotPickerRepresentations().forEach { representation -> + addRow( + arrayOf( + representation.id, + representation.maxSelectedAffordances, + ) + ) + } + } + } + + private fun queryFlags(): Cursor { + return MatrixCursor( + arrayOf( + Contract.FlagsTable.Columns.NAME, + Contract.FlagsTable.Columns.VALUE, + ) + ) + .apply { + interactor.getPickerFlags().forEach { flag -> + addRow( + arrayOf( + flag.name, + if (flag.value) { + 1 + } else { + 0 + }, + ) + ) + } + } + } + + private fun deleteSelection( + uri: Uri, + selectionArgs: Array?, + ): Int { + if (selectionArgs == null) { + throw IllegalArgumentException( + "Cannot delete selection, selection arguments not included!" + ) + } + + val (slotId, affordanceId) = + when (selectionArgs.size) { + 1 -> Pair(selectionArgs[0], null) + 2 -> Pair(selectionArgs[0], selectionArgs[1]) + else -> + throw IllegalArgumentException( + "Cannot delete selection, selection arguments has wrong size, expected to" + + " have 1 or 2 arguments, had ${selectionArgs.size} instead!" + ) + } + + val deleted = + interactor.unselect( + slotId = slotId, + affordanceId = affordanceId, + ) + + return if (deleted) { + Log.d(TAG, "Successfully unselected $affordanceId for slot $slotId") + context?.contentResolver?.notifyChange(uri, null) + 1 + } else { + Log.d(TAG, "Failed to unselect $affordanceId for slot $slotId") + 0 + } + } + + companion object { + private const val TAG = "KeyguardQuickAffordanceProvider" + private const val MATCH_CODE_ALL_SLOTS = 1 + private const val MATCH_CODE_ALL_AFFORDANCES = 2 + private const val MATCH_CODE_ALL_SELECTIONS = 3 + private const val MATCH_CODE_ALL_FLAGS = 4 + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt deleted file mode 100644 index cbcede023708..000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt +++ /dev/null @@ -1,360 +0,0 @@ -/* - * Copyright (C) 2022 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 - -import android.content.ContentProvider -import android.content.ContentValues -import android.content.Context -import android.content.UriMatcher -import android.content.pm.PackageManager -import android.content.pm.ProviderInfo -import android.database.Cursor -import android.database.MatrixCursor -import android.net.Uri -import android.os.Binder -import android.os.Bundle -import android.util.Log -import com.android.systemui.SystemUIAppComponentFactoryBase -import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback -import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor -import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager -import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract -import javax.inject.Inject -import kotlinx.coroutines.runBlocking - -class KeyguardQuickAffordanceProvider : - ContentProvider(), SystemUIAppComponentFactoryBase.ContextInitializer { - - @Inject lateinit var interactor: KeyguardQuickAffordanceInteractor - @Inject lateinit var previewManager: KeyguardRemotePreviewManager - - private lateinit var contextAvailableCallback: ContextAvailableCallback - - private val uriMatcher = - UriMatcher(UriMatcher.NO_MATCH).apply { - addURI( - Contract.AUTHORITY, - Contract.SlotTable.TABLE_NAME, - MATCH_CODE_ALL_SLOTS, - ) - addURI( - Contract.AUTHORITY, - Contract.AffordanceTable.TABLE_NAME, - MATCH_CODE_ALL_AFFORDANCES, - ) - addURI( - Contract.AUTHORITY, - Contract.SelectionTable.TABLE_NAME, - MATCH_CODE_ALL_SELECTIONS, - ) - addURI( - Contract.AUTHORITY, - Contract.FlagsTable.TABLE_NAME, - MATCH_CODE_ALL_FLAGS, - ) - } - - override fun onCreate(): Boolean { - return true - } - - override fun attachInfo(context: Context?, info: ProviderInfo?) { - contextAvailableCallback.onContextAvailable(checkNotNull(context)) - super.attachInfo(context, info) - } - - override fun setContextAvailableCallback(callback: ContextAvailableCallback) { - contextAvailableCallback = callback - } - - override fun getType(uri: Uri): String? { - val prefix = - when (uriMatcher.match(uri)) { - MATCH_CODE_ALL_SLOTS, - MATCH_CODE_ALL_AFFORDANCES, - MATCH_CODE_ALL_FLAGS, - MATCH_CODE_ALL_SELECTIONS -> "vnd.android.cursor.dir/vnd." - else -> null - } - - val tableName = - when (uriMatcher.match(uri)) { - MATCH_CODE_ALL_SLOTS -> Contract.SlotTable.TABLE_NAME - MATCH_CODE_ALL_AFFORDANCES -> Contract.AffordanceTable.TABLE_NAME - MATCH_CODE_ALL_SELECTIONS -> Contract.SelectionTable.TABLE_NAME - MATCH_CODE_ALL_FLAGS -> Contract.FlagsTable.TABLE_NAME - else -> null - } - - if (prefix == null || tableName == null) { - return null - } - - return "$prefix${Contract.AUTHORITY}.$tableName" - } - - override fun insert(uri: Uri, values: ContentValues?): Uri? { - if (uriMatcher.match(uri) != MATCH_CODE_ALL_SELECTIONS) { - throw UnsupportedOperationException() - } - - return insertSelection(values) - } - - override fun query( - uri: Uri, - projection: Array?, - selection: String?, - selectionArgs: Array?, - sortOrder: String?, - ): Cursor? { - return when (uriMatcher.match(uri)) { - MATCH_CODE_ALL_AFFORDANCES -> runBlocking { queryAffordances() } - MATCH_CODE_ALL_SLOTS -> querySlots() - MATCH_CODE_ALL_SELECTIONS -> runBlocking { querySelections() } - MATCH_CODE_ALL_FLAGS -> queryFlags() - else -> null - } - } - - override fun update( - uri: Uri, - values: ContentValues?, - selection: String?, - selectionArgs: Array?, - ): Int { - Log.e(TAG, "Update is not supported!") - return 0 - } - - override fun delete( - uri: Uri, - selection: String?, - selectionArgs: Array?, - ): Int { - if (uriMatcher.match(uri) != MATCH_CODE_ALL_SELECTIONS) { - throw UnsupportedOperationException() - } - - return deleteSelection(uri, selectionArgs) - } - - override fun call(method: String, arg: String?, extras: Bundle?): Bundle? { - return if ( - requireContext() - .checkPermission( - android.Manifest.permission.BIND_WALLPAPER, - Binder.getCallingPid(), - Binder.getCallingUid(), - ) == PackageManager.PERMISSION_GRANTED - ) { - previewManager.preview(extras) - } else { - null - } - } - - private fun insertSelection(values: ContentValues?): Uri? { - if (values == null) { - throw IllegalArgumentException("Cannot insert selection, no values passed in!") - } - - if (!values.containsKey(Contract.SelectionTable.Columns.SLOT_ID)) { - throw IllegalArgumentException( - "Cannot insert selection, " + - "\"${Contract.SelectionTable.Columns.SLOT_ID}\" not specified!" - ) - } - - if (!values.containsKey(Contract.SelectionTable.Columns.AFFORDANCE_ID)) { - throw IllegalArgumentException( - "Cannot insert selection, " + - "\"${Contract.SelectionTable.Columns.AFFORDANCE_ID}\" not specified!" - ) - } - - val slotId = values.getAsString(Contract.SelectionTable.Columns.SLOT_ID) - val affordanceId = values.getAsString(Contract.SelectionTable.Columns.AFFORDANCE_ID) - - if (slotId.isNullOrEmpty()) { - throw IllegalArgumentException("Cannot insert selection, slot ID was empty!") - } - - if (affordanceId.isNullOrEmpty()) { - throw IllegalArgumentException("Cannot insert selection, affordance ID was empty!") - } - - val success = - interactor.select( - slotId = slotId, - affordanceId = affordanceId, - ) - - return if (success) { - Log.d(TAG, "Successfully selected $affordanceId for slot $slotId") - context?.contentResolver?.notifyChange(Contract.SelectionTable.URI, null) - Contract.SelectionTable.URI - } else { - Log.d(TAG, "Failed to select $affordanceId for slot $slotId") - null - } - } - - private suspend fun querySelections(): Cursor { - return MatrixCursor( - arrayOf( - Contract.SelectionTable.Columns.SLOT_ID, - Contract.SelectionTable.Columns.AFFORDANCE_ID, - Contract.SelectionTable.Columns.AFFORDANCE_NAME, - ) - ) - .apply { - val affordanceRepresentationsBySlotId = interactor.getSelections() - affordanceRepresentationsBySlotId.entries.forEach { - (slotId, affordanceRepresentations) -> - affordanceRepresentations.forEach { affordanceRepresentation -> - addRow( - arrayOf( - slotId, - affordanceRepresentation.id, - affordanceRepresentation.name, - ) - ) - } - } - } - } - - private suspend fun queryAffordances(): Cursor { - return MatrixCursor( - arrayOf( - Contract.AffordanceTable.Columns.ID, - Contract.AffordanceTable.Columns.NAME, - 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 { - interactor.getAffordancePickerRepresentations().forEach { representation -> - addRow( - arrayOf( - representation.id, - representation.name, - representation.iconResourceId, - if (representation.isEnabled) 1 else 0, - representation.instructions?.joinToString( - Contract.AffordanceTable.ENABLEMENT_INSTRUCTIONS_DELIMITER - ), - representation.actionText, - representation.actionComponentName, - ) - ) - } - } - } - - private fun querySlots(): Cursor { - return MatrixCursor( - arrayOf( - Contract.SlotTable.Columns.ID, - Contract.SlotTable.Columns.CAPACITY, - ) - ) - .apply { - interactor.getSlotPickerRepresentations().forEach { representation -> - addRow( - arrayOf( - representation.id, - representation.maxSelectedAffordances, - ) - ) - } - } - } - - private fun queryFlags(): Cursor { - return MatrixCursor( - arrayOf( - Contract.FlagsTable.Columns.NAME, - Contract.FlagsTable.Columns.VALUE, - ) - ) - .apply { - interactor.getPickerFlags().forEach { flag -> - addRow( - arrayOf( - flag.name, - if (flag.value) { - 1 - } else { - 0 - }, - ) - ) - } - } - } - - private fun deleteSelection( - uri: Uri, - selectionArgs: Array?, - ): Int { - if (selectionArgs == null) { - throw IllegalArgumentException( - "Cannot delete selection, selection arguments not included!" - ) - } - - val (slotId, affordanceId) = - when (selectionArgs.size) { - 1 -> Pair(selectionArgs[0], null) - 2 -> Pair(selectionArgs[0], selectionArgs[1]) - else -> - throw IllegalArgumentException( - "Cannot delete selection, selection arguments has wrong size, expected to" + - " have 1 or 2 arguments, had ${selectionArgs.size} instead!" - ) - } - - val deleted = - interactor.unselect( - slotId = slotId, - affordanceId = affordanceId, - ) - - return if (deleted) { - Log.d(TAG, "Successfully unselected $affordanceId for slot $slotId") - context?.contentResolver?.notifyChange(uri, null) - 1 - } else { - Log.d(TAG, "Failed to unselect $affordanceId for slot $slotId") - 0 - } - } - - companion object { - private const val TAG = "KeyguardQuickAffordanceProvider" - private const val MATCH_CODE_ALL_SLOTS = 1 - private const val MATCH_CODE_ALL_AFFORDANCES = 2 - private const val MATCH_CODE_ALL_SELECTIONS = 3 - private const val MATCH_CODE_ALL_FLAGS = 4 - } -} 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 02ebcd3200a4..20588e9ccdc1 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 @@ -22,7 +22,7 @@ import android.content.Intent 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.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract +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. */ @@ -90,8 +90,9 @@ interface KeyguardQuickAffordanceConfig { * 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. + * separated by + * [Contract.LockScreenQuickAffordances.AffordanceTable.COMPONENT_NAME_SEPARATOR] for + * convenience, you can use the [componentName] function. */ val actionComponentName: String? = null, ) : PickerScreenState() { @@ -145,8 +146,8 @@ interface KeyguardQuickAffordanceConfig { /** * Returning this as a result from the [onTriggered] method means that the implementation - * has _not_ taken care of the action and the system should show a Dialog using the - * given [AlertDialog] and [Expandable]. + * has _not_ taken care of the action and the system should show a Dialog using the given + * [AlertDialog] and [Expandable]. */ data class ShowDialog( val dialog: AlertDialog, @@ -162,7 +163,8 @@ interface KeyguardQuickAffordanceConfig { return when { action.isNullOrEmpty() -> null !packageName.isNullOrEmpty() -> - "$packageName${Contract.AffordanceTable.COMPONENT_NAME_SEPARATOR}$action" + "$packageName${Contract.LockScreenQuickAffordances.AffordanceTable + .COMPONENT_NAME_SEPARATOR}$action" else -> action } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceProviderClientFactory.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceProviderClientFactory.kt index 727a81391dc2..60ef116489bd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceProviderClientFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceProviderClientFactory.kt @@ -19,13 +19,13 @@ package com.android.systemui.keyguard.data.quickaffordance import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.settings.UserTracker -import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderClient -import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderClientImpl +import com.android.systemui.shared.customization.data.content.CustomizationProviderClient +import com.android.systemui.shared.customization.data.content.CustomizationProviderClientImpl import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher interface KeyguardQuickAffordanceProviderClientFactory { - fun create(): KeyguardQuickAffordanceProviderClient + fun create(): CustomizationProviderClient } class KeyguardQuickAffordanceProviderClientFactoryImpl @@ -34,8 +34,8 @@ constructor( private val userTracker: UserTracker, @Background private val backgroundDispatcher: CoroutineDispatcher, ) : KeyguardQuickAffordanceProviderClientFactory { - override fun create(): KeyguardQuickAffordanceProviderClient { - return KeyguardQuickAffordanceProviderClientImpl( + override fun create(): CustomizationProviderClient { + return CustomizationProviderClientImpl( context = userTracker.userContext, backgroundDispatcher = backgroundDispatcher, ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManager.kt index 8ffef25d3aae..e9bd88932c52 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManager.kt @@ -24,7 +24,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.settings.UserTracker -import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderClient +import com.android.systemui.shared.customization.data.content.CustomizationProviderClient import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -69,7 +69,7 @@ constructor( awaitClose { userTracker.removeCallback(callback) } } - private val clientOrNull: StateFlow = + private val clientOrNull: StateFlow = userId .distinctUntilChanged() .map { selectedUserId -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt index 9772cb9806a6..c219380ad263 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt @@ -36,7 +36,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentati import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserTracker -import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract +import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.policy.KeyguardStateController import dagger.Lazy diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml index d8331ab34640..d4e06bc2a5c8 100644 --- a/packages/SystemUI/tests/AndroidManifest.xml +++ b/packages/SystemUI/tests/AndroidManifest.xml @@ -146,7 +146,7 @@ tools:replace="android:authorities" tools:node="remove" /> - ().apply { + whenever( + getSharedPreferences( + anyString(), + anyInt(), + anyInt(), + ) + ) + .thenReturn(FakeSharedPreferences()) + }, + userTracker = userTracker, + broadcastDispatcher = fakeBroadcastDispatcher, + ) + val remoteUserSelectionManager = + KeyguardQuickAffordanceRemoteUserSelectionManager( + scope = testScope.backgroundScope, + userTracker = userTracker, + clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker), + userHandle = UserHandle.SYSTEM, + ) + val quickAffordanceRepository = + KeyguardQuickAffordanceRepository( + appContext = context, + scope = testScope.backgroundScope, + localUserSelectionManager = localUserSelectionManager, + remoteUserSelectionManager = remoteUserSelectionManager, + userTracker = userTracker, + configs = + setOf( + FakeKeyguardQuickAffordanceConfig( + key = AFFORDANCE_1, + pickerName = AFFORDANCE_1_NAME, + pickerIconResourceId = 1, + ), + FakeKeyguardQuickAffordanceConfig( + key = AFFORDANCE_2, + pickerName = AFFORDANCE_2_NAME, + pickerIconResourceId = 2, + ), + ), + legacySettingSyncer = + KeyguardQuickAffordanceLegacySettingSyncer( + scope = testScope.backgroundScope, + backgroundDispatcher = testDispatcher, + secureSettings = FakeSettings(), + selectionsManager = localUserSelectionManager, + ), + dumpManager = mock(), + userHandle = UserHandle.SYSTEM, + ) + underTest.interactor = + KeyguardQuickAffordanceInteractor( + keyguardInteractor = + KeyguardInteractor( + repository = FakeKeyguardRepository(), + ), + registry = mock(), + lockPatternUtils = lockPatternUtils, + keyguardStateController = keyguardStateController, + userTracker = userTracker, + activityStarter = activityStarter, + featureFlags = + FakeFeatureFlags().apply { + set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true) + set(Flags.LOCKSCREEN_CUSTOM_CLOCKS, true) + set(Flags.REVAMPED_WALLPAPER_UI, true) + }, + repository = { quickAffordanceRepository }, + launchAnimator = launchAnimator, + ) + underTest.previewManager = + KeyguardRemotePreviewManager( + previewRendererFactory = previewRendererFactory, + mainDispatcher = testDispatcher, + backgroundHandler = backgroundHandler, + ) + + underTest.attachInfoForTesting( + context, + ProviderInfo().apply { authority = Contract.AUTHORITY }, + ) + context.contentResolver.addProvider(Contract.AUTHORITY, underTest) + context.testablePermissions.setPermission( + Contract.PERMISSION, + PackageManager.PERMISSION_GRANTED, + ) + } + + @Test + fun `onAttachInfo - reportsContext`() { + val callback: SystemUIAppComponentFactoryBase.ContextAvailableCallback = mock() + underTest.setContextAvailableCallback(callback) + + underTest.attachInfo(context, null) + + verify(callback).onContextAvailable(context) + } + + @Test + fun getType() { + assertThat(underTest.getType(Contract.LockScreenQuickAffordances.AffordanceTable.URI)) + .isEqualTo( + "vnd.android.cursor.dir/vnd." + + "${Contract.AUTHORITY}." + + Contract.LockScreenQuickAffordances.qualifiedTablePath( + Contract.LockScreenQuickAffordances.AffordanceTable.TABLE_NAME + ) + ) + assertThat(underTest.getType(Contract.LockScreenQuickAffordances.SlotTable.URI)) + .isEqualTo( + "vnd.android.cursor.dir/vnd.${Contract.AUTHORITY}." + + Contract.LockScreenQuickAffordances.qualifiedTablePath( + Contract.LockScreenQuickAffordances.SlotTable.TABLE_NAME + ) + ) + assertThat(underTest.getType(Contract.LockScreenQuickAffordances.SelectionTable.URI)) + .isEqualTo( + "vnd.android.cursor.dir/vnd." + + "${Contract.AUTHORITY}." + + Contract.LockScreenQuickAffordances.qualifiedTablePath( + Contract.LockScreenQuickAffordances.SelectionTable.TABLE_NAME + ) + ) + assertThat(underTest.getType(Contract.FlagsTable.URI)) + .isEqualTo( + "vnd.android.cursor.dir/vnd." + + "${Contract.AUTHORITY}." + + Contract.FlagsTable.TABLE_NAME + ) + } + + @Test + fun `insert and query selection`() = + testScope.runTest { + val slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START + val affordanceId = AFFORDANCE_2 + val affordanceName = AFFORDANCE_2_NAME + + insertSelection( + slotId = slotId, + affordanceId = affordanceId, + ) + + assertThat(querySelections()) + .isEqualTo( + listOf( + Selection( + slotId = slotId, + affordanceId = affordanceId, + affordanceName = affordanceName, + ) + ) + ) + } + + @Test + fun `query slots`() = + testScope.runTest { + assertThat(querySlots()) + .isEqualTo( + listOf( + Slot( + id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, + capacity = 1, + ), + Slot( + id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, + capacity = 1, + ), + ) + ) + } + + @Test + fun `query affordances`() = + testScope.runTest { + assertThat(queryAffordances()) + .isEqualTo( + listOf( + Affordance( + id = AFFORDANCE_1, + name = AFFORDANCE_1_NAME, + iconResourceId = 1, + ), + Affordance( + id = AFFORDANCE_2, + name = AFFORDANCE_2_NAME, + iconResourceId = 2, + ), + ) + ) + } + + @Test + fun `delete and query selection`() = + testScope.runTest { + insertSelection( + slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, + affordanceId = AFFORDANCE_1, + ) + insertSelection( + slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, + affordanceId = AFFORDANCE_2, + ) + + context.contentResolver.delete( + Contract.LockScreenQuickAffordances.SelectionTable.URI, + "${Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID} = ? AND" + + " ${Contract.LockScreenQuickAffordances.SelectionTable.Columns.AFFORDANCE_ID}" + + " = ?", + arrayOf( + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, + AFFORDANCE_2, + ), + ) + + assertThat(querySelections()) + .isEqualTo( + listOf( + Selection( + slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, + affordanceId = AFFORDANCE_1, + affordanceName = AFFORDANCE_1_NAME, + ) + ) + ) + } + + @Test + fun `delete all selections in a slot`() = + testScope.runTest { + insertSelection( + slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, + affordanceId = AFFORDANCE_1, + ) + insertSelection( + slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, + affordanceId = AFFORDANCE_2, + ) + + context.contentResolver.delete( + Contract.LockScreenQuickAffordances.SelectionTable.URI, + Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID, + arrayOf( + KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, + ), + ) + + assertThat(querySelections()) + .isEqualTo( + listOf( + Selection( + slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, + affordanceId = AFFORDANCE_1, + affordanceName = AFFORDANCE_1_NAME, + ) + ) + ) + } + + @Test + fun preview() = + testScope.runTest { + val hostToken: IBinder = mock() + whenever(previewRenderer.hostToken).thenReturn(hostToken) + val extras = Bundle() + + val result = underTest.call("whatever", "anything", extras) + + verify(previewRenderer).render() + verify(hostToken).linkToDeath(any(), anyInt()) + assertThat(result!!).isNotNull() + assertThat(result.get(KeyguardRemotePreviewManager.KEY_PREVIEW_SURFACE_PACKAGE)) + .isEqualTo(previewSurfacePackage) + assertThat(result.containsKey(KeyguardRemotePreviewManager.KEY_PREVIEW_CALLBACK)) + } + + private fun insertSelection( + slotId: String, + affordanceId: String, + ) { + context.contentResolver.insert( + Contract.LockScreenQuickAffordances.SelectionTable.URI, + ContentValues().apply { + put(Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID, slotId) + put( + Contract.LockScreenQuickAffordances.SelectionTable.Columns.AFFORDANCE_ID, + affordanceId + ) + } + ) + } + + private fun querySelections(): List { + return context.contentResolver + .query( + Contract.LockScreenQuickAffordances.SelectionTable.URI, + null, + null, + null, + null, + ) + ?.use { cursor -> + buildList { + val slotIdColumnIndex = + cursor.getColumnIndex( + Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID + ) + val affordanceIdColumnIndex = + cursor.getColumnIndex( + Contract.LockScreenQuickAffordances.SelectionTable.Columns.AFFORDANCE_ID + ) + val affordanceNameColumnIndex = + cursor.getColumnIndex( + Contract.LockScreenQuickAffordances.SelectionTable.Columns + .AFFORDANCE_NAME + ) + if ( + slotIdColumnIndex == -1 || + affordanceIdColumnIndex == -1 || + affordanceNameColumnIndex == -1 + ) { + return@buildList + } + + while (cursor.moveToNext()) { + add( + Selection( + slotId = cursor.getString(slotIdColumnIndex), + affordanceId = cursor.getString(affordanceIdColumnIndex), + affordanceName = cursor.getString(affordanceNameColumnIndex), + ) + ) + } + } + } + ?: emptyList() + } + + private fun querySlots(): List { + return context.contentResolver + .query( + Contract.LockScreenQuickAffordances.SlotTable.URI, + null, + null, + null, + null, + ) + ?.use { cursor -> + buildList { + val idColumnIndex = + cursor.getColumnIndex( + Contract.LockScreenQuickAffordances.SlotTable.Columns.ID + ) + val capacityColumnIndex = + cursor.getColumnIndex( + Contract.LockScreenQuickAffordances.SlotTable.Columns.CAPACITY + ) + if (idColumnIndex == -1 || capacityColumnIndex == -1) { + return@buildList + } + + while (cursor.moveToNext()) { + add( + Slot( + id = cursor.getString(idColumnIndex), + capacity = cursor.getInt(capacityColumnIndex), + ) + ) + } + } + } + ?: emptyList() + } + + private fun queryAffordances(): List { + return context.contentResolver + .query( + Contract.LockScreenQuickAffordances.AffordanceTable.URI, + null, + null, + null, + null, + ) + ?.use { cursor -> + buildList { + val idColumnIndex = + cursor.getColumnIndex( + Contract.LockScreenQuickAffordances.AffordanceTable.Columns.ID + ) + val nameColumnIndex = + cursor.getColumnIndex( + Contract.LockScreenQuickAffordances.AffordanceTable.Columns.NAME + ) + val iconColumnIndex = + cursor.getColumnIndex( + Contract.LockScreenQuickAffordances.AffordanceTable.Columns.ICON + ) + if (idColumnIndex == -1 || nameColumnIndex == -1 || iconColumnIndex == -1) { + return@buildList + } + + while (cursor.moveToNext()) { + add( + Affordance( + id = cursor.getString(idColumnIndex), + name = cursor.getString(nameColumnIndex), + iconResourceId = cursor.getInt(iconColumnIndex), + ) + ) + } + } + } + ?: emptyList() + } + + data class Slot( + val id: String, + val capacity: Int, + ) + + data class Affordance( + val id: String, + val name: String, + val iconResourceId: Int, + ) + + data class Selection( + val slotId: String, + val affordanceId: String, + val affordanceName: String, + ) + + companion object { + private const val AFFORDANCE_1 = "affordance_1" + private const val AFFORDANCE_2 = "affordance_2" + private const val AFFORDANCE_1_NAME = "affordance_1_name" + private const val AFFORDANCE_2_NAME = "affordance_2_name" + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt deleted file mode 100644 index 5e4a43f79711..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt +++ /dev/null @@ -1,509 +0,0 @@ -/* - * Copyright (C) 2022 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 - -import android.content.ContentValues -import android.content.pm.PackageManager -import android.content.pm.ProviderInfo -import android.os.Bundle -import android.os.Handler -import android.os.IBinder -import android.os.UserHandle -import android.testing.AndroidTestingRunner -import android.testing.TestableLooper -import android.view.SurfaceControlViewHost -import androidx.test.filters.SmallTest -import com.android.internal.widget.LockPatternUtils -import com.android.systemui.SystemUIAppComponentFactoryBase -import com.android.systemui.SysuiTestCase -import com.android.systemui.animation.DialogLaunchAnimator -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags -import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig -import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceProviderClientFactory -import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLegacySettingSyncer -import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceLocalUserSelectionManager -import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceRemoteUserSelectionManager -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository -import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor -import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor -import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRenderer -import com.android.systemui.keyguard.ui.preview.KeyguardPreviewRendererFactory -import com.android.systemui.keyguard.ui.preview.KeyguardRemotePreviewManager -import com.android.systemui.plugins.ActivityStarter -import com.android.systemui.settings.UserFileManager -import com.android.systemui.settings.UserTracker -import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots -import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderContract as Contract -import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.util.FakeSharedPreferences -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.whenever -import com.android.systemui.util.settings.FakeSettings -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.StandardTestDispatcher -import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentMatchers.anyInt -import org.mockito.ArgumentMatchers.anyString -import org.mockito.Mock -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations - -@OptIn(ExperimentalCoroutinesApi::class) -@SmallTest -@RunWith(AndroidTestingRunner::class) -@TestableLooper.RunWithLooper(setAsMainLooper = true) -class KeyguardQuickAffordanceProviderTest : SysuiTestCase() { - - @Mock private lateinit var lockPatternUtils: LockPatternUtils - @Mock private lateinit var keyguardStateController: KeyguardStateController - @Mock private lateinit var userTracker: UserTracker - @Mock private lateinit var activityStarter: ActivityStarter - @Mock private lateinit var previewRendererFactory: KeyguardPreviewRendererFactory - @Mock private lateinit var previewRenderer: KeyguardPreviewRenderer - @Mock private lateinit var backgroundHandler: Handler - @Mock private lateinit var previewSurfacePackage: SurfaceControlViewHost.SurfacePackage - @Mock private lateinit var launchAnimator: DialogLaunchAnimator - - private lateinit var underTest: KeyguardQuickAffordanceProvider - - private lateinit var testScope: TestScope - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - whenever(previewRenderer.surfacePackage).thenReturn(previewSurfacePackage) - whenever(previewRendererFactory.create(any())).thenReturn(previewRenderer) - whenever(backgroundHandler.looper).thenReturn(TestableLooper.get(this).looper) - - underTest = KeyguardQuickAffordanceProvider() - val testDispatcher = StandardTestDispatcher() - testScope = TestScope(testDispatcher) - val localUserSelectionManager = - KeyguardQuickAffordanceLocalUserSelectionManager( - context = context, - userFileManager = - mock().apply { - whenever( - getSharedPreferences( - anyString(), - anyInt(), - anyInt(), - ) - ) - .thenReturn(FakeSharedPreferences()) - }, - userTracker = userTracker, - broadcastDispatcher = fakeBroadcastDispatcher, - ) - val remoteUserSelectionManager = - KeyguardQuickAffordanceRemoteUserSelectionManager( - scope = testScope.backgroundScope, - userTracker = userTracker, - clientFactory = FakeKeyguardQuickAffordanceProviderClientFactory(userTracker), - userHandle = UserHandle.SYSTEM, - ) - val quickAffordanceRepository = - KeyguardQuickAffordanceRepository( - appContext = context, - scope = testScope.backgroundScope, - localUserSelectionManager = localUserSelectionManager, - remoteUserSelectionManager = remoteUserSelectionManager, - userTracker = userTracker, - configs = - setOf( - FakeKeyguardQuickAffordanceConfig( - key = AFFORDANCE_1, - pickerName = AFFORDANCE_1_NAME, - pickerIconResourceId = 1, - ), - FakeKeyguardQuickAffordanceConfig( - key = AFFORDANCE_2, - pickerName = AFFORDANCE_2_NAME, - pickerIconResourceId = 2, - ), - ), - legacySettingSyncer = - KeyguardQuickAffordanceLegacySettingSyncer( - scope = testScope.backgroundScope, - backgroundDispatcher = testDispatcher, - secureSettings = FakeSettings(), - selectionsManager = localUserSelectionManager, - ), - dumpManager = mock(), - userHandle = UserHandle.SYSTEM, - ) - underTest.interactor = - KeyguardQuickAffordanceInteractor( - keyguardInteractor = - KeyguardInteractor( - repository = FakeKeyguardRepository(), - ), - registry = mock(), - lockPatternUtils = lockPatternUtils, - keyguardStateController = keyguardStateController, - userTracker = userTracker, - activityStarter = activityStarter, - featureFlags = - FakeFeatureFlags().apply { - set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true) - set(Flags.LOCKSCREEN_CUSTOM_CLOCKS, true) - set(Flags.REVAMPED_WALLPAPER_UI, true) - }, - repository = { quickAffordanceRepository }, - launchAnimator = launchAnimator, - ) - underTest.previewManager = - KeyguardRemotePreviewManager( - previewRendererFactory = previewRendererFactory, - mainDispatcher = testDispatcher, - backgroundHandler = backgroundHandler, - ) - - underTest.attachInfoForTesting( - context, - ProviderInfo().apply { authority = Contract.AUTHORITY }, - ) - context.contentResolver.addProvider(Contract.AUTHORITY, underTest) - context.testablePermissions.setPermission( - Contract.PERMISSION, - PackageManager.PERMISSION_GRANTED, - ) - } - - @Test - fun `onAttachInfo - reportsContext`() { - val callback: SystemUIAppComponentFactoryBase.ContextAvailableCallback = mock() - underTest.setContextAvailableCallback(callback) - - underTest.attachInfo(context, null) - - verify(callback).onContextAvailable(context) - } - - @Test - fun getType() { - assertThat(underTest.getType(Contract.AffordanceTable.URI)) - .isEqualTo( - "vnd.android.cursor.dir/vnd." + - "${Contract.AUTHORITY}.${Contract.AffordanceTable.TABLE_NAME}" - ) - assertThat(underTest.getType(Contract.SlotTable.URI)) - .isEqualTo( - "vnd.android.cursor.dir/vnd.${Contract.AUTHORITY}.${Contract.SlotTable.TABLE_NAME}" - ) - assertThat(underTest.getType(Contract.SelectionTable.URI)) - .isEqualTo( - "vnd.android.cursor.dir/vnd." + - "${Contract.AUTHORITY}.${Contract.SelectionTable.TABLE_NAME}" - ) - } - - @Test - fun `insert and query selection`() = - testScope.runTest { - val slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START - val affordanceId = AFFORDANCE_2 - val affordanceName = AFFORDANCE_2_NAME - - insertSelection( - slotId = slotId, - affordanceId = affordanceId, - ) - - assertThat(querySelections()) - .isEqualTo( - listOf( - Selection( - slotId = slotId, - affordanceId = affordanceId, - affordanceName = affordanceName, - ) - ) - ) - } - - @Test - fun `query slots`() = - testScope.runTest { - assertThat(querySlots()) - .isEqualTo( - listOf( - Slot( - id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, - capacity = 1, - ), - Slot( - id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, - capacity = 1, - ), - ) - ) - } - - @Test - fun `query affordances`() = - testScope.runTest { - assertThat(queryAffordances()) - .isEqualTo( - listOf( - Affordance( - id = AFFORDANCE_1, - name = AFFORDANCE_1_NAME, - iconResourceId = 1, - ), - Affordance( - id = AFFORDANCE_2, - name = AFFORDANCE_2_NAME, - iconResourceId = 2, - ), - ) - ) - } - - @Test - fun `delete and query selection`() = - testScope.runTest { - insertSelection( - slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, - affordanceId = AFFORDANCE_1, - ) - insertSelection( - slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, - affordanceId = AFFORDANCE_2, - ) - - context.contentResolver.delete( - Contract.SelectionTable.URI, - "${Contract.SelectionTable.Columns.SLOT_ID} = ? AND" + - " ${Contract.SelectionTable.Columns.AFFORDANCE_ID} = ?", - arrayOf( - KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, - AFFORDANCE_2, - ), - ) - - assertThat(querySelections()) - .isEqualTo( - listOf( - Selection( - slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, - affordanceId = AFFORDANCE_1, - affordanceName = AFFORDANCE_1_NAME, - ) - ) - ) - } - - @Test - fun `delete all selections in a slot`() = - testScope.runTest { - insertSelection( - slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, - affordanceId = AFFORDANCE_1, - ) - insertSelection( - slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, - affordanceId = AFFORDANCE_2, - ) - - context.contentResolver.delete( - Contract.SelectionTable.URI, - Contract.SelectionTable.Columns.SLOT_ID, - arrayOf( - KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, - ), - ) - - assertThat(querySelections()) - .isEqualTo( - listOf( - Selection( - slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, - affordanceId = AFFORDANCE_1, - affordanceName = AFFORDANCE_1_NAME, - ) - ) - ) - } - - @Test - fun preview() = - testScope.runTest { - val hostToken: IBinder = mock() - whenever(previewRenderer.hostToken).thenReturn(hostToken) - val extras = Bundle() - - val result = underTest.call("whatever", "anything", extras) - - verify(previewRenderer).render() - verify(hostToken).linkToDeath(any(), anyInt()) - assertThat(result!!).isNotNull() - assertThat(result.get(KeyguardRemotePreviewManager.KEY_PREVIEW_SURFACE_PACKAGE)) - .isEqualTo(previewSurfacePackage) - assertThat(result.containsKey(KeyguardRemotePreviewManager.KEY_PREVIEW_CALLBACK)) - } - - private fun insertSelection( - slotId: String, - affordanceId: String, - ) { - context.contentResolver.insert( - Contract.SelectionTable.URI, - ContentValues().apply { - put(Contract.SelectionTable.Columns.SLOT_ID, slotId) - put(Contract.SelectionTable.Columns.AFFORDANCE_ID, affordanceId) - } - ) - } - - private fun querySelections(): List { - return context.contentResolver - .query( - Contract.SelectionTable.URI, - null, - null, - null, - null, - ) - ?.use { cursor -> - buildList { - val slotIdColumnIndex = - cursor.getColumnIndex(Contract.SelectionTable.Columns.SLOT_ID) - val affordanceIdColumnIndex = - cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_ID) - val affordanceNameColumnIndex = - cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_NAME) - if ( - slotIdColumnIndex == -1 || - affordanceIdColumnIndex == -1 || - affordanceNameColumnIndex == -1 - ) { - return@buildList - } - - while (cursor.moveToNext()) { - add( - Selection( - slotId = cursor.getString(slotIdColumnIndex), - affordanceId = cursor.getString(affordanceIdColumnIndex), - affordanceName = cursor.getString(affordanceNameColumnIndex), - ) - ) - } - } - } - ?: emptyList() - } - - private fun querySlots(): List { - return context.contentResolver - .query( - Contract.SlotTable.URI, - null, - null, - null, - null, - ) - ?.use { cursor -> - buildList { - val idColumnIndex = cursor.getColumnIndex(Contract.SlotTable.Columns.ID) - val capacityColumnIndex = - cursor.getColumnIndex(Contract.SlotTable.Columns.CAPACITY) - if (idColumnIndex == -1 || capacityColumnIndex == -1) { - return@buildList - } - - while (cursor.moveToNext()) { - add( - Slot( - id = cursor.getString(idColumnIndex), - capacity = cursor.getInt(capacityColumnIndex), - ) - ) - } - } - } - ?: emptyList() - } - - private fun queryAffordances(): List { - return context.contentResolver - .query( - Contract.AffordanceTable.URI, - null, - null, - null, - null, - ) - ?.use { cursor -> - buildList { - val idColumnIndex = cursor.getColumnIndex(Contract.AffordanceTable.Columns.ID) - val nameColumnIndex = - cursor.getColumnIndex(Contract.AffordanceTable.Columns.NAME) - val iconColumnIndex = - cursor.getColumnIndex(Contract.AffordanceTable.Columns.ICON) - if (idColumnIndex == -1 || nameColumnIndex == -1 || iconColumnIndex == -1) { - return@buildList - } - - while (cursor.moveToNext()) { - add( - Affordance( - id = cursor.getString(idColumnIndex), - name = cursor.getString(nameColumnIndex), - iconResourceId = cursor.getInt(iconColumnIndex), - ) - ) - } - } - } - ?: emptyList() - } - - data class Slot( - val id: String, - val capacity: Int, - ) - - data class Affordance( - val id: String, - val name: String, - val iconResourceId: Int, - ) - - data class Selection( - val slotId: String, - val affordanceId: String, - val affordanceName: String, - ) - - companion object { - private const val AFFORDANCE_1 = "affordance_1" - private const val AFFORDANCE_2 = "affordance_2" - private const val AFFORDANCE_1_NAME = "affordance_1_name" - private const val AFFORDANCE_2_NAME = "affordance_2_name" - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt index d7e9cf144f88..b21cec970136 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceRemoteUserSelectionManagerTest.kt @@ -22,8 +22,8 @@ import android.os.UserHandle import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.settings.FakeUserTracker +import com.android.systemui.shared.customization.data.content.FakeCustomizationProviderClient import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots -import com.android.systemui.shared.quickaffordance.data.content.FakeKeyguardQuickAffordanceProviderClient import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -54,16 +54,16 @@ class KeyguardQuickAffordanceRemoteUserSelectionManagerTest : SysuiTestCase() { private lateinit var testScope: TestScope private lateinit var testDispatcher: TestDispatcher private lateinit var userTracker: FakeUserTracker - private lateinit var client1: FakeKeyguardQuickAffordanceProviderClient - private lateinit var client2: FakeKeyguardQuickAffordanceProviderClient + private lateinit var client1: FakeCustomizationProviderClient + private lateinit var client2: FakeCustomizationProviderClient @Before fun setUp() { MockitoAnnotations.initMocks(this) whenever(userHandle.identifier).thenReturn(UserHandle.USER_SYSTEM) whenever(userHandle.isSystem).thenReturn(true) - client1 = FakeKeyguardQuickAffordanceProviderClient() - client2 = FakeKeyguardQuickAffordanceProviderClient() + client1 = FakeCustomizationProviderClient() + client2 = FakeCustomizationProviderClient() userTracker = FakeUserTracker() userTracker.set( @@ -122,11 +122,11 @@ class KeyguardQuickAffordanceRemoteUserSelectionManagerTest : SysuiTestCase() { client1.insertSelection( slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, - affordanceId = FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1, + affordanceId = FakeCustomizationProviderClient.AFFORDANCE_1, ) client2.insertSelection( slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, - affordanceId = FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_2, + affordanceId = FakeCustomizationProviderClient.AFFORDANCE_2, ) userTracker.set( @@ -139,7 +139,7 @@ class KeyguardQuickAffordanceRemoteUserSelectionManagerTest : SysuiTestCase() { mapOf( KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to listOf( - FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1, + FakeCustomizationProviderClient.AFFORDANCE_1, ), ) ) @@ -154,7 +154,7 @@ class KeyguardQuickAffordanceRemoteUserSelectionManagerTest : SysuiTestCase() { mapOf( KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to listOf( - FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_2, + FakeCustomizationProviderClient.AFFORDANCE_2, ), ) ) @@ -174,7 +174,7 @@ class KeyguardQuickAffordanceRemoteUserSelectionManagerTest : SysuiTestCase() { client1.insertSelection( slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, - affordanceId = FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1, + affordanceId = FakeCustomizationProviderClient.AFFORDANCE_1, ) userTracker.set( userInfos = userTracker.userProfiles, @@ -197,7 +197,7 @@ class KeyguardQuickAffordanceRemoteUserSelectionManagerTest : SysuiTestCase() { underTest.setSelections( slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, - affordanceIds = listOf(FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1), + affordanceIds = listOf(FakeCustomizationProviderClient.AFFORDANCE_1), ) runCurrent() @@ -206,7 +206,7 @@ class KeyguardQuickAffordanceRemoteUserSelectionManagerTest : SysuiTestCase() { mapOf( KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to listOf( - FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1, + FakeCustomizationProviderClient.AFFORDANCE_1, ), ) ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt index c187a3ff63bd..b071a028865d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt @@ -32,8 +32,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerR import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.UserFileManager +import com.android.systemui.shared.customization.data.content.FakeCustomizationProviderClient import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots -import com.android.systemui.shared.quickaffordance.data.content.FakeKeyguardQuickAffordanceProviderClient import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -63,19 +63,13 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() { private lateinit var config1: FakeKeyguardQuickAffordanceConfig private lateinit var config2: FakeKeyguardQuickAffordanceConfig private lateinit var userTracker: FakeUserTracker - private lateinit var client1: FakeKeyguardQuickAffordanceProviderClient - private lateinit var client2: FakeKeyguardQuickAffordanceProviderClient + private lateinit var client1: FakeCustomizationProviderClient + private lateinit var client2: FakeCustomizationProviderClient @Before fun setUp() { - config1 = - FakeKeyguardQuickAffordanceConfig( - FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_1 - ) - config2 = - FakeKeyguardQuickAffordanceConfig( - FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_2 - ) + config1 = FakeKeyguardQuickAffordanceConfig(FakeCustomizationProviderClient.AFFORDANCE_1) + config2 = FakeKeyguardQuickAffordanceConfig(FakeCustomizationProviderClient.AFFORDANCE_2) val scope = CoroutineScope(IMMEDIATE) userTracker = FakeUserTracker() val localUserSelectionManager = @@ -95,8 +89,8 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() { userTracker = userTracker, broadcastDispatcher = fakeBroadcastDispatcher, ) - client1 = FakeKeyguardQuickAffordanceProviderClient() - client2 = FakeKeyguardQuickAffordanceProviderClient() + client1 = FakeCustomizationProviderClient() + client2 = FakeCustomizationProviderClient() val remoteUserSelectionManager = KeyguardQuickAffordanceRemoteUserSelectionManager( scope = scope, @@ -256,7 +250,7 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() { ) client2.insertSelection( slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, - affordanceId = FakeKeyguardQuickAffordanceProviderClient.AFFORDANCE_2, + affordanceId = FakeCustomizationProviderClient.AFFORDANCE_2, ) val observed = mutableListOf>>() val job = underTest.selections.onEach { observed.add(it) }.launchIn(this) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceProviderClientFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceProviderClientFactory.kt index d85dd2e7bc8a..16a1298d7674 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceProviderClientFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceProviderClientFactory.kt @@ -18,17 +18,17 @@ package com.android.systemui.keyguard.data.quickaffordance import com.android.systemui.settings.UserTracker -import com.android.systemui.shared.quickaffordance.data.content.FakeKeyguardQuickAffordanceProviderClient -import com.android.systemui.shared.quickaffordance.data.content.KeyguardQuickAffordanceProviderClient +import com.android.systemui.shared.customization.data.content.CustomizationProviderClient +import com.android.systemui.shared.customization.data.content.FakeCustomizationProviderClient class FakeKeyguardQuickAffordanceProviderClientFactory( private val userTracker: UserTracker, - private val callback: (Int) -> KeyguardQuickAffordanceProviderClient = { - FakeKeyguardQuickAffordanceProviderClient() + private val callback: (Int) -> CustomizationProviderClient = { + FakeCustomizationProviderClient() }, ) : KeyguardQuickAffordanceProviderClientFactory { - override fun create(): KeyguardQuickAffordanceProviderClient { + override fun create(): CustomizationProviderClient { return callback(userTracker.userId) } } -- cgit v1.2.3-59-g8ed1b