Controls UI - Add 'reset' option for seeding
Enable for testing controls seeding. Make sure subscriptions are cleaned up properly. Up the seeding limit to 6. If we received our requested control limit on load, cancel() the subscription and immediately load. Bug: 153455870 Test: manual, use 'Reset' button to test seeding process Change-Id: I536315de423c49231e77c0b1b24ad20883ddaae7
This commit is contained in:
@@ -45,7 +45,7 @@ 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 = 4L
|
||||
private const val SUGGESTED_CONTROLS_REQUEST = 6L
|
||||
}
|
||||
|
||||
private var currentUser = UserHandle.of(ActivityManager.getCurrentUser())
|
||||
@@ -61,6 +61,11 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
||||
*/
|
||||
private var statefulControlSubscriber: StatefulControlSubscriber? = null
|
||||
|
||||
/*
|
||||
* Will track any active load subscriber. Only one can be active at any time.
|
||||
*/
|
||||
private var loadSubscriber: LoadSubscriber? = null
|
||||
|
||||
private val actionCallbackService = object : IControlsActionCallback.Stub() {
|
||||
override fun accept(
|
||||
token: IBinder,
|
||||
@@ -99,17 +104,24 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
||||
component: ComponentName,
|
||||
callback: ControlsBindingController.LoadCallback
|
||||
): Runnable {
|
||||
val subscriber = LoadSubscriber(callback, MAX_CONTROLS_REQUEST)
|
||||
retrieveLifecycleManager(component).maybeBindAndLoad(subscriber)
|
||||
return subscriber.loadCancel()
|
||||
loadSubscriber?.loadCancel()
|
||||
|
||||
val ls = LoadSubscriber(callback, MAX_CONTROLS_REQUEST)
|
||||
loadSubscriber = ls
|
||||
|
||||
retrieveLifecycleManager(component).maybeBindAndLoad(ls)
|
||||
return ls.loadCancel()
|
||||
}
|
||||
|
||||
override fun bindAndLoadSuggested(
|
||||
component: ComponentName,
|
||||
callback: ControlsBindingController.LoadCallback
|
||||
) {
|
||||
val subscriber = LoadSubscriber(callback, SUGGESTED_CONTROLS_REQUEST)
|
||||
retrieveLifecycleManager(component).maybeBindAndLoadSuggested(subscriber)
|
||||
loadSubscriber?.loadCancel()
|
||||
val ls = LoadSubscriber(callback, SUGGESTED_CONTROLS_REQUEST)
|
||||
loadSubscriber = ls
|
||||
|
||||
retrieveLifecycleManager(component).maybeBindAndLoadSuggested(ls)
|
||||
}
|
||||
|
||||
override fun subscribe(structureInfo: StructureInfo) {
|
||||
@@ -152,13 +164,16 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
||||
override fun changeUser(newUser: UserHandle) {
|
||||
if (newUser == currentUser) return
|
||||
|
||||
unsubscribe()
|
||||
unbind()
|
||||
currentProvider = null
|
||||
currentUser = newUser
|
||||
}
|
||||
|
||||
private fun unbind() {
|
||||
unsubscribe()
|
||||
|
||||
loadSubscriber?.loadCancel()
|
||||
loadSubscriber = null
|
||||
|
||||
currentProvider?.unbindService()
|
||||
currentProvider = null
|
||||
}
|
||||
@@ -210,6 +225,20 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
||||
val callback: ControlsBindingController.LoadCallback
|
||||
) : CallbackRunnable(token) {
|
||||
override fun doRun() {
|
||||
Log.d(TAG, "LoadSubscription: Complete and loading controls")
|
||||
callback.accept(list)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class OnCancelAndLoadRunnable(
|
||||
token: IBinder,
|
||||
val list: List<Control>,
|
||||
val subscription: IControlsSubscription,
|
||||
val callback: ControlsBindingController.LoadCallback
|
||||
) : CallbackRunnable(token) {
|
||||
override fun doRun() {
|
||||
Log.d(TAG, "LoadSubscription: Canceling and loading controls")
|
||||
provider?.cancelSubscription(subscription)
|
||||
callback.accept(list)
|
||||
}
|
||||
}
|
||||
@@ -220,6 +249,7 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
||||
val requestLimit: Long
|
||||
) : CallbackRunnable(token) {
|
||||
override fun doRun() {
|
||||
Log.d(TAG, "LoadSubscription: Starting subscription")
|
||||
provider?.startSubscription(subscription, requestLimit)
|
||||
}
|
||||
}
|
||||
@@ -254,34 +284,54 @@ open class ControlsBindingControllerImpl @Inject constructor(
|
||||
val requestLimit: Long
|
||||
) : IControlsSubscriber.Stub() {
|
||||
val loadedControls = ArrayList<Control>()
|
||||
var hasError = false
|
||||
private var isTerminated = false
|
||||
private var _loadCancelInternal: (() -> Unit)? = null
|
||||
private lateinit var subscription: IControlsSubscription
|
||||
|
||||
fun loadCancel() = Runnable {
|
||||
Log.d(TAG, "Cancel load requested")
|
||||
_loadCancelInternal?.invoke()
|
||||
}
|
||||
Log.d(TAG, "Cancel load requested")
|
||||
_loadCancelInternal?.invoke()
|
||||
}
|
||||
|
||||
override fun onSubscribe(token: IBinder, subs: IControlsSubscription) {
|
||||
_loadCancelInternal = subs::cancel
|
||||
subscription = subs
|
||||
_loadCancelInternal = { currentProvider?.cancelSubscription(subscription) }
|
||||
backgroundExecutor.execute(OnSubscribeRunnable(token, subs, requestLimit))
|
||||
}
|
||||
|
||||
override fun onNext(token: IBinder, c: Control) {
|
||||
backgroundExecutor.execute { loadedControls.add(c) }
|
||||
backgroundExecutor.execute {
|
||||
if (isTerminated) return@execute
|
||||
|
||||
loadedControls.add(c)
|
||||
|
||||
// Once we have reached our requestLimit, send a request to cancel, and immediately
|
||||
// load the results. Calls to onError() and onComplete() are not required after
|
||||
// cancel.
|
||||
if (loadedControls.size >= requestLimit) {
|
||||
maybeTerminateAndRun(
|
||||
OnCancelAndLoadRunnable(token, loadedControls, subscription, callback)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(token: IBinder, s: String) {
|
||||
hasError = true
|
||||
_loadCancelInternal = {}
|
||||
currentProvider?.cancelLoadTimeout()
|
||||
backgroundExecutor.execute(OnLoadErrorRunnable(token, s, callback))
|
||||
maybeTerminateAndRun(OnLoadErrorRunnable(token, s, callback))
|
||||
}
|
||||
|
||||
override fun onComplete(token: IBinder) {
|
||||
maybeTerminateAndRun(OnLoadRunnable(token, loadedControls, callback))
|
||||
}
|
||||
|
||||
private fun maybeTerminateAndRun(postTerminateFn: Runnable) {
|
||||
if (isTerminated) return
|
||||
|
||||
isTerminated = true
|
||||
_loadCancelInternal = {}
|
||||
if (!hasError) {
|
||||
currentProvider?.cancelLoadTimeout()
|
||||
backgroundExecutor.execute(OnLoadRunnable(token, loadedControls, callback))
|
||||
}
|
||||
currentProvider?.cancelLoadTimeout()
|
||||
|
||||
backgroundExecutor.execute(postTerminateFn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +179,11 @@ interface ControlsController : UserAwareController {
|
||||
*/
|
||||
fun countFavoritesForComponent(componentName: ComponentName): Int
|
||||
|
||||
/**
|
||||
* TEMPORARY for testing
|
||||
*/
|
||||
fun resetFavorites()
|
||||
|
||||
/**
|
||||
* Interface for structure to pass data to [ControlsFavoritingActivity].
|
||||
*/
|
||||
|
||||
@@ -365,6 +365,8 @@ class ControlsControllerImpl @Inject constructor (
|
||||
componentName: ComponentName,
|
||||
callback: Consumer<Boolean>
|
||||
) {
|
||||
if (seedingInProgress) return
|
||||
|
||||
Log.i(TAG, "Beginning request to seed favorites for: $componentName")
|
||||
if (!confirmAvailability()) {
|
||||
if (userChanging) {
|
||||
@@ -495,6 +497,13 @@ class ControlsControllerImpl @Inject constructor (
|
||||
}
|
||||
}
|
||||
|
||||
override fun resetFavorites() {
|
||||
executor.execute {
|
||||
Favorites.clear()
|
||||
persistenceWrapper.storeFavorites(Favorites.getAllStructures())
|
||||
}
|
||||
}
|
||||
|
||||
override fun refreshStatus(componentName: ComponentName, control: Control) {
|
||||
if (!confirmAvailability()) {
|
||||
Log.d(TAG, "Controls not available")
|
||||
|
||||
@@ -63,8 +63,6 @@ class ControlsProviderLifecycleManager(
|
||||
) : IBinder.DeathRecipient {
|
||||
|
||||
val token: IBinder = Binder()
|
||||
@GuardedBy("subscriptions")
|
||||
private val subscriptions = mutableListOf<IControlsSubscription>()
|
||||
private var requiresBound = false
|
||||
@GuardedBy("queuedServiceMethods")
|
||||
private val queuedServiceMethods: MutableSet<ServiceMethod> = ArraySet()
|
||||
@@ -194,7 +192,7 @@ class ControlsProviderLifecycleManager(
|
||||
* Request a call to [IControlsProvider.loadSuggested].
|
||||
*
|
||||
* If the service is not bound, the call will be queued and the service will be bound first.
|
||||
* The service will be unbound after the controls are returned or the call times out.
|
||||
* The service will be unbound if the call times out.
|
||||
*
|
||||
* @param subscriber the subscriber that manages coordination for loading controls
|
||||
*/
|
||||
@@ -245,9 +243,7 @@ class ControlsProviderLifecycleManager(
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "startSubscription: $subscription")
|
||||
}
|
||||
synchronized(subscriptions) {
|
||||
subscriptions.add(subscription)
|
||||
}
|
||||
|
||||
wrapper?.request(subscription, requestLimit)
|
||||
}
|
||||
|
||||
@@ -261,9 +257,7 @@ class ControlsProviderLifecycleManager(
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "cancelSubscription: $subscription")
|
||||
}
|
||||
synchronized(subscriptions) {
|
||||
subscriptions.remove(subscription)
|
||||
}
|
||||
|
||||
wrapper?.cancel(subscription)
|
||||
}
|
||||
|
||||
@@ -281,17 +275,6 @@ class ControlsProviderLifecycleManager(
|
||||
onLoadCanceller?.run()
|
||||
onLoadCanceller = null
|
||||
|
||||
// be sure to cancel all subscriptions
|
||||
val subs = synchronized(subscriptions) {
|
||||
ArrayList(subscriptions).also {
|
||||
subscriptions.clear()
|
||||
}
|
||||
}
|
||||
|
||||
subs.forEach {
|
||||
wrapper?.cancel(it)
|
||||
}
|
||||
|
||||
bindService(false)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,18 +16,26 @@
|
||||
|
||||
package com.android.systemui.controls.ui
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.animation.ObjectAnimator
|
||||
import android.app.AlertDialog
|
||||
import android.app.Dialog
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.os.Process
|
||||
import android.service.controls.Control
|
||||
import android.service.controls.actions.ControlAction
|
||||
import android.util.TypedValue
|
||||
import android.util.Log
|
||||
import android.view.animation.AccelerateInterpolator
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import android.view.ContextThemeWrapper
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@@ -77,6 +85,8 @@ class ControlsUiControllerImpl @Inject constructor (
|
||||
private const val PREF_COMPONENT = "controls_component"
|
||||
private const val PREF_STRUCTURE = "controls_structure"
|
||||
|
||||
private const val FADE_IN_MILLIS = 225L
|
||||
|
||||
private val EMPTY_COMPONENT = ComponentName("", "")
|
||||
private val EMPTY_STRUCTURE = StructureInfo(
|
||||
EMPTY_COMPONENT,
|
||||
@@ -153,7 +163,20 @@ class ControlsUiControllerImpl @Inject constructor (
|
||||
|
||||
private fun reload(parent: ViewGroup) {
|
||||
if (hidden) return
|
||||
show(parent)
|
||||
|
||||
val fadeAnim = ObjectAnimator.ofFloat(parent, "alpha", 1.0f, 0.0f)
|
||||
fadeAnim.setInterpolator(AccelerateInterpolator(1.0f))
|
||||
fadeAnim.setDuration(FADE_IN_MILLIS)
|
||||
fadeAnim.addListener(object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
show(parent)
|
||||
val showAnim = ObjectAnimator.ofFloat(parent, "alpha", 0.0f, 1.0f)
|
||||
showAnim.setInterpolator(DecelerateInterpolator(1.0f))
|
||||
showAnim.setDuration(FADE_IN_MILLIS)
|
||||
showAnim.start()
|
||||
}
|
||||
})
|
||||
fadeAnim.start()
|
||||
}
|
||||
|
||||
private fun showSeedingView(items: List<SelectionItem>) {
|
||||
@@ -229,7 +252,8 @@ class ControlsUiControllerImpl @Inject constructor (
|
||||
|
||||
private fun createMenu() {
|
||||
val items = arrayOf(
|
||||
context.resources.getString(R.string.controls_menu_add)
|
||||
context.resources.getString(R.string.controls_menu_add),
|
||||
"Reset"
|
||||
)
|
||||
var adapter = ArrayAdapter<String>(context, R.layout.controls_more_item, items)
|
||||
|
||||
@@ -249,6 +273,8 @@ class ControlsUiControllerImpl @Inject constructor (
|
||||
when (pos) {
|
||||
// 0: Add Control
|
||||
0 -> startFavoritingActivity(view.context, selectedStructure)
|
||||
// 1: TEMPORARY for reset controls
|
||||
1 -> showResetConfirmation()
|
||||
else -> Log.w(ControlsUiController.TAG,
|
||||
"Unsupported index ($pos) on 'more' menu selection")
|
||||
}
|
||||
@@ -275,6 +301,39 @@ class ControlsUiControllerImpl @Inject constructor (
|
||||
})
|
||||
}
|
||||
|
||||
private fun showResetConfirmation() {
|
||||
val builder = AlertDialog.Builder(
|
||||
context,
|
||||
android.R.style.Theme_DeviceDefault_Dialog_Alert
|
||||
).apply {
|
||||
setMessage("For testing purposes: Would you like to " +
|
||||
"reset your favorited device controls?")
|
||||
setPositiveButton(
|
||||
android.R.string.ok,
|
||||
DialogInterface.OnClickListener { dialog, _ ->
|
||||
val userHandle = Process.myUserHandle()
|
||||
val userContext = context.createContextAsUser(userHandle, 0)
|
||||
val prefs = userContext.getSharedPreferences(
|
||||
"controls_prefs", Context.MODE_PRIVATE)
|
||||
prefs.edit().putBoolean("ControlsSeedingCompleted", false).apply()
|
||||
controlsController.get().resetFavorites()
|
||||
dialog.dismiss()
|
||||
context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
|
||||
})
|
||||
setNegativeButton(
|
||||
android.R.string.cancel,
|
||||
DialogInterface.OnClickListener {
|
||||
dialog, _ -> dialog.cancel()
|
||||
}
|
||||
)
|
||||
}
|
||||
builder.create().apply {
|
||||
getWindow().apply {
|
||||
setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
|
||||
}
|
||||
}.show()
|
||||
}
|
||||
|
||||
private fun createDropDown(items: List<SelectionItem>) {
|
||||
items.forEach {
|
||||
RenderInfo.registerComponentIcon(it.componentName, it.icon)
|
||||
|
||||
@@ -205,7 +205,9 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
|
||||
private final IWindowManager mIWindowManager;
|
||||
private final Executor mBackgroundExecutor;
|
||||
private final ControlsListingController mControlsListingController;
|
||||
private boolean mAnyControlsProviders = false;
|
||||
private List<ControlsServiceInfo> mControlsServiceInfos = new ArrayList<>();
|
||||
private ControlsController mControlsController;
|
||||
private SharedPreferences mControlsPreferences;
|
||||
|
||||
@VisibleForTesting
|
||||
public enum GlobalActionsEvent implements UiEventLogger.UiEventEnum {
|
||||
@@ -271,6 +273,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
|
||||
mBackgroundExecutor = backgroundExecutor;
|
||||
mControlsListingController = controlsListingController;
|
||||
mBlurUtils = blurUtils;
|
||||
mControlsController = controlsController;
|
||||
|
||||
// receive broadcasts
|
||||
IntentFilter filter = new IntentFilter();
|
||||
@@ -309,45 +312,54 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
|
||||
}
|
||||
});
|
||||
|
||||
String preferredControlsPackage = mContext.getResources()
|
||||
.getString(com.android.systemui.R.string.config_controlsPreferredPackage);
|
||||
mControlsListingController.addCallback(list -> {
|
||||
mAnyControlsProviders = !list.isEmpty();
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
ComponentName preferredComponent = null;
|
||||
for (ControlsServiceInfo info : list) {
|
||||
if (info.componentName.getPackageName().equals(preferredControlsPackage)) {
|
||||
preferredComponent = info.componentName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (preferredComponent == null) return;
|
||||
|
||||
SharedPreferences prefs = context.getSharedPreferences(PREFS_CONTROLS_FILE,
|
||||
Context.MODE_PRIVATE);
|
||||
boolean isSeeded = prefs.getBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, false);
|
||||
boolean hasFavorites = controlsController.getFavorites().size() > 0;
|
||||
if (!isSeeded && !hasFavorites) {
|
||||
controlsController.seedFavoritesForComponent(
|
||||
preferredComponent,
|
||||
(accepted) -> {
|
||||
Log.i(TAG, "Controls seeded: " + accepted);
|
||||
prefs.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED,
|
||||
accepted).apply();
|
||||
}
|
||||
);
|
||||
}
|
||||
mControlsServiceInfos = list;
|
||||
});
|
||||
|
||||
// Need to be user-specific with the context to make sure we read the correct prefs
|
||||
Context userContext = context.createContextAsUser(
|
||||
new UserHandle(mUserManager.getUserHandle()), 0);
|
||||
mControlsPreferences = userContext.getSharedPreferences(PREFS_CONTROLS_FILE,
|
||||
Context.MODE_PRIVATE);
|
||||
|
||||
}
|
||||
|
||||
private void seedFavorites() {
|
||||
if (mControlsServiceInfos.isEmpty()
|
||||
|| mControlsController.getFavorites().size() > 0
|
||||
|| mControlsPreferences.getBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, false)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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) {
|
||||
if (info.componentName.getPackageName().equals(preferredControlsPackage)) {
|
||||
preferredComponent = info.componentName;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (preferredComponent == null) {
|
||||
Log.i(TAG, "Controls seeding: No preferred component has been set, will not seed");
|
||||
mControlsPreferences.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED, true).apply();
|
||||
}
|
||||
|
||||
mControlsController.seedFavoritesForComponent(
|
||||
preferredComponent,
|
||||
(accepted) -> {
|
||||
Log.i(TAG, "Controls seeded: " + accepted);
|
||||
mControlsPreferences.edit().putBoolean(PREFS_CONTROLS_SEEDING_COMPLETED,
|
||||
accepted).apply();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the global actions dialog (creating if necessary)
|
||||
@@ -393,6 +405,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
|
||||
awakenIfNecessary();
|
||||
mDialog = createDialog();
|
||||
prepareDialog();
|
||||
seedFavorites();
|
||||
|
||||
// If we only have 1 item and it's a simple press action, just do this action.
|
||||
if (mAdapter.getCount() == 1
|
||||
@@ -2017,6 +2030,6 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener,
|
||||
private boolean shouldShowControls() {
|
||||
return mKeyguardStateController.isUnlocked()
|
||||
&& mControlsUiController.getAvailable()
|
||||
&& mAnyControlsProviders;
|
||||
&& !mControlsServiceInfos.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ class ControlsBindingControllerImplTest : SysuiTestCase() {
|
||||
loadSubscriberCaptor.value.onSubscribe(Binder(), subscription)
|
||||
|
||||
canceller.run()
|
||||
verify(subscription).cancel()
|
||||
verify(providers[0]).cancelSubscription(subscription)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -145,7 +145,7 @@ class ControlsBindingControllerImplTest : SysuiTestCase() {
|
||||
|
||||
loadSubscriberCaptor.value.onComplete(b)
|
||||
canceller.run()
|
||||
verify(subscription, never()).cancel()
|
||||
verify(providers[0], never()).cancelSubscription(subscription)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -203,7 +203,7 @@ class ControlsBindingControllerImplTest : SysuiTestCase() {
|
||||
|
||||
loadSubscriberCaptor.value.onError(b, "")
|
||||
canceller.run()
|
||||
verify(subscription, never()).cancel()
|
||||
verify(providers[0], never()).cancelSubscription(subscription)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user