Merge "Controls UI - Add 'reset' option for seeding" into rvc-dev

This commit is contained in:
TreeHugger Robot
2020-04-09 16:27:07 +00:00
committed by Android (Google) Code Review
7 changed files with 201 additions and 82 deletions

View File

@@ -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)
}
}
}

View File

@@ -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].
*/

View File

@@ -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")

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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();
}
}

View File

@@ -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