From cd757c894c7356271feaf1a4d0b6a4bc4b40e161 Mon Sep 17 00:00:00 2001 From: Matt Pietal Date: Wed, 8 Apr 2020 10:20:48 -0400 Subject: [PATCH] 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 --- .../ControlsBindingControllerImpl.kt | 94 ++++++++++++++----- .../controls/controller/ControlsController.kt | 5 + .../controller/ControlsControllerImpl.kt | 9 ++ .../ControlsProviderLifecycleManager.kt | 23 +---- .../controls/ui/ControlsUiControllerImpl.kt | 63 ++++++++++++- .../globalactions/GlobalActionsDialog.java | 83 +++++++++------- .../ControlsBindingControllerImplTest.kt | 6 +- 7 files changed, 201 insertions(+), 82 deletions(-) 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 5d03fc51004d7..7e8fec716b1f0 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt @@ -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, + 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() - 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) } } } 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 ae75dd4d94ab3..568fb289027d5 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt @@ -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]. */ 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 34833396acef3..8805694616a4b 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt @@ -365,6 +365,8 @@ class ControlsControllerImpl @Inject constructor ( componentName: ComponentName, callback: Consumer ) { + 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") diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt index 895f1d218982f..a6af6a11d8b73 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt @@ -63,8 +63,6 @@ class ControlsProviderLifecycleManager( ) : IBinder.DeathRecipient { val token: IBinder = Binder() - @GuardedBy("subscriptions") - private val subscriptions = mutableListOf() private var requiresBound = false @GuardedBy("queuedServiceMethods") private val queuedServiceMethods: MutableSet = 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) } 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 208d9117e0881..a5f42983e4e04 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -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) { @@ -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(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) { items.forEach { RenderInfo.registerComponentIcon(it.componentName, it.icon) diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index 4dd5e87d2c93f..79def1d14a1d2 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -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 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(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt index 88316f2d43238..bb003ee59978b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt @@ -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