From 04777dbab2221d202b0f4c05bb6d3fcaa9ca625e Mon Sep 17 00:00:00 2001 From: Alejandro Nijamkin Date: Fri, 28 Oct 2022 17:11:04 -0700 Subject: [PATCH] Content provider for quick affordances. This is the content provider that wallpaper picker will be using to query for slots, affordances, and selections, and for setting or unsetting selections. Fix: 254857637 Test: Manually verified the API by issuing adb shell content commands and seeing affordances appear and disappear on the actual lock screen. Change-Id: Ia47a8ee322028042bb3e118ddb82a92a27eae191 Merged-In: Ia47a8ee322028042bb3e118ddb82a92a27eae191 --- packages/SystemUI/AndroidManifest.xml | 10 + .../compose/features/AndroidManifest.xml | 5 + ...KeyguardQuickAffordanceProviderContract.kt | 111 +++++++ .../dagger/ReferenceSysUIComponent.java | 6 + .../KeyguardQuickAffordanceProvider.kt | 297 ++++++++++++++++++ packages/SystemUI/tests/AndroidManifest.xml | 6 + 6 files changed, 435 insertions(+) create mode 100644 packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/data/content/KeyguardQuickAffordanceProviderContract.kt create mode 100644 packages/SystemUI/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProvider.kt diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 4267ba2ff0b77..68ff116be4b05 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -195,6 +195,9 @@ + + @@ -976,5 +979,12 @@ + + diff --git a/packages/SystemUI/compose/features/AndroidManifest.xml b/packages/SystemUI/compose/features/AndroidManifest.xml index eada40e6a40dc..278a89f7dba33 100644 --- a/packages/SystemUI/compose/features/AndroidManifest.xml +++ b/packages/SystemUI/compose/features/AndroidManifest.xml @@ -34,6 +34,11 @@ android:enabled="false" tools:replace="android:authorities" tools:node="remove" /> + "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/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml index ea0b03d1ebfd0..78c28ea23be05 100644 --- a/packages/SystemUI/tests/AndroidManifest.xml +++ b/packages/SystemUI/tests/AndroidManifest.xml @@ -140,6 +140,12 @@ tools:replace="android:authorities" tools:node="remove" /> + +