Merge "Controls API - New method for suggested controls"
diff --git a/api/current.txt b/api/current.txt
index 475506a..9f40c56 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -43305,6 +43305,7 @@
public abstract class ControlsProviderService extends android.app.Service {
ctor public ControlsProviderService();
method public abstract void loadAvailableControls(@NonNull java.util.function.Consumer<java.util.List<android.service.controls.Control>>);
+ method public void loadSuggestedControls(int, @NonNull java.util.function.Consumer<java.util.List<android.service.controls.Control>>);
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<java.lang.Integer>);
method @NonNull public abstract java.util.concurrent.Flow.Publisher<android.service.controls.Control> publisherFor(@NonNull java.util.List<java.lang.String>);
diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java
index bc65818..de4c056 100644
--- a/core/java/android/service/controls/ControlsProviderService.java
+++ b/core/java/android/service/controls/ControlsProviderService.java
@@ -35,6 +35,7 @@
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;
@@ -73,6 +74,18 @@
public abstract void loadAvailableControls(@NonNull Consumer<List<Control>> 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<List<Control>> 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
* is valid.
@@ -104,6 +117,11 @@
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<String> controlIds,
IControlsSubscriber subscriber) {
SubscribeMessage msg = new SubscribeMessage(controlIds, subscriber);
@@ -128,6 +146,14 @@
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 @@
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 @@
};
}
- private Consumer<List<Control>> consumerFor(IControlsLoadCallback cb) {
+ private Consumer<List<Control>> consumerFor(int maxNumber, IControlsLoadCallback cb) {
return (@NonNull List<Control> 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<Control> list = new ArrayList<>();
for (Control control: controls) {
if (control == null) {
@@ -268,4 +307,14 @@
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 4ce658e..4375fbb 100644
--- a/core/java/android/service/controls/IControlsProvider.aidl
+++ b/core/java/android/service/controls/IControlsProvider.aidl
@@ -27,6 +27,8 @@
oneway interface IControlsProvider {
void load(IControlsLoadCallback cb);
+ void loadSuggested(int maxNumber, IControlsLoadCallback cb);
+
void subscribe(in List<String> 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 2648a06..a24b4e0 100644
--- a/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java
+++ b/core/tests/coretests/src/android/service/controls/ControlProviderServiceTest.java
@@ -147,6 +147,34 @@
}
@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<List<Control>> captor = ArgumentCaptor.forClass(List.class);
+
+ ArrayList<Control> 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<Control> 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)
.setTitle("TEST_TITLE")
@@ -216,6 +244,11 @@
}
@Override
+ public void loadSuggestedControls(int maxNumber, Consumer<List<Control>> cb) {
+ cb.accept(mControls);
+ }
+
+ @Override
public Publisher<Control> publisherFor(List<String> ids) {
return new Publisher<Control>() {
public void subscribe(final Subscriber s) {