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
This commit is contained in:
Alejandro Nijamkin
2022-12-24 07:57:12 -08:00
parent 78a04abbbc
commit 102b4d6769
18 changed files with 466 additions and 356 deletions

View File

@@ -193,7 +193,7 @@
<permission android:name="com.android.systemui.permission.FLAGS"
android:protectionLevel="signature" />
<permission android:name="android.permission.ACCESS_KEYGUARD_QUICK_AFFORDANCES"
<permission android:name="android.permission.CUSTOMIZE_SYSTEM_UI"
android:protectionLevel="signature|privileged" />
<!-- Adding Quick Settings tiles -->
@@ -1003,10 +1003,10 @@
</receiver>
<provider
android:authorities="com.android.systemui.keyguard.quickaffordance"
android:name="com.android.systemui.keyguard.KeyguardQuickAffordanceProvider"
android:authorities="com.android.systemui.customization"
android:name="com.android.systemui.keyguard.CustomizationProvider"
android:exported="true"
android:permission="android.permission.ACCESS_KEYGUARD_QUICK_AFFORDANCES"
android:permission="android.permission.CUSTOMIZE_SYSTEM_UI"
/>
</application>
</manifest>

View File

@@ -35,7 +35,7 @@
android:enabled="false"
tools:replace="android:authorities"
tools:node="remove" />
<provider android:name="com.android.systemui.keyguard.KeyguardQuickAffordanceProvider"
<provider android:name="com.android.systemui.keyguard.CustomizationProvider"
android:authorities="com.android.systemui.test.keyguard.quickaffordance.disabled"
android:enabled="false"
tools:replace="android:authorities"

View File

