Merge "Controls UI - Add 'reset' option for seeding" into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
7cbba76be9
@@ -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