From 1f7c8174f002771bbc093cf672c6ce60d7d46230 Mon Sep 17 00:00:00 2001 From: Matt Pietal Date: Tue, 26 May 2020 14:29:06 -0400 Subject: [PATCH 1/2] Controls UI - Support seeding for up to 6 structures Limit to 36 controls: 6 structures with up to 6 controls per structure Fixes: 153606605 Test: atest ControlsControllerImplTest Change-Id: Ieddceda4a0902e4b42208680c7a77ee03514a16c --- .../controls/ControlsProviderService.java | 9 +++++--- .../ControlsBindingControllerImpl.kt | 4 +++- .../controller/ControlsControllerImpl.kt | 9 +++++--- .../controller/ControlsControllerImplTest.kt | 22 ++++++++++++++----- 4 files changed, 31 insertions(+), 13 deletions(-) diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java index 4262c40221318..4e5aa0018b618 100644 --- a/core/java/android/service/controls/ControlsProviderService.java +++ b/core/java/android/service/controls/ControlsProviderService.java @@ -98,9 +98,12 @@ public abstract class ControlsProviderService extends Service { * * The service may be asked to provide a small number of recommended controls, in * order to suggest some controls to the user for favoriting. The controls shall be built using - * the stateless builder {@link Control.StatelessBuilder}. The number of controls requested - * through {@link Subscription#request} will be limited. Call {@link Subscriber#onComplete} - * when done, or {@link Subscriber#onError} for error scenarios. + * the stateless builder {@link Control.StatelessBuilder}. The total number of controls + * requested through {@link Subscription#request} will be restricted to a maximum. Within this + * larger limit, only 6 controls per structure will be loaded. Therefore, it is advisable to + * seed multiple structures if they exist. Any control sent over this limit will be discarded. + * Call {@link Subscriber#onComplete} when done, or {@link Subscriber#onError} for error + * scenarios. */ @Nullable public Publisher createPublisherForSuggested() { diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt index e84f439c1fe2a..58807f0f70250 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt @@ -46,7 +46,9 @@ open class ControlsBindingControllerImpl @Inject constructor( companion object { private const val TAG = "ControlsBindingControllerImpl" private const val MAX_CONTROLS_REQUEST = 100000L - private const val SUGGESTED_CONTROLS_REQUEST = 6L + private const val SUGGESTED_STRUCTURES = 6L + private const val SUGGESTED_CONTROLS_REQUEST = + ControlsControllerImpl.SUGGESTED_CONTROLS_PER_STRUCTURE * SUGGESTED_STRUCTURES } private var currentUser = UserHandle.of(ActivityManager.getCurrentUser()) diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt index f8f913e8573d3..dc727dfb36ea1 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -72,6 +72,7 @@ class ControlsControllerImpl @Inject constructor ( private const val USER_CHANGE_RETRY_DELAY = 500L // ms private const val DEFAULT_ENABLED = 1 private const val PERMISSION_SELF = "com.android.systemui.permission.SELF" + const val SUGGESTED_CONTROLS_PER_STRUCTURE = 6 private fun isAvailable(userId: Int, cr: ContentResolver) = Settings.Secure.getIntForUser( cr, CONTROLS_AVAILABLE, DEFAULT_ENABLED, userId) != 0 @@ -396,9 +397,11 @@ class ControlsControllerImpl @Inject constructor ( val structure = it.structure ?: "" val list = structureToControls.get(structure) ?: mutableListOf() - list.add( - ControlInfo(it.controlId, it.title, it.subtitle, it.deviceType)) - structureToControls.put(structure, list) + if (list.size < SUGGESTED_CONTROLS_PER_STRUCTURE) { + list.add( + ControlInfo(it.controlId, it.title, it.subtitle, it.deviceType)) + structureToControls.put(structure, list) + } } structureToControls.forEach { diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt index b3888875e82e4..05e676db99133 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt @@ -797,11 +797,14 @@ class ControlsControllerImplTest : SysuiTestCase() { } @Test - fun testSeedFavoritesForComponent() { + fun testSeedFavoritesForComponentWithLimit() { var succeeded = false - val control = statelessBuilderFromInfo(TEST_CONTROL_INFO, TEST_STRUCTURE_INFO.structure) - .build() + val controls = mutableListOf() + for (i in 1..10) { + controls.add(statelessBuilderFromInfo(ControlInfo("id$i", TEST_CONTROL_TITLE, + TEST_CONTROL_SUBTITLE, TEST_DEVICE_TYPE), "testStructure").build()) + } controller.seedFavoritesForComponent(TEST_COMPONENT, Consumer { accepted -> succeeded = accepted }) @@ -809,12 +812,19 @@ class ControlsControllerImplTest : SysuiTestCase() { verify(bindingController).bindAndLoadSuggested(eq(TEST_COMPONENT), capture(controlLoadCallbackCaptor)) - controlLoadCallbackCaptor.value.accept(listOf(control)) + controlLoadCallbackCaptor.value.accept(controls) delayableExecutor.runAllReady() - assertEquals(listOf(TEST_STRUCTURE_INFO), - controller.getFavoritesForComponent(TEST_COMPONENT)) + val structureInfo = controller.getFavoritesForComponent(TEST_COMPONENT)[0] + assertEquals(structureInfo.controls.size, + ControlsControllerImpl.SUGGESTED_CONTROLS_PER_STRUCTURE) + + var i = 1 + structureInfo.controls.forEach { + assertEquals(it.controlId, "id$i") + i++ + } assertTrue(succeeded) } From 5459e77da593f206d455b3d3503f73599bc1ff8c Mon Sep 17 00:00:00 2001 From: Matt Pietal Date: Thu, 28 May 2020 17:18:26 -0400 Subject: [PATCH 2/2] Controls UI - Allow seeding for multiple apps Allow up to 2 applications to seed controls into the space. Save the seeding state of each component separately, to be able to reattempt to seed at a later time on failure. Fixes: 155083005 Test: atest ControlsControllerImplTest Change-Id: I28bed9bb0b221a3e5c9b293ed9d3f85e86404a38 --- packages/SystemUI/res/values/config.xml | 6 +- .../controls/controller/ControlsController.kt | 12 ++-- .../controller/ControlsControllerImpl.kt | 40 ++++++++--- .../controls/ui/ControlsUiControllerImpl.kt | 2 +- .../globalactions/GlobalActionsDialog.java | 72 +++++++++++-------- .../controller/ControlsControllerImplTest.kt | 65 +++++++++++------ 6 files changed, 126 insertions(+), 71 deletions(-) diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 14075743ce34f..00537ff0466dc 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -540,10 +540,10 @@ false - - + 2 diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt index 8196a256731ad..07319207d9aba 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt @@ -114,12 +114,12 @@ interface ControlsController : UserAwareController { /** * Send a request to seed favorites into the persisted XML file * - * @param componentName the component to seed controls from - * @param callback true if the favorites were persisted + * @param componentNames the list of components to seed controls from + * @param callback one [SeedResponse] per componentName */ - fun seedFavoritesForComponent( - componentName: ComponentName, - callback: Consumer + fun seedFavoritesForComponents( + componentNames: List, + callback: Consumer ) /** @@ -235,3 +235,5 @@ fun createLoadDataObject( override val errorOnLoad = error } } + +data class SeedResponse(val packageName: String, val accepted: Boolean) diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt index dc727dfb36ea1..fd9fda3662a3a 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -362,29 +362,47 @@ class ControlsControllerImpl @Inject constructor ( return true } - override fun seedFavoritesForComponent( - componentName: ComponentName, - callback: Consumer + override fun seedFavoritesForComponents( + componentNames: List, + callback: Consumer ) { - if (seedingInProgress) return + if (seedingInProgress || componentNames.isEmpty()) return - Log.i(TAG, "Beginning request to seed favorites for: $componentName") if (!confirmAvailability()) { if (userChanging) { // Try again later, userChanging should not last forever. If so, we have bigger // problems. This will return a runnable that allows to cancel the delayed version, // it will not be able to cancel the load if executor.executeDelayed( - { seedFavoritesForComponent(componentName, callback) }, + { seedFavoritesForComponents(componentNames, callback) }, USER_CHANGE_RETRY_DELAY, TimeUnit.MILLISECONDS ) } else { - callback.accept(false) + componentNames.forEach { + callback.accept(SeedResponse(it.packageName, false)) + } } return } seedingInProgress = true + startSeeding(componentNames, callback, false) + } + + private fun startSeeding( + remainingComponentNames: List, + callback: Consumer, + didAnyFail: Boolean + ) { + if (remainingComponentNames.isEmpty()) { + endSeedingCall(!didAnyFail) + return + } + + val componentName = remainingComponentNames[0] + Log.d(TAG, "Beginning request to seed favorites for: $componentName") + + val remaining = remainingComponentNames.drop(1) bindingController.bindAndLoadSuggested( componentName, object : ControlsBindingController.LoadCallback { @@ -410,16 +428,16 @@ class ControlsControllerImpl @Inject constructor ( } persistenceWrapper.storeFavorites(Favorites.getAllStructures()) - callback.accept(true) - endSeedingCall(true) + callback.accept(SeedResponse(componentName.packageName, true)) + startSeeding(remaining, callback, didAnyFail) } } override fun error(message: String) { Log.e(TAG, "Unable to seed favorites: $message") executor.execute { - callback.accept(false) - endSeedingCall(false) + callback.accept(SeedResponse(componentName.packageName, false)) + startSeeding(remaining, callback, true) } } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 606e94760946c..35ebac5b1ed5b 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -328,7 +328,7 @@ class ControlsUiControllerImpl @Inject constructor ( val userContext = context.createContextAsUser(userHandle, 0) val prefs = userContext.getSharedPreferences( "controls_prefs", Context.MODE_PRIVATE) - prefs.edit().putBoolean("ControlsSeedingCompleted", false).apply() + prefs.edit().remove("SeedingCompleted").apply() controlsController.get().resetFavorites() dialog.dismiss() context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 0f3a94b743426..bee5e69078cbf 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -139,8 +139,11 @@ import com.android.systemui.util.RingerModeTracker; import com.android.systemui.util.leak.RotationUtils; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.concurrent.Executor; import javax.inject.Inject; @@ -180,8 +183,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, static final String GLOBAL_ACTION_KEY_EMERGENCY = "emergency"; static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot"; - private static final String PREFS_CONTROLS_SEEDING_COMPLETED = "ControlsSeedingCompleted"; + private static final String PREFS_CONTROLS_SEEDING_COMPLETED = "SeedingCompleted"; private static final String PREFS_CONTROLS_FILE = "controls_prefs"; + private static final int SEEDING_MAX = 2; private final Context mContext; private final GlobalActionsManager mWindowManagerFuncs; @@ -409,47 +413,55 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, }); } + /** + * See if any available control service providers match one of the preferred components. If + * they do, and there are no current favorites for that component, query the preferred + * component for a limited number of suggested controls. + */ private void seedFavorites() { - if (!mControlsControllerOptional.isPresent()) return; - if (mControlsServiceInfos.isEmpty() - || mControlsControllerOptional.get().getFavorites().size() > 0) { + if (!mControlsControllerOptional.isPresent() + || mControlsServiceInfos.isEmpty()) { return; } - // Need to be user-specific with the context to make sure we read the correct prefs + String[] preferredControlsPackages = mContext.getResources() + .getStringArray(com.android.systemui.R.array.config_controlsPreferredPackages); + SharedPreferences prefs = mCurrentUserContextTracker.getCurrentUserContext() .getSharedPreferences(PREFS_CONTROLS_FILE, Context.MODE_PRIVATE); - if (prefs.getBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, false)) { - return; - } + Set seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, + Collections.emptySet()); - /* - * See if any service providers match the preferred component. If they do, - * and there are no current favorites, and we haven't successfully loaded favorites to - * date, query the preferred component for a limited number of suggested controls. - */ - String preferredControlsPackage = mContext.getResources() - .getString(com.android.systemui.R.string.config_controlsPreferredPackage); - - ComponentName preferredComponent = null; + List componentsToSeed = new ArrayList<>(); for (ControlsServiceInfo info : mControlsServiceInfos) { - if (info.componentName.getPackageName().equals(preferredControlsPackage)) { - preferredComponent = info.componentName; - break; + String pkg = info.componentName.getPackageName(); + if (seededPackages.contains(pkg) + || mControlsControllerOptional.get().countFavoritesForComponent( + info.componentName) > 0) { + continue; + } + + for (int i = 0; i < Math.min(SEEDING_MAX, preferredControlsPackages.length); i++) { + if (pkg.equals(preferredControlsPackages[i])) { + componentsToSeed.add(info.componentName); + break; + } } } - if (preferredComponent == null) { - Log.i(TAG, "Controls seeding: No preferred component has been set, will not seed"); - prefs.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, true).apply(); - return; - } + if (componentsToSeed.isEmpty()) return; - mControlsControllerOptional.get().seedFavoritesForComponent( - preferredComponent, - (accepted) -> { - Log.i(TAG, "Controls seeded: " + accepted); - prefs.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, accepted).apply(); + mControlsControllerOptional.get().seedFavoritesForComponents( + componentsToSeed, + (response) -> { + Log.d(TAG, "Controls seeded: " + response); + Set completedPkgs = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, + new HashSet()); + if (response.getAccepted()) { + completedPkgs.add(response.getPackageName()); + prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, + completedPkgs).apply(); + } }); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt index 05e676db99133..45262c7788f1a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt @@ -89,6 +89,9 @@ class ControlsControllerImplTest : SysuiTestCase() { @Captor private lateinit var controlLoadCallbackCaptor: ArgumentCaptor + @Captor + private lateinit var controlLoadCallbackCaptor2: + ArgumentCaptor @Captor private lateinit var broadcastReceiverCaptor: ArgumentCaptor @@ -797,43 +800,63 @@ class ControlsControllerImplTest : SysuiTestCase() { } @Test - fun testSeedFavoritesForComponentWithLimit() { - var succeeded = false + fun testSeedFavoritesForComponentsWithLimit() { + var responses = mutableListOf() - val controls = mutableListOf() + val controls1 = mutableListOf() for (i in 1..10) { - controls.add(statelessBuilderFromInfo(ControlInfo("id$i", TEST_CONTROL_TITLE, + controls1.add(statelessBuilderFromInfo(ControlInfo("id1:$i", TEST_CONTROL_TITLE, TEST_CONTROL_SUBTITLE, TEST_DEVICE_TYPE), "testStructure").build()) } - controller.seedFavoritesForComponent(TEST_COMPONENT, Consumer { accepted -> - succeeded = accepted + val controls2 = mutableListOf() + for (i in 1..3) { + controls2.add(statelessBuilderFromInfo(ControlInfo("id2:$i", TEST_CONTROL_TITLE, + TEST_CONTROL_SUBTITLE, TEST_DEVICE_TYPE), "testStructure2").build()) + } + controller.seedFavoritesForComponents(listOf(TEST_COMPONENT, TEST_COMPONENT_2), Consumer { + resp -> responses.add(resp) }) verify(bindingController).bindAndLoadSuggested(eq(TEST_COMPONENT), capture(controlLoadCallbackCaptor)) - - controlLoadCallbackCaptor.value.accept(controls) - + controlLoadCallbackCaptor.value.accept(controls1) delayableExecutor.runAllReady() + verify(bindingController).bindAndLoadSuggested(eq(TEST_COMPONENT_2), + capture(controlLoadCallbackCaptor2)) + controlLoadCallbackCaptor2.value.accept(controls2) + delayableExecutor.runAllReady() + + // COMPONENT 1 val structureInfo = controller.getFavoritesForComponent(TEST_COMPONENT)[0] assertEquals(structureInfo.controls.size, ControlsControllerImpl.SUGGESTED_CONTROLS_PER_STRUCTURE) var i = 1 structureInfo.controls.forEach { - assertEquals(it.controlId, "id$i") + assertEquals(it.controlId, "id1:$i") i++ } - assertTrue(succeeded) + assertEquals(SeedResponse(TEST_COMPONENT.packageName, true), responses[0]) + + // COMPONENT 2 + val structureInfo2 = controller.getFavoritesForComponent(TEST_COMPONENT_2)[0] + assertEquals(structureInfo2.controls.size, 3) + + i = 1 + structureInfo2.controls.forEach { + assertEquals(it.controlId, "id2:$i") + i++ + } + assertEquals(SeedResponse(TEST_COMPONENT.packageName, true), responses[1]) } @Test - fun testSeedFavoritesForComponent_error() { - var succeeded = false + fun testSeedFavoritesForComponents_error() { + var response: SeedResponse? = null - controller.seedFavoritesForComponent(TEST_COMPONENT, Consumer { accepted -> - succeeded = accepted + controller.seedFavoritesForComponents(listOf(TEST_COMPONENT), Consumer { resp -> + response = resp }) verify(bindingController).bindAndLoadSuggested(eq(TEST_COMPONENT), @@ -844,18 +867,18 @@ class ControlsControllerImplTest : SysuiTestCase() { delayableExecutor.runAllReady() assertEquals(listOf(), controller.getFavoritesForComponent(TEST_COMPONENT)) - assertFalse(succeeded) + assertEquals(SeedResponse(TEST_COMPONENT.packageName, false), response) } @Test - fun testSeedFavoritesForComponent_inProgressCallback() { - var succeeded = false + fun testSeedFavoritesForComponents_inProgressCallback() { + var response: SeedResponse? = null var seeded = false val control = statelessBuilderFromInfo(TEST_CONTROL_INFO, TEST_STRUCTURE_INFO.structure) .build() - controller.seedFavoritesForComponent(TEST_COMPONENT, Consumer { accepted -> - succeeded = accepted + controller.seedFavoritesForComponents(listOf(TEST_COMPONENT), Consumer { resp -> + response = resp }) verify(bindingController).bindAndLoadSuggested(eq(TEST_COMPONENT), @@ -870,7 +893,7 @@ class ControlsControllerImplTest : SysuiTestCase() { assertEquals(listOf(TEST_STRUCTURE_INFO), controller.getFavoritesForComponent(TEST_COMPONENT)) - assertTrue(succeeded) + assertEquals(SeedResponse(TEST_COMPONENT.packageName, true), response) assertTrue(seeded) }