Merge changes from topic "controls_config" into rvc-dev am: 61d46be2c2

Change-Id: Ifcba3a0e09e93748dfcad14d1baaab20d430885f
This commit is contained in:
TreeHugger Robot
2020-05-29 18:14:50 +00:00
committed by Automerger Merge Worker
8 changed files with 152 additions and 79 deletions

View File

@@ -98,9 +98,12 @@ public abstract class ControlsProviderService extends Service {
* *
* The service may be asked to provide a small number of recommended controls, in * 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 * 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 * the stateless builder {@link Control.StatelessBuilder}. The total number of controls
* through {@link Subscription#request} will be limited. Call {@link Subscriber#onComplete} * requested through {@link Subscription#request} will be restricted to a maximum. Within this
* when done, or {@link Subscriber#onError} for error scenarios. * 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 @Nullable
public Publisher<Control> createPublisherForSuggested() { public Publisher<Control> createPublisherForSuggested() {

View File

@@ -564,10 +564,10 @@
<!-- Respect drawable/rounded.xml intrinsic size for multiple radius corner path customization --> <!-- Respect drawable/rounded.xml intrinsic size for multiple radius corner path customization -->
<bool name="config_roundedCornerMultipleRadius">false</bool> <bool name="config_roundedCornerMultipleRadius">false</bool>
<!-- Controls can query a preferred application for limited number of suggested controls. <!-- Controls can query 2 preferred applications for limited number of suggested controls.
This config value should contain the package name of that preferred application. This config value should contain a list of package names of thoses preferred applications.
--> -->
<string translatable="false" name="config_controlsPreferredPackage"></string> <string-array translatable="false" name="config_controlsPreferredPackages" />
<!-- Max number of columns for quick controls area --> <!-- Max number of columns for quick controls area -->
<integer name="controls_max_columns">2</integer> <integer name="controls_max_columns">2</integer>

View File

@@ -46,7 +46,9 @@ open class ControlsBindingControllerImpl @Inject constructor(
companion object { companion object {
private const val TAG = "ControlsBindingControllerImpl" private const val TAG = "ControlsBindingControllerImpl"
private const val MAX_CONTROLS_REQUEST = 100000L 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()) private var currentUser = UserHandle.of(ActivityManager.getCurrentUser())

View File

@@ -114,12 +114,12 @@ interface ControlsController : UserAwareController {
/** /**
* Send a request to seed favorites into the persisted XML file * Send a request to seed favorites into the persisted XML file
* *
* @param componentName the component to seed controls from * @param componentNames the list of components to seed controls from
* @param callback true if the favorites were persisted * @param callback one [SeedResponse] per componentName
*/ */
fun seedFavoritesForComponent( fun seedFavoritesForComponents(
componentName: ComponentName, componentNames: List<ComponentName>,
callback: Consumer<Boolean> callback: Consumer<SeedResponse>
) )
/** /**
@@ -235,3 +235,5 @@ fun createLoadDataObject(
override val errorOnLoad = error override val errorOnLoad = error
} }
} }
data class SeedResponse(val packageName: String, val accepted: Boolean)

View File

@@ -72,6 +72,7 @@ class ControlsControllerImpl @Inject constructor (
private const val USER_CHANGE_RETRY_DELAY = 500L // ms private const val USER_CHANGE_RETRY_DELAY = 500L // ms
private const val DEFAULT_ENABLED = 1 private const val DEFAULT_ENABLED = 1
private const val PERMISSION_SELF = "com.android.systemui.permission.SELF" 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( private fun isAvailable(userId: Int, cr: ContentResolver) = Settings.Secure.getIntForUser(
cr, CONTROLS_AVAILABLE, DEFAULT_ENABLED, userId) != 0 cr, CONTROLS_AVAILABLE, DEFAULT_ENABLED, userId) != 0
@@ -361,29 +362,47 @@ class ControlsControllerImpl @Inject constructor (
return true return true
} }
override fun seedFavoritesForComponent( override fun seedFavoritesForComponents(
componentName: ComponentName, componentNames: List<ComponentName>,
callback: Consumer<Boolean> callback: Consumer<SeedResponse>
) { ) {
if (seedingInProgress) return if (seedingInProgress || componentNames.isEmpty()) return
Log.i(TAG, "Beginning request to seed favorites for: $componentName")
if (!confirmAvailability()) { if (!confirmAvailability()) {
if (userChanging) { if (userChanging) {
// Try again later, userChanging should not last forever. If so, we have bigger // 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, // problems. This will return a runnable that allows to cancel the delayed version,
// it will not be able to cancel the load if // it will not be able to cancel the load if
executor.executeDelayed( executor.executeDelayed(
{ seedFavoritesForComponent(componentName, callback) }, { seedFavoritesForComponents(componentNames, callback) },
USER_CHANGE_RETRY_DELAY, USER_CHANGE_RETRY_DELAY,
TimeUnit.MILLISECONDS TimeUnit.MILLISECONDS
) )
} else { } else {
callback.accept(false) componentNames.forEach {
callback.accept(SeedResponse(it.packageName, false))
}
} }
return return
} }
seedingInProgress = true seedingInProgress = true
startSeeding(componentNames, callback, false)
}
private fun startSeeding(
remainingComponentNames: List<ComponentName>,
callback: Consumer<SeedResponse>,
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( bindingController.bindAndLoadSuggested(
componentName, componentName,
object : ControlsBindingController.LoadCallback { object : ControlsBindingController.LoadCallback {
@@ -396,9 +415,11 @@ class ControlsControllerImpl @Inject constructor (
val structure = it.structure ?: "" val structure = it.structure ?: ""
val list = structureToControls.get(structure) val list = structureToControls.get(structure)
?: mutableListOf<ControlInfo>() ?: mutableListOf<ControlInfo>()
list.add( if (list.size < SUGGESTED_CONTROLS_PER_STRUCTURE) {
ControlInfo(it.controlId, it.title, it.subtitle, it.deviceType)) list.add(
structureToControls.put(structure, list) ControlInfo(it.controlId, it.title, it.subtitle, it.deviceType))
structureToControls.put(structure, list)
}
} }
structureToControls.forEach { structureToControls.forEach {
@@ -407,16 +428,16 @@ class ControlsControllerImpl @Inject constructor (
} }
persistenceWrapper.storeFavorites(Favorites.getAllStructures()) persistenceWrapper.storeFavorites(Favorites.getAllStructures())
callback.accept(true) callback.accept(SeedResponse(componentName.packageName, true))
endSeedingCall(true) startSeeding(remaining, callback, didAnyFail)
} }
} }
override fun error(message: String) { override fun error(message: String) {
Log.e(TAG, "Unable to seed favorites: $message") Log.e(TAG, "Unable to seed favorites: $message")
executor.execute { executor.execute {
callback.accept(false) callback.accept(SeedResponse(componentName.packageName, false))
endSeedingCall(false) startSeeding(remaining, callback, true)
} }
} }
} }

View File

@@ -328,7 +328,7 @@ class ControlsUiControllerImpl @Inject constructor (
val userContext = context.createContextAsUser(userHandle, 0) val userContext = context.createContextAsUser(userHandle, 0)
val prefs = userContext.getSharedPreferences( val prefs = userContext.getSharedPreferences(
"controls_prefs", Context.MODE_PRIVATE) "controls_prefs", Context.MODE_PRIVATE)
prefs.edit().putBoolean("ControlsSeedingCompleted", false).apply() prefs.edit().remove("SeedingCompleted").apply()
controlsController.get().resetFavorites() controlsController.get().resetFavorites()
dialog.dismiss() dialog.dismiss()
context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))

View File

@@ -139,8 +139,11 @@ import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.leak.RotationUtils; import com.android.systemui.util.leak.RotationUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import javax.inject.Inject; 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_EMERGENCY = "emergency";
static final String GLOBAL_ACTION_KEY_SCREENSHOT = "screenshot"; 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 String PREFS_CONTROLS_FILE = "controls_prefs";
private static final int SEEDING_MAX = 2;
private final Context mContext; private final Context mContext;
private final GlobalActionsManager mWindowManagerFuncs; 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() { private void seedFavorites() {
if (!mControlsControllerOptional.isPresent()) return; if (!mControlsControllerOptional.isPresent()
if (mControlsServiceInfos.isEmpty() || mControlsServiceInfos.isEmpty()) {
|| mControlsControllerOptional.get().getFavorites().size() > 0) {
return; 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() SharedPreferences prefs = mCurrentUserContextTracker.getCurrentUserContext()
.getSharedPreferences(PREFS_CONTROLS_FILE, Context.MODE_PRIVATE); .getSharedPreferences(PREFS_CONTROLS_FILE, Context.MODE_PRIVATE);
if (prefs.getBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, false)) { Set<String> seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED,
return; Collections.emptySet());
}
/* List<ComponentName> componentsToSeed = new ArrayList<>();
* 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;
for (ControlsServiceInfo info : mControlsServiceInfos) { for (ControlsServiceInfo info : mControlsServiceInfos) {
if (info.componentName.getPackageName().equals(preferredControlsPackage)) { String pkg = info.componentName.getPackageName();
preferredComponent = info.componentName; if (seededPackages.contains(pkg)
break; || 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) { if (componentsToSeed.isEmpty()) return;
Log.i(TAG, "Controls seeding: No preferred component has been set, will not seed");
prefs.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, true).apply();
return;
}
mControlsControllerOptional.get().seedFavoritesForComponent( mControlsControllerOptional.get().seedFavoritesForComponents(
preferredComponent, componentsToSeed,
(accepted) -> { (response) -> {
Log.i(TAG, "Controls seeded: " + accepted); Log.d(TAG, "Controls seeded: " + response);
prefs.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, accepted).apply(); Set<String> completedPkgs = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED,
new HashSet<String>());
if (response.getAccepted()) {
completedPkgs.add(response.getPackageName());
prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED,
completedPkgs).apply();
}
}); });
} }

View File

@@ -89,6 +89,9 @@ class ControlsControllerImplTest : SysuiTestCase() {
@Captor @Captor
private lateinit var controlLoadCallbackCaptor: private lateinit var controlLoadCallbackCaptor:
ArgumentCaptor<ControlsBindingController.LoadCallback> ArgumentCaptor<ControlsBindingController.LoadCallback>
@Captor
private lateinit var controlLoadCallbackCaptor2:
ArgumentCaptor<ControlsBindingController.LoadCallback>
@Captor @Captor
private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver> private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver>
@@ -797,33 +800,63 @@ class ControlsControllerImplTest : SysuiTestCase() {
} }
@Test @Test
fun testSeedFavoritesForComponent() { fun testSeedFavoritesForComponentsWithLimit() {
var succeeded = false var responses = mutableListOf<SeedResponse>()
val control = statelessBuilderFromInfo(TEST_CONTROL_INFO, TEST_STRUCTURE_INFO.structure)
.build()
controller.seedFavoritesForComponent(TEST_COMPONENT, Consumer { accepted -> val controls1 = mutableListOf<Control>()
succeeded = accepted for (i in 1..10) {
controls1.add(statelessBuilderFromInfo(ControlInfo("id1:$i", TEST_CONTROL_TITLE,
TEST_CONTROL_SUBTITLE, TEST_DEVICE_TYPE), "testStructure").build())
}
val controls2 = mutableListOf<Control>()
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), verify(bindingController).bindAndLoadSuggested(eq(TEST_COMPONENT),
capture(controlLoadCallbackCaptor)) capture(controlLoadCallbackCaptor))
controlLoadCallbackCaptor.value.accept(controls1)
controlLoadCallbackCaptor.value.accept(listOf(control))
delayableExecutor.runAllReady() delayableExecutor.runAllReady()
assertEquals(listOf(TEST_STRUCTURE_INFO), verify(bindingController).bindAndLoadSuggested(eq(TEST_COMPONENT_2),
controller.getFavoritesForComponent(TEST_COMPONENT)) capture(controlLoadCallbackCaptor2))
assertTrue(succeeded) 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, "id1:$i")
i++
}
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 @Test
fun testSeedFavoritesForComponent_error() { fun testSeedFavoritesForComponents_error() {
var succeeded = false var response: SeedResponse? = null
controller.seedFavoritesForComponent(TEST_COMPONENT, Consumer { accepted -> controller.seedFavoritesForComponents(listOf(TEST_COMPONENT), Consumer { resp ->
succeeded = accepted response = resp
}) })
verify(bindingController).bindAndLoadSuggested(eq(TEST_COMPONENT), verify(bindingController).bindAndLoadSuggested(eq(TEST_COMPONENT),
@@ -834,18 +867,18 @@ class ControlsControllerImplTest : SysuiTestCase() {
delayableExecutor.runAllReady() delayableExecutor.runAllReady()
assertEquals(listOf<StructureInfo>(), controller.getFavoritesForComponent(TEST_COMPONENT)) assertEquals(listOf<StructureInfo>(), controller.getFavoritesForComponent(TEST_COMPONENT))
assertFalse(succeeded) assertEquals(SeedResponse(TEST_COMPONENT.packageName, false), response)
} }
@Test @Test
fun testSeedFavoritesForComponent_inProgressCallback() { fun testSeedFavoritesForComponents_inProgressCallback() {
var succeeded = false var response: SeedResponse? = null
var seeded = false var seeded = false
val control = statelessBuilderFromInfo(TEST_CONTROL_INFO, TEST_STRUCTURE_INFO.structure) val control = statelessBuilderFromInfo(TEST_CONTROL_INFO, TEST_STRUCTURE_INFO.structure)
.build() .build()
controller.seedFavoritesForComponent(TEST_COMPONENT, Consumer { accepted -> controller.seedFavoritesForComponents(listOf(TEST_COMPONENT), Consumer { resp ->
succeeded = accepted response = resp
}) })
verify(bindingController).bindAndLoadSuggested(eq(TEST_COMPONENT), verify(bindingController).bindAndLoadSuggested(eq(TEST_COMPONENT),
@@ -860,7 +893,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
assertEquals(listOf(TEST_STRUCTURE_INFO), assertEquals(listOf(TEST_STRUCTURE_INFO),
controller.getFavoritesForComponent(TEST_COMPONENT)) controller.getFavoritesForComponent(TEST_COMPONENT))
assertTrue(succeeded) assertEquals(SeedResponse(TEST_COMPONENT.packageName, true), response)
assertTrue(seeded) assertTrue(seeded)
} }