diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 11237dca78047..47771aa3c774b 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -195,9 +195,6 @@ - - @@ -996,12 +993,5 @@ android:excludeFromRecents="true" android:exported="false"> - - diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderClient.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderClient.kt deleted file mode 100644 index 8612b3a2c587c..0000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderClient.kt +++ /dev/null @@ -1,326 +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.keyguard.data.content - -import android.annotation.SuppressLint -import android.content.ContentValues -import android.content.Context -import android.database.ContentObserver -import android.graphics.drawable.Drawable -import android.net.Uri -import android.os.UserHandle -import androidx.annotation.DrawableRes -import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract -import kotlinx.coroutines.CoroutineDispatcher -import kotlinx.coroutines.Dispatchers -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 - -/** Collection of utility functions for using a content provider implementing the [Contract]. */ -object 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( - context: Context, - slotId: String, - affordanceId: String, - dispatcher: CoroutineDispatcher = Dispatchers.IO, - ) { - withContext(dispatcher) { - context.contentResolver.insert( - Contract.SelectionTable.URI, - ContentValues().apply { - put(Contract.SelectionTable.Columns.SLOT_ID, slotId) - put(Contract.SelectionTable.Columns.AFFORDANCE_ID, affordanceId) - } - ) - } - } - - /** Returns all available slots supported by the device. */ - suspend fun querySlots( - context: Context, - dispatcher: CoroutineDispatcher = Dispatchers.IO, - ): List { - return withContext(dispatcher) { - 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() - } - - /** - * Returns [Flow] for observing the collection of slots. - * - * @see [querySlots] - */ - fun observeSlots( - context: Context, - dispatcher: CoroutineDispatcher = Dispatchers.IO, - ): Flow> { - return observeUri( - context, - Contract.SlotTable.URI, - ) - .map { querySlots(context, dispatcher) } - } - - /** - * Returns all available affordances supported by the device, regardless of current slot - * placement. - */ - suspend fun queryAffordances( - context: Context, - dispatcher: CoroutineDispatcher = Dispatchers.IO, - ): List { - return withContext(dispatcher) { - 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() - } - - /** - * Returns [Flow] for observing the collection of affordances. - * - * @see [queryAffordances] - */ - fun observeAffordances( - context: Context, - dispatcher: CoroutineDispatcher = Dispatchers.IO, - ): Flow> { - return observeUri( - context, - Contract.AffordanceTable.URI, - ) - .map { queryAffordances(context, dispatcher) } - } - - /** Returns the current slot-affordance selections. */ - suspend fun querySelections( - context: Context, - dispatcher: CoroutineDispatcher = Dispatchers.IO, - ): List { - return withContext(dispatcher) { - 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) - if (slotIdColumnIndex == -1 || affordanceIdColumnIndex == -1) { - return@buildList - } - - while (cursor.moveToNext()) { - add( - Selection( - slotId = cursor.getString(slotIdColumnIndex), - affordanceId = cursor.getString(affordanceIdColumnIndex), - ) - ) - } - } - } - } - ?: emptyList() - } - - /** - * Returns [Flow] for observing the collection of selections. - * - * @see [querySelections] - */ - fun observeSelections( - context: Context, - dispatcher: CoroutineDispatcher = Dispatchers.IO, - ): Flow> { - return observeUri( - context, - Contract.SelectionTable.URI, - ) - .map { querySelections(context, dispatcher) } - } - - /** Unselects an affordance with the given ID from the slot with the given ID. */ - suspend fun deleteSelection( - context: Context, - slotId: String, - affordanceId: String, - dispatcher: CoroutineDispatcher = Dispatchers.IO, - ) { - withContext(dispatcher) { - context.contentResolver.delete( - Contract.SelectionTable.URI, - "${Contract.SelectionTable.Columns.SLOT_ID} = ? AND" + - " ${Contract.SelectionTable.Columns.AFFORDANCE_ID} = ?", - arrayOf( - slotId, - affordanceId, - ), - ) - } - } - - /** Unselects all affordances from the slot with the given ID. */ - suspend fun deleteAllSelections( - context: Context, - slotId: String, - dispatcher: CoroutineDispatcher = Dispatchers.IO, - ) { - withContext(dispatcher) { - context.contentResolver.delete( - Contract.SelectionTable.URI, - "${Contract.SelectionTable.Columns.SLOT_ID}", - arrayOf( - slotId, - ), - ) - } - } - - private fun observeUri( - context: Context, - uri: Uri, - ): Flow { - return callbackFlow { - val observer = - object : ContentObserver(null) { - override fun onChange(selfChange: Boolean) { - trySend(Unit) - } - } - - context.contentResolver.registerContentObserver( - uri, - /* notifyForDescendants= */ true, - observer, - UserHandle.USER_CURRENT, - ) - - awaitClose { context.contentResolver.unregisterContentObserver(observer) } - } - .onStart { emit(Unit) } - } - - @SuppressLint("UseCompatLoadingForDrawables") - suspend fun getAffordanceIcon( - context: Context, - @DrawableRes iconResourceId: Int, - dispatcher: CoroutineDispatcher = Dispatchers.IO, - ): Drawable { - return withContext(dispatcher) { - context.packageManager - .getResourcesForApplication(SYSTEM_UI_PACKAGE_NAME) - .getDrawable(iconResourceId) - } - } - - 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, - ) - - private const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui" -} diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt deleted file mode 100644 index c2658a9e61b12..0000000000000 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt +++ /dev/null @@ -1,111 +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.keyguard.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() - - 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" - } - } - - /** - * 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" - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java index d3555eec02435..7ab36e84178e3 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSysUIComponent.java @@ -16,7 +16,6 @@ package com.android.systemui.dagger; -import com.android.systemui.keyguard.KeyguardQuickAffordanceProvider; import com.android.systemui.statusbar.QsFrameTranslateModule; import dagger.Subcomponent; @@ -43,9 +42,4 @@ public interface ReferenceSysUIComponent extends SysUIComponent { interface Builder extends SysUIComponent.Builder { ReferenceSysUIComponent build(); } - - /** - * Member injection into the supplied argument. - */ - void inject(KeyguardQuickAffordanceProvider keyguardQuickAffordanceProvider); } 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 0f4581ce3e616..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt +++ /dev/null @@ -1,297 +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.ProviderInfo -import android.database.Cursor -import android.database.MatrixCursor -import android.net.Uri -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.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract -import javax.inject.Inject -import kotlinx.coroutines.runBlocking - -class KeyguardQuickAffordanceProvider : - ContentProvider(), SystemUIAppComponentFactoryBase.ContextInitializer { - - @Inject lateinit var interactor: KeyguardQuickAffordanceInteractor - - 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, - ) - } - - 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_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 - 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 -> queryAffordances() - MATCH_CODE_ALL_SLOTS -> querySlots() - MATCH_CODE_ALL_SELECTIONS -> querySelections() - 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) - } - - 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 = runBlocking { - 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 fun querySelections(): Cursor { - return MatrixCursor( - arrayOf( - Contract.SelectionTable.Columns.SLOT_ID, - Contract.SelectionTable.Columns.AFFORDANCE_ID, - ) - ) - .apply { - val affordanceIdsBySlotId = runBlocking { interactor.getSelections() } - affordanceIdsBySlotId.entries.forEach { (slotId, affordanceIds) -> - affordanceIds.forEach { affordanceId -> - addRow( - arrayOf( - slotId, - affordanceId, - ) - ) - } - } - } - } - - private fun queryAffordances(): Cursor { - return MatrixCursor( - arrayOf( - Contract.AffordanceTable.Columns.ID, - Contract.AffordanceTable.Columns.NAME, - Contract.AffordanceTable.Columns.ICON, - ) - ) - .apply { - interactor.getAffordancePickerRepresentations().forEach { representation -> - addRow( - arrayOf( - representation.id, - representation.name, - representation.iconResourceId, - ) - ) - } - } - } - - 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 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 = runBlocking { - 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 - } -} 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 4d66a168303c1..0000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt +++ /dev/null @@ -1,302 +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.pm.PackageManager -import android.content.pm.ProviderInfo -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.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags -import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig -import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager -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.plugins.ActivityStarter -import com.android.systemui.settings.UserTracker -import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderClient as Client -import com.android.systemui.shared.keyguard.data.content.KeyguardQuickAffordanceProviderContract as Contract -import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots -import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.util.mockito.mock -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runBlocking -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 -import org.mockito.Mock -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations - -@SmallTest -@RunWith(JUnit4::class) -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 - - private lateinit var underTest: KeyguardQuickAffordanceProvider - - @Before - fun setUp() { - MockitoAnnotations.initMocks(this) - - underTest = KeyguardQuickAffordanceProvider() - val quickAffordanceRepository = - KeyguardQuickAffordanceRepository( - scope = CoroutineScope(IMMEDIATE), - backgroundDispatcher = IMMEDIATE, - selectionManager = KeyguardQuickAffordanceSelectionManager(), - configs = - setOf( - FakeKeyguardQuickAffordanceConfig( - key = AFFORDANCE_1, - pickerIconResourceId = 1, - ), - FakeKeyguardQuickAffordanceConfig( - key = AFFORDANCE_2, - pickerIconResourceId = 2, - ), - ), - ) - 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) - }, - repository = { quickAffordanceRepository }, - ) - - 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`() = - runBlocking(IMMEDIATE) { - val slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START - val affordanceId = AFFORDANCE_2 - - Client.insertSelection( - context = context, - slotId = slotId, - affordanceId = affordanceId, - dispatcher = IMMEDIATE, - ) - - assertThat( - Client.querySelections( - context = context, - dispatcher = IMMEDIATE, - ) - ) - .isEqualTo( - listOf( - Client.Selection( - slotId = slotId, - affordanceId = affordanceId, - ) - ) - ) - } - - @Test - fun `query slots`() = - runBlocking(IMMEDIATE) { - assertThat( - Client.querySlots( - context = context, - dispatcher = IMMEDIATE, - ) - ) - .isEqualTo( - listOf( - Client.Slot( - id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, - capacity = 1, - ), - Client.Slot( - id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, - capacity = 1, - ), - ) - ) - } - - @Test - fun `query affordances`() = - runBlocking(IMMEDIATE) { - assertThat( - Client.queryAffordances( - context = context, - dispatcher = IMMEDIATE, - ) - ) - .isEqualTo( - listOf( - Client.Affordance( - id = AFFORDANCE_1, - name = AFFORDANCE_1, - iconResourceId = 1, - ), - Client.Affordance( - id = AFFORDANCE_2, - name = AFFORDANCE_2, - iconResourceId = 2, - ), - ) - ) - } - - @Test - fun `delete and query selection`() = - runBlocking(IMMEDIATE) { - Client.insertSelection( - context = context, - slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, - affordanceId = AFFORDANCE_1, - dispatcher = IMMEDIATE, - ) - Client.insertSelection( - context = context, - slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, - affordanceId = AFFORDANCE_2, - dispatcher = IMMEDIATE, - ) - - Client.deleteSelection( - context = context, - slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, - affordanceId = AFFORDANCE_2, - dispatcher = IMMEDIATE, - ) - - assertThat( - Client.querySelections( - context = context, - dispatcher = IMMEDIATE, - ) - ) - .isEqualTo( - listOf( - Client.Selection( - slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, - affordanceId = AFFORDANCE_1, - ) - ) - ) - } - - @Test - fun `delete all selections in a slot`() = - runBlocking(IMMEDIATE) { - Client.insertSelection( - context = context, - slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, - affordanceId = AFFORDANCE_1, - dispatcher = IMMEDIATE, - ) - Client.insertSelection( - context = context, - slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, - affordanceId = AFFORDANCE_2, - dispatcher = IMMEDIATE, - ) - - Client.deleteAllSelections( - context = context, - slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, - dispatcher = IMMEDIATE, - ) - - assertThat( - Client.querySelections( - context = context, - dispatcher = IMMEDIATE, - ) - ) - .isEqualTo( - listOf( - Client.Selection( - slotId = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, - affordanceId = AFFORDANCE_1, - ) - ) - ) - } - - companion object { - private val IMMEDIATE = Dispatchers.Main.immediate - private const val AFFORDANCE_1 = "affordance_1" - private const val AFFORDANCE_2 = "affordance_2" - } -}