From 587a5f7f6f7a64a5f9b912c3b8162c3108f1438a Mon Sep 17 00:00:00 2001 From: Matt Pietal Date: Fri, 7 Feb 2020 09:34:49 -0500 Subject: [PATCH] Controls API - New method for suggested controls Add a defaulted method, loadSuggestedControls(), in order to ask an application for controls to suggest to the user. The idea is that OEMs can choose a default application, and seed the controls area with a small number of controls that may be helpful to the user. Bug: 149037812 Test: atest ControlsProviderServiceTest Change-Id: Icf9638c2d07988519527c24383693c4b9e0bb6d3 --- api/current.txt | 1 + .../controls/ControlsProviderService.java | 53 ++++++++++++++++++- .../service/controls/IControlsProvider.aidl | 2 + .../controls/ControlProviderServiceTest.java | 33 ++++++++++++ 4 files changed, 87 insertions(+), 2 deletions(-) diff --git a/api/current.txt b/api/current.txt index dcf23b039c075..4f5a57a1f30c8 100644 --- a/api/current.txt +++ b/api/current.txt @@ -43289,6 +43289,7 @@ package android.service.controls { public abstract class ControlsProviderService extends android.app.Service { ctor public ControlsProviderService(); method public abstract void loadAvailableControls(@NonNull java.util.function.Consumer>); + method public void loadSuggestedControls(int, @NonNull java.util.function.Consumer>); method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent); method public abstract void performControlAction(@NonNull String, @NonNull android.service.controls.actions.ControlAction, @NonNull java.util.function.Consumer); method @NonNull public abstract java.util.concurrent.Flow.Publisher publisherFor(@NonNull java.util.List); diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java index bc65818870481..de4c056e55011 100644 --- a/core/java/android/service/controls/ControlsProviderService.java +++ b/core/java/android/service/controls/ControlsProviderService.java @@ -35,6 +35,7 @@ import android.util.Log; import com.android.internal.util.Preconditions; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.Flow.Publisher; import java.util.concurrent.Flow.Subscriber; @@ -72,6 +73,18 @@ public abstract class ControlsProviderService extends Service { */ public abstract void loadAvailableControls(@NonNull Consumer> consumer); + /** + * (Optional) 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 + * the stateless builder {@link Control.StatelessBuilder}, followed by an invocation to the + * provided consumer to callback to the call originator. If the number of controls + * is greater than maxNumber, the list will be truncated. + */ + public void loadSuggestedControls(int maxNumber, @NonNull Consumer> consumer) { + // Override to change the default behavior + consumer.accept(Collections.emptyList()); + } + /** * Return a valid Publisher for the given controlIds. This publisher will be asked * to provide updates for the given list of controlIds as long as the Subscription @@ -104,6 +117,11 @@ public abstract class ControlsProviderService extends Service { mHandler.obtainMessage(RequestHandler.MSG_LOAD, cb).sendToTarget(); } + public void loadSuggested(int maxNumber, IControlsLoadCallback cb) { + LoadMessage msg = new LoadMessage(maxNumber, cb); + mHandler.obtainMessage(RequestHandler.MSG_LOAD_SUGGESTED, msg).sendToTarget(); + } + public void subscribe(List controlIds, IControlsSubscriber subscriber) { SubscribeMessage msg = new SubscribeMessage(controlIds, subscriber); @@ -128,6 +146,14 @@ public abstract class ControlsProviderService extends Service { private static final int MSG_LOAD = 1; private static final int MSG_SUBSCRIBE = 2; private static final int MSG_ACTION = 3; + private static final int MSG_LOAD_SUGGESTED = 4; + + /** + * This the maximum number of controls that can be loaded via + * {@link ControlsProviderService#loadAvailablecontrols}. Anything over this number + * will be truncated. + */ + private static final int MAX_NUMBER_OF_CONTROLS_ALLOWED = 1000; RequestHandler(Looper looper) { super(looper); @@ -137,7 +163,14 @@ public abstract class ControlsProviderService extends Service { switch(msg.what) { case MSG_LOAD: final IControlsLoadCallback cb = (IControlsLoadCallback) msg.obj; - ControlsProviderService.this.loadAvailableControls(consumerFor(cb)); + ControlsProviderService.this.loadAvailableControls(consumerFor( + MAX_NUMBER_OF_CONTROLS_ALLOWED, cb)); + break; + + case MSG_LOAD_SUGGESTED: + final LoadMessage lMsg = (LoadMessage) msg.obj; + ControlsProviderService.this.loadSuggestedControls(lMsg.mMaxNumber, + consumerFor(lMsg.mMaxNumber, lMsg.mCb)); break; case MSG_SUBSCRIBE: @@ -201,9 +234,15 @@ public abstract class ControlsProviderService extends Service { }; } - private Consumer> consumerFor(IControlsLoadCallback cb) { + private Consumer> consumerFor(int maxNumber, IControlsLoadCallback cb) { return (@NonNull List controls) -> { Preconditions.checkNotNull(controls); + if (controls.size() > maxNumber) { + Log.w(TAG, "Too many controls. Provided: " + controls.size() + ", Max allowed: " + + maxNumber + ". Truncating the list."); + controls = controls.subList(0, maxNumber); + } + List list = new ArrayList<>(); for (Control control: controls) { if (control == null) { @@ -268,4 +307,14 @@ public abstract class ControlsProviderService extends Service { this.mSubscriber = subscriber; } } + + private static class LoadMessage { + final int mMaxNumber; + final IControlsLoadCallback mCb; + + LoadMessage(int maxNumber, IControlsLoadCallback cb) { + this.mMaxNumber = maxNumber; + this.mCb = cb; + } + } } diff --git a/core/java/android/service/controls/IControlsProvider.aidl b/core/java/android/service/controls/IControlsProvider.aidl index 4ce658ed6990f..4375fbb289db4 100644 --- a/core/java/android/service/controls/IControlsProvider.aidl +++ b/core/java/android/service/controls/IControlsProvider.aidl @@ -27,6 +27,8 @@ import android.service.controls.actions.ControlActionWrapper; oneway interface IControlsProvider { void load(IControlsLoadCallback cb); + void loadSuggested(int maxNumber, IControlsLoadCallback cb); + void subscribe(in List controlIds, IControlsSubscriber subscriber); diff --git a/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java b/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java index 2648a0644dc13..a24b4e06225a0 100644 --- a/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java +++ b/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java @@ -146,6 +146,34 @@ public class ControlProviderServiceTest { assertEquals(Control.STATUS_UNKNOWN, l.get(0).getStatus()); } + @Test + public void testLoadSuggested_withMaxNumber() throws RemoteException { + Control control1 = new Control.StatelessBuilder("TEST_ID", mPendingIntent).build(); + Control control2 = new Control.StatelessBuilder("TEST_ID_2", mPendingIntent) + .setDeviceType(DeviceTypes.TYPE_AIR_FRESHENER).build(); + + @SuppressWarnings("unchecked") + ArgumentCaptor> captor = ArgumentCaptor.forClass(List.class); + + ArrayList list = new ArrayList<>(); + list.add(control1); + list.add(control2); + + final int maxSuggested = 1; + + mControlsProviderService.setControls(list); + mControlsProvider.loadSuggested(maxSuggested, mLoadCallback); + InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + + verify(mLoadCallback).accept(eq(mToken), captor.capture()); + List l = captor.getValue(); + assertEquals(maxSuggested, l.size()); + + for (int i = 0; i < maxSuggested; ++i) { + assertTrue(equals(list.get(i), l.get(i))); + } + } + @Test public void testSubscribe() throws RemoteException { Control control = new Control.StatefulBuilder("TEST_ID", mPendingIntent) @@ -215,6 +243,11 @@ public class ControlProviderServiceTest { cb.accept(mControls); } + @Override + public void loadSuggestedControls(int maxNumber, Consumer> cb) { + cb.accept(mControls); + } + @Override public Publisher publisherFor(List ids) { return new Publisher() {