@@ -15,7 +15,7 @@
*
*/
package com.android.systemui.shared.quickaffordance.data.content
package com.android.systemui.shared.customization.data.content
import android.annotation.SuppressLint
import android.content.ContentValues
@@ -25,7 +25,7 @@ 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 com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
@@ -35,7 +35,7 @@ import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.withContext
/** Client for using a content provider implementing the [Contract]. */
interface KeyguardQuickAffordanceProviderClient {
interface CustomizationProviderClient {
/**
* Selects an affordance with the given ID for a slot on the lock screen with the given ID.
@@ -190,10 +190,10 @@ interface KeyguardQuickAffordanceProviderClient {
)
}
class KeyguardQuickAffordanceProviderClientImpl(
class CustomizationProviderClientImpl(
private val context: Context,
private val backgroundDispatcher: CoroutineDispatcher,
) : KeyguardQuickAffordanceProviderClient {
) : CustomizationProviderClient {
override suspend fun insertSelection(
slotId: String,
@@ -201,20 +201,23 @@ class KeyguardQuickAffordanceProviderClientImpl(
) {
withContext(backgroundDispatcher) {
context.contentResolver.insert(
Contract.SelectionTable.URI,
Contract.LockScreenQuickAffordances.SelectionTable.URI,
ContentValues().apply {
put(Contract.SelectionTable.Columns.SLOT_ID, slotId)
put(Contract.SelectionTable.Columns.AFFORDANCE_ID, affordanceId)
put(Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID, slotId)
put(
Contract.LockScreenQuickAffordances.SelectionTable.Columns.AFFORDANCE_ID,
affordanceId
)
}
)
}
}
override suspend fun querySlots(): List<KeyguardQuickAffordanceProviderClient.Slot> {
override suspend fun querySlots(): List<CustomizationProviderClient.Slot> {
return withContext(backgroundDispatcher) {
context.contentResolver
.query(
Contract.SlotTable.URI,
Contract.LockScreenQuickAffordances.SlotTable.URI,
null,
null,
null,
@@ -222,16 +225,21 @@ class KeyguardQuickAffordanceProviderClientImpl(
)
?.use { cursor ->
buildList {
val idColumnIndex = cursor.getColumnIndex(Contract.SlotTable.Columns.ID)
val idColumnIndex =
cursor.getColumnIndex(
Contract.LockScreenQuickAffordances.SlotTable.Columns.ID
)
val capacityColumnIndex =
cursor.getColumnIndex(Contract.SlotTable.Columns.CAPACITY)
cursor.getColumnIndex(
Contract.LockScreenQuickAffordances.SlotTable.Columns.CAPACITY
)
if (idColumnIndex == -1 || capacityColumnIndex == -1) {
return@buildList
}
while (cursor.moveToNext()) {
add(
KeyguardQuickAffordanceProviderClient.Slot(
CustomizationProviderClient.Slot(
id = cursor.getString(idColumnIndex),
capacity = cursor.getInt(capacityColumnIndex),
)
@@ -243,7 +251,7 @@ class KeyguardQuickAffordanceProviderClientImpl(
?: emptyList()
}
override suspend fun queryFlags(): List<KeyguardQuickAffordanceProviderClient.Flag> {
override suspend fun queryFlags(): List<CustomizationProviderClient.Flag> {
return withContext(backgroundDispatcher) {
context.contentResolver
.query(
@@ -265,7 +273,7 @@ class KeyguardQuickAffordanceProviderClientImpl(
while (cursor.moveToNext()) {
add(
KeyguardQuickAffordanceProviderClient.Flag(
CustomizationProviderClient.Flag(
name = cursor.getString(nameColumnIndex),
value = cursor.getInt(valueColumnIndex) == 1,
)
@@ -277,20 +285,19 @@ class KeyguardQuickAffordanceProviderClientImpl(
?: emptyList()
}
override fun observeSlots(): Flow<List<KeyguardQuickAffordanceProviderClient.Slot>> {
return observeUri(Contract.SlotTable.URI).map { querySlots() }
override fun observeSlots(): Flow<List<CustomizationProviderClient.Slot>> {
return observeUri(Contract.LockScreenQuickAffordances.SlotTable.URI).map { querySlots() }
}
override fun observeFlags(): Flow<List<KeyguardQuickAffordanceProviderClient.Flag>> {
override fun observeFlags(): Flow<List<CustomizationProviderClient.Flag>> {
return observeUri(Contract.FlagsTable.URI).map { queryFlags() }
}
override suspend fun queryAffordances():
List<KeyguardQuickAffordanceProviderClient.Affordance> {
override suspend fun queryAffordances(): List<CustomizationProviderClient.Affordance> {
return withContext(backgroundDispatcher) {
context.contentResolver
.query(
Contract.AffordanceTable.URI,
Contract.LockScreenQuickAffordances.AffordanceTable.URI,
null,
null,
null,
@@ -299,24 +306,36 @@ class KeyguardQuickAffordanceProviderClientImpl(
?.use { cursor ->
buildList {
val idColumnIndex =
cursor.getColumnIndex(Contract.AffordanceTable.Columns.ID)
cursor.getColumnIndex(
Contract.LockScreenQuickAffordances.AffordanceTable.Columns.ID
)
val nameColumnIndex =
cursor.getColumnIndex(Contract.AffordanceTable.Columns.NAME)
cursor.getColumnIndex(
Contract.LockScreenQuickAffordances.AffordanceTable.Columns.NAME
)
val iconColumnIndex =
cursor.getColumnIndex(Contract.AffordanceTable.Columns.ICON)
cursor.getColumnIndex(
Contract.LockScreenQuickAffordances.AffordanceTable.Columns.ICON
)
val isEnabledColumnIndex =
cursor.getColumnIndex(Contract.AffordanceTable.Columns.IS_ENABLED)
cursor.getColumnIndex(
Contract.LockScreenQuickAffordances.AffordanceTable.Columns
.IS_ENABLED
)
val enablementInstructionsColumnIndex =
cursor.getColumnIndex(
Contract.AffordanceTable.Columns.ENABLEMENT_INSTRUCTIONS
Contract.LockScreenQuickAffordances.AffordanceTable.Columns
.ENABLEMENT_INSTRUCTIONS
)
val enablementActionTextColumnIndex =
cursor.getColumnIndex(
Contract.AffordanceTable.Columns.ENABLEMENT_ACTION_TEXT
Contract.LockScreenQuickAffordances.AffordanceTable.Columns
.ENABLEMENT_ACTION_TEXT
)
val enablementComponentNameColumnIndex =
cursor.getColumnIndex(
Contract.AffordanceTable.Columns.ENABLEMENT_COMPONENT_NAME
Contract.LockScreenQuickAffordances.AffordanceTable.Columns
.ENABLEMENT_COMPONENT_NAME
)
if (
idColumnIndex == -1 ||
@@ -332,7 +351,7 @@ class KeyguardQuickAffordanceProviderClientImpl(
while (cursor.moveToNext()) {
add(
KeyguardQuickAffordanceProviderClient.Affordance(
CustomizationProviderClient.Affordance(
id = cursor.getString(idColumnIndex),
name = cursor.getString(nameColumnIndex),
iconResourceId = cursor.getInt(iconColumnIndex),
@@ -341,7 +360,7 @@ class KeyguardQuickAffordanceProviderClientImpl(
cursor
.getString(enablementInstructionsColumnIndex)
?.split(
Contract.AffordanceTable
Contract.LockScreenQuickAffordances.AffordanceTable
.ENABLEMENT_INSTRUCTIONS_DELIMITER
),
enablementActionText =
@@ -357,16 +376,17 @@ class KeyguardQuickAffordanceProviderClientImpl(
?: emptyList()
}
override fun observeAffordances():
Flow<List<KeyguardQuickAffordanceProviderClient.Affordance>> {
return observeUri(Contract.AffordanceTable.URI).map { queryAffordances() }
override fun observeAffordances(): Flow<List<CustomizationProviderClient.Affordance>> {
return observeUri(Contract.LockScreenQuickAffordances.AffordanceTable.URI).map {
queryAffordances()
}
}
override suspend fun querySelections(): List<KeyguardQuickAffordanceProviderClient.Selection> {
override suspend fun querySelections(): List<CustomizationProviderClient.Selection> {
return withContext(backgroundDispatcher) {
context.contentResolver
.query(
Contract.SelectionTable.URI,
Contract.LockScreenQuickAffordances.SelectionTable.URI,
null,
null,
null,
@@ -375,11 +395,19 @@ class KeyguardQuickAffordanceProviderClientImpl(
?.use { cursor ->
buildList {
val slotIdColumnIndex =
cursor.getColumnIndex(Contract.SelectionTable.Columns.SLOT_ID)
cursor.getColumnIndex(
Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID
)
val affordanceIdColumnIndex =
cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_ID)
cursor.getColumnIndex(
Contract.LockScreenQuickAffordances.SelectionTable.Columns
.AFFORDANCE_ID
)
val affordanceNameColumnIndex =
cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_NAME)
cursor.getColumnIndex(
Contract.LockScreenQuickAffordances.SelectionTable.Columns
.AFFORDANCE_NAME
)
if (
slotIdColumnIndex == -1 ||
affordanceIdColumnIndex == -1 ||
@@ -390,7 +418,7 @@ class KeyguardQuickAffordanceProviderClientImpl(
while (cursor.moveToNext()) {
add(
KeyguardQuickAffordanceProviderClient.Selection(
CustomizationProviderClient.Selection(
slotId = cursor.getString(slotIdColumnIndex),
affordanceId = cursor.getString(affordanceIdColumnIndex),
affordanceName = cursor.getString(affordanceNameColumnIndex),
@@ -403,8 +431,10 @@ class KeyguardQuickAffordanceProviderClientImpl(
?: emptyList()
}
override fun observeSelections(): Flow<List<KeyguardQuickAffordanceProviderClient.Selection>> {
return observeUri(Contract.SelectionTable.URI).map { querySelections() }
override fun observeSelections(): Flow<List<CustomizationProviderClient.Selection>> {
return observeUri(Contract.LockScreenQuickAffordances.SelectionTable.URI).map {
querySelections()
}
}
override suspend fun deleteSelection(
@@ -413,9 +443,10 @@ class KeyguardQuickAffordanceProviderClientImpl(
) {
withContext(backgroundDispatcher) {
context.contentResolver.delete(
Contract.SelectionTable.URI,
"${Contract.SelectionTable.Columns.SLOT_ID} = ? AND" +
" ${Contract.SelectionTable.Columns.AFFORDANCE_ID} = ?",
Contract.LockScreenQuickAffordances.SelectionTable.URI,
"${Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID} = ? AND" +
" ${Contract.LockScreenQuickAffordances.SelectionTable.Columns.AFFORDANCE_ID}" +
" = ?",
arrayOf(
slotId,
affordanceId,
@@ -429,8 +460,8 @@ class KeyguardQuickAffordanceProviderClientImpl(
) {
withContext(backgroundDispatcher) {
context.contentResolver.delete(
Contract.SelectionTable.URI,
Contract.SelectionTable.Columns.SLOT_ID,
Contract.LockScreenQuickAffordances.SelectionTable.URI,
Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID,
arrayOf(
slotId,
),

View File

@@ -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"
}
}
}

View File

@@ -15,7 +15,7 @@
*
*/
package com.android.systemui.shared.quickaffordance.data.content
package com.android.systemui.shared.customization.data.content
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
@@ -25,46 +25,46 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
class FakeKeyguardQuickAffordanceProviderClient(
slots: List<KeyguardQuickAffordanceProviderClient.Slot> =
class FakeCustomizationProviderClient(
slots: List<CustomizationProviderClient.Slot> =
listOf(
KeyguardQuickAffordanceProviderClient.Slot(
CustomizationProviderClient.Slot(
id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
capacity = 1,
),
KeyguardQuickAffordanceProviderClient.Slot(
CustomizationProviderClient.Slot(
id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
capacity = 1,
),
),
affordances: List<KeyguardQuickAffordanceProviderClient.Affordance> =
affordances: List<CustomizationProviderClient.Affordance> =
listOf(
KeyguardQuickAffordanceProviderClient.Affordance(
CustomizationProviderClient.Affordance(
id = AFFORDANCE_1,
name = AFFORDANCE_1,
iconResourceId = 1,
),
KeyguardQuickAffordanceProviderClient.Affordance(
CustomizationProviderClient.Affordance(
id = AFFORDANCE_2,
name = AFFORDANCE_2,
iconResourceId = 2,
),
KeyguardQuickAffordanceProviderClient.Affordance(
CustomizationProviderClient.Affordance(
id = AFFORDANCE_3,
name = AFFORDANCE_3,
iconResourceId = 3,
),
),
flags: List<KeyguardQuickAffordanceProviderClient.Flag> =
flags: List<CustomizationProviderClient.Flag> =
listOf(
KeyguardQuickAffordanceProviderClient.Flag(
CustomizationProviderClient.Flag(
name =
KeyguardQuickAffordanceProviderContract.FlagsTable
CustomizationProviderContract.FlagsTable
.FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED,
value = true,
)
),
) : KeyguardQuickAffordanceProviderClient {
) : CustomizationProviderClient {
private val slots = MutableStateFlow(slots)
private val affordances = MutableStateFlow(affordances)
@@ -85,37 +85,35 @@ class FakeKeyguardQuickAffordanceProviderClient(
selections.value = selections.value.toMutableMap().apply { this[slotId] = affordances }
}
override suspend fun querySlots(): List<KeyguardQuickAffordanceProviderClient.Slot> {
override suspend fun querySlots(): List<CustomizationProviderClient.Slot> {
return slots.value
}
override suspend fun queryFlags(): List<KeyguardQuickAffordanceProviderClient.Flag> {
override suspend fun queryFlags(): List<CustomizationProviderClient.Flag> {
return flags.value
}
override fun observeSlots(): Flow<List<KeyguardQuickAffordanceProviderClient.Slot>> {
override fun observeSlots(): Flow<List<CustomizationProviderClient.Slot>> {
return slots.asStateFlow()
}
override fun observeFlags(): Flow<List<KeyguardQuickAffordanceProviderClient.Flag>> {
override fun observeFlags(): Flow<List<CustomizationProviderClient.Flag>> {
return flags.asStateFlow()
}
override suspend fun queryAffordances():
List<KeyguardQuickAffordanceProviderClient.Affordance> {
override suspend fun queryAffordances(): List<CustomizationProviderClient.Affordance> {
return affordances.value
}
override fun observeAffordances():
Flow<List<KeyguardQuickAffordanceProviderClient.Affordance>> {
override fun observeAffordances(): Flow<List<CustomizationProviderClient.Affordance>> {
return affordances.asStateFlow()
}
override suspend fun querySelections(): List<KeyguardQuickAffordanceProviderClient.Selection> {
override suspend fun querySelections(): List<CustomizationProviderClient.Selection> {
return toSelectionList(selections.value, affordances.value)
}
override fun observeSelections(): Flow<List<KeyguardQuickAffordanceProviderClient.Selection>> {
override fun observeSelections(): Flow<List<CustomizationProviderClient.Selection>> {
return combine(selections, affordances) { selections, affordances ->
toSelectionList(selections, affordances)
}
@@ -148,7 +146,7 @@ class FakeKeyguardQuickAffordanceProviderClient(
flags.value =
flags.value.toMutableList().apply {
removeIf { it.name == name }
add(KeyguardQuickAffordanceProviderClient.Flag(name = name, value = value))
add(CustomizationProviderClient.Flag(name = name, value = value))
}
}
@@ -157,29 +155,26 @@ class FakeKeyguardQuickAffordanceProviderClient(
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)
)
set(index, CustomizationProviderClient.Slot(id = slotId, capacity = capacity))
}
}
fun addAffordance(affordance: KeyguardQuickAffordanceProviderClient.Affordance): Int {
fun addAffordance(affordance: CustomizationProviderClient.Affordance): Int {
affordances.value = affordances.value + listOf(affordance)
return affordances.value.size - 1
}
private fun toSelectionList(
selections: Map<String, List<String>>,
affordances: List<KeyguardQuickAffordanceProviderClient.Affordance>,
): List<KeyguardQuickAffordanceProviderClient.Selection> {
affordances: List<CustomizationProviderClient.Affordance>,
): List<CustomizationProviderClient.Selection> {
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(
CustomizationProviderClient.Selection(
slotId = slotId,
affordanceId = affordanceId,
affordanceName = affordanceName,

View File

@@ -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"
}
}
}

View File

@@ -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.

View File

@@ -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);
}

View File

@@ -33,11 +33,11 @@ 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 com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
import javax.inject.Inject
import kotlinx.coroutines.runBlocking
class KeyguardQuickAffordanceProvider :
class CustomizationProvider :
ContentProvider(), SystemUIAppComponentFactoryBase.ContextInitializer {
@Inject lateinit var interactor: KeyguardQuickAffordanceInteractor
@@ -49,17 +49,23 @@ class KeyguardQuickAffordanceProvider :
UriMatcher(UriMatcher.NO_MATCH).apply {
addURI(
Contract.AUTHORITY,
Contract.SlotTable.TABLE_NAME,
Contract.LockScreenQuickAffordances.qualifiedTablePath(
Contract.LockScreenQuickAffordances.SlotTable.TABLE_NAME,
),
MATCH_CODE_ALL_SLOTS,
)
addURI(
Contract.AUTHORITY,
Contract.AffordanceTable.TABLE_NAME,
Contract.LockScreenQuickAffordances.qualifiedTablePath(
Contract.LockScreenQuickAffordances.AffordanceTable.TABLE_NAME,
),
MATCH_CODE_ALL_AFFORDANCES,
)
addURI(
Contract.AUTHORITY,
Contract.SelectionTable.TABLE_NAME,
Contract.LockScreenQuickAffordances.qualifiedTablePath(
Contract.LockScreenQuickAffordances.SelectionTable.TABLE_NAME,
),
MATCH_CODE_ALL_SELECTIONS,
)
addURI(
@@ -94,9 +100,18 @@ class KeyguardQuickAffordanceProvider :
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_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
}
@@ -174,22 +189,34 @@ class KeyguardQuickAffordanceProvider :
throw IllegalArgumentException("Cannot insert selection, no values passed in!")
}
if (!values.containsKey(Contract.SelectionTable.Columns.SLOT_ID)) {
if (
!values.containsKey(Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID)
) {
throw IllegalArgumentException(
"Cannot insert selection, " +
"\"${Contract.SelectionTable.Columns.SLOT_ID}\" not specified!"
"\"${Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID}\"" +
" not specified!"
)
}
if (!values.containsKey(Contract.SelectionTable.Columns.AFFORDANCE_ID)) {
if (
!values.containsKey(
Contract.LockScreenQuickAffordances.SelectionTable.Columns.AFFORDANCE_ID
)
) {
throw IllegalArgumentException(
"Cannot insert selection, " +
"\"${Contract.SelectionTable.Columns.AFFORDANCE_ID}\" not specified!"
"\"${Contract.LockScreenQuickAffordances
.SelectionTable.Columns.AFFORDANCE_ID}\" not specified!"
)
}
val slotId = values.getAsString(Contract.SelectionTable.Columns.SLOT_ID)
val affordanceId = values.getAsString(Contract.SelectionTable.Columns.AFFORDANCE_ID)
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!")
@@ -207,8 +234,10 @@ class KeyguardQuickAffordanceProvider :
return if (success) {
Log.d(TAG, "Successfully selected $affordanceId for slot $slotId")
context?.contentResolver?.notifyChange(Contract.SelectionTable.URI, null)
Contract.SelectionTable.URI
context
?.contentResolver
?.notifyChange(Contract.LockScreenQuickAffordances.SelectionTable.URI, null)
Contract.LockScreenQuickAffordances.SelectionTable.URI
} else {
Log.d(TAG, "Failed to select $affordanceId for slot $slotId")
null
@@ -218,9 +247,9 @@ class KeyguardQuickAffordanceProvider :
private suspend fun querySelections(): Cursor {
return MatrixCursor(
arrayOf(
Contract.SelectionTable.Columns.SLOT_ID,
Contract.SelectionTable.Columns.AFFORDANCE_ID,
Contract.SelectionTable.Columns.AFFORDANCE_NAME,
Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID,
Contract.LockScreenQuickAffordances.SelectionTable.Columns.AFFORDANCE_ID,
Contract.LockScreenQuickAffordances.SelectionTable.Columns.AFFORDANCE_NAME,
)
)
.apply {
@@ -243,13 +272,16 @@ class KeyguardQuickAffordanceProvider :
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,
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 {
@@ -261,7 +293,8 @@ class KeyguardQuickAffordanceProvider :
representation.iconResourceId,
if (representation.isEnabled) 1 else 0,
representation.instructions?.joinToString(
Contract.AffordanceTable.ENABLEMENT_INSTRUCTIONS_DELIMITER
Contract.LockScreenQuickAffordances.AffordanceTable
.ENABLEMENT_INSTRUCTIONS_DELIMITER
),
representation.actionText,
representation.actionComponentName,
@@ -274,8 +307,8 @@ class KeyguardQuickAffordanceProvider :
private fun querySlots(): Cursor {
return MatrixCursor(
arrayOf(
Contract.SlotTable.Columns.ID,
Contract.SlotTable.Columns.CAPACITY,
Contract.LockScreenQuickAffordances.SlotTable.Columns.ID,
Contract.LockScreenQuickAffordances.SlotTable.Columns.CAPACITY,
)
)
.apply {

View File

@@ -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
}
}

View File

@@ -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,
)

View File

@@ -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<KeyguardQuickAffordanceProviderClient?> =
private val clientOrNull: StateFlow<CustomizationProviderClient?> =
userId
.distinctUntilChanged()
.map { selectedUserId ->

View File

@@ -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

View File

@@ -146,7 +146,7 @@
tools:replace="android:authorities"
tools:node="remove" />
<provider android:name="com.android.systemui.keyguard.KeyguardQuickAffordanceProvider"
<provider android:name="com.android.systemui.keyguard.CustomizationProvider"
android:authorities="com.android.systemui.test.keyguard.quickaffordance.disabled"
android:enabled="false"
tools:replace="android:authorities"

View File

@@ -49,8 +49,8 @@ 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.customization.data.content.CustomizationProviderContract as Contract
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
@@ -75,7 +75,7 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
class CustomizationProviderTest : SysuiTestCase() {
@Mock private lateinit var lockPatternUtils: LockPatternUtils
@Mock private lateinit var keyguardStateController: KeyguardStateController
@@ -87,7 +87,7 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
@Mock private lateinit var previewSurfacePackage: SurfaceControlViewHost.SurfacePackage
@Mock private lateinit var launchAnimator: DialogLaunchAnimator
private lateinit var underTest: KeyguardQuickAffordanceProvider
private lateinit var underTest: CustomizationProvider
private lateinit var testScope: TestScope
@@ -98,7 +98,7 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
whenever(previewRendererFactory.create(any())).thenReturn(previewRenderer)
whenever(backgroundHandler.looper).thenReturn(TestableLooper.get(this).looper)
underTest = KeyguardQuickAffordanceProvider()
underTest = CustomizationProvider()
val testDispatcher = StandardTestDispatcher()
testScope = TestScope(testDispatcher)
val localUserSelectionManager =
@@ -205,19 +205,34 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
@Test
fun getType() {
assertThat(underTest.getType(Contract.AffordanceTable.URI))
assertThat(underTest.getType(Contract.LockScreenQuickAffordances.AffordanceTable.URI))
.isEqualTo(
"vnd.android.cursor.dir/vnd." +
"${Contract.AUTHORITY}.${Contract.AffordanceTable.TABLE_NAME}"
"${Contract.AUTHORITY}." +
Contract.LockScreenQuickAffordances.qualifiedTablePath(
Contract.LockScreenQuickAffordances.AffordanceTable.TABLE_NAME
)
)
assertThat(underTest.getType(Contract.SlotTable.URI))
assertThat(underTest.getType(Contract.LockScreenQuickAffordances.SlotTable.URI))
.isEqualTo(
"vnd.android.cursor.dir/vnd.${Contract.AUTHORITY}.${Contract.SlotTable.TABLE_NAME}"
"vnd.android.cursor.dir/vnd.${Contract.AUTHORITY}." +
Contract.LockScreenQuickAffordances.qualifiedTablePath(
Contract.LockScreenQuickAffordances.SlotTable.TABLE_NAME
)
)
assertThat(underTest.getType(Contract.SelectionTable.URI))
assertThat(underTest.getType(Contract.LockScreenQuickAffordances.SelectionTable.URI))
.isEqualTo(
"vnd.android.cursor.dir/vnd." +
"${Contract.AUTHORITY}.${Contract.SelectionTable.TABLE_NAME}"
"${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
)
}
@@ -296,9 +311,10 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
)
context.contentResolver.delete(
Contract.SelectionTable.URI,
"${Contract.SelectionTable.Columns.SLOT_ID} = ? AND" +
" ${Contract.SelectionTable.Columns.AFFORDANCE_ID} = ?",
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,
@@ -330,8 +346,8 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
)
context.contentResolver.delete(
Contract.SelectionTable.URI,
Contract.SelectionTable.Columns.SLOT_ID,
Contract.LockScreenQuickAffordances.SelectionTable.URI,
Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID,
arrayOf(
KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
),
@@ -371,10 +387,13 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
affordanceId: String,
) {
context.contentResolver.insert(
Contract.SelectionTable.URI,
Contract.LockScreenQuickAffordances.SelectionTable.URI,
ContentValues().apply {
put(Contract.SelectionTable.Columns.SLOT_ID, slotId)
put(Contract.SelectionTable.Columns.AFFORDANCE_ID, affordanceId)
put(Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID, slotId)
put(
Contract.LockScreenQuickAffordances.SelectionTable.Columns.AFFORDANCE_ID,
affordanceId
)
}
)
}
@@ -382,7 +401,7 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
private fun querySelections(): List<Selection> {
return context.contentResolver
.query(
Contract.SelectionTable.URI,
Contract.LockScreenQuickAffordances.SelectionTable.URI,
null,
null,
null,
@@ -391,11 +410,18 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
?.use { cursor ->
buildList {
val slotIdColumnIndex =
cursor.getColumnIndex(Contract.SelectionTable.Columns.SLOT_ID)
cursor.getColumnIndex(
Contract.LockScreenQuickAffordances.SelectionTable.Columns.SLOT_ID
)
val affordanceIdColumnIndex =
cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_ID)
cursor.getColumnIndex(
Contract.LockScreenQuickAffordances.SelectionTable.Columns.AFFORDANCE_ID
)
val affordanceNameColumnIndex =
cursor.getColumnIndex(Contract.SelectionTable.Columns.AFFORDANCE_NAME)
cursor.getColumnIndex(
Contract.LockScreenQuickAffordances.SelectionTable.Columns
.AFFORDANCE_NAME
)
if (
slotIdColumnIndex == -1 ||
affordanceIdColumnIndex == -1 ||
@@ -421,7 +447,7 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
private fun querySlots(): List<Slot> {
return context.contentResolver
.query(
Contract.SlotTable.URI,
Contract.LockScreenQuickAffordances.SlotTable.URI,
null,
null,
null,
@@ -429,9 +455,14 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
)
?.use { cursor ->
buildList {
val idColumnIndex = cursor.getColumnIndex(Contract.SlotTable.Columns.ID)
val idColumnIndex =
cursor.getColumnIndex(
Contract.LockScreenQuickAffordances.SlotTable.Columns.ID
)
val capacityColumnIndex =
cursor.getColumnIndex(Contract.SlotTable.Columns.CAPACITY)
cursor.getColumnIndex(
Contract.LockScreenQuickAffordances.SlotTable.Columns.CAPACITY
)
if (idColumnIndex == -1 || capacityColumnIndex == -1) {
return@buildList
}
@@ -452,7 +483,7 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
private fun queryAffordances(): List<Affordance> {
return context.contentResolver
.query(
Contract.AffordanceTable.URI,
Contract.LockScreenQuickAffordances.AffordanceTable.URI,
null,
null,
null,
@@ -460,11 +491,18 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
)
?.use { cursor ->
buildList {
val idColumnIndex = cursor.getColumnIndex(Contract.AffordanceTable.Columns.ID)
val idColumnIndex =
cursor.getColumnIndex(
Contract.LockScreenQuickAffordances.AffordanceTable.Columns.ID
)
val nameColumnIndex =
cursor.getColumnIndex(Contract.AffordanceTable.Columns.NAME)
cursor.getColumnIndex(
Contract.LockScreenQuickAffordances.AffordanceTable.Columns.NAME
)
val iconColumnIndex =
cursor.getColumnIndex(Contract.AffordanceTable.Columns.ICON)
cursor.getColumnIndex(
Contract.LockScreenQuickAffordances.AffordanceTable.Columns.ICON
)
if (idColumnIndex == -1 || nameColumnIndex == -1 || iconColumnIndex == -1) {
return@buildList
}

View File

@@ -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,
),
)
)

View File

@@ -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<Map<String, List<KeyguardQuickAffordanceConfig>>>()
val job = underTest.selections.onEach { observed.add(it) }.launchIn(this)

View File

@@ -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)
}
}