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