Unhide APIs of Android HDMI Framework for Q.

Test: manual
Bug: 110094868
Change-Id: I4f8d992a1dc7be7e52710153d5d093ca2b82abd6
diff --git a/api/system-current.txt b/api/system-current.txt
index 5bdcd23..59c21ba 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1810,9 +1810,15 @@
   public final class HdmiControlManager {
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void addHotplugEventListener(android.hardware.hdmi.HdmiControlManager.HotplugEventListener);
     method @Nullable public android.hardware.hdmi.HdmiClient getClient(int);
+    method @Nullable public java.util.List<android.hardware.hdmi.HdmiDeviceInfo> getConnectedDevicesList();
+    method public int getPhysicalAddress();
     method @Nullable public android.hardware.hdmi.HdmiPlaybackClient getPlaybackClient();
+    method @Nullable public android.hardware.hdmi.HdmiSwitchClient getSwitchClient();
     method @Nullable public android.hardware.hdmi.HdmiTvClient getTvClient();
+    method public boolean isRemoteDeviceConnected(android.hardware.hdmi.HdmiDeviceInfo);
+    method public void powerOffRemoteDevice(android.hardware.hdmi.HdmiDeviceInfo);
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void removeHotplugEventListener(android.hardware.hdmi.HdmiControlManager.HotplugEventListener);
+    method public void requestRemoteDeviceToBecomeActiveSource(android.hardware.hdmi.HdmiDeviceInfo);
     method @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void setStandbyMode(boolean);
     field public static final String ACTION_OSD_MESSAGE = "android.hardware.hdmi.action.OSD_MESSAGE";
     field public static final int AVR_VOLUME_MUTED = 101; // 0x65
@@ -1902,6 +1908,9 @@
     field public static final int TIMER_STATUS_PROGRAMMED_INFO_NO_MEDIA_INFO = 10; // 0xa
   }
 
+  @IntDef({android.hardware.hdmi.HdmiControlManager.RESULT_SUCCESS, android.hardware.hdmi.HdmiControlManager.RESULT_TIMEOUT, android.hardware.hdmi.HdmiControlManager.RESULT_SOURCE_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE, android.hardware.hdmi.HdmiControlManager.RESULT_ALREADY_IN_PROGRESS, android.hardware.hdmi.HdmiControlManager.RESULT_EXCEPTION, android.hardware.hdmi.HdmiControlManager.RESULT_INCORRECT_MODE, android.hardware.hdmi.HdmiControlManager.RESULT_COMMUNICATION_FAILED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public static @interface HdmiControlManager.ControlCallbackResult {
+  }
+
   public static interface HdmiControlManager.HotplugEventListener {
     method public void onReceived(android.hardware.hdmi.HdmiHotplugEvent);
   }
@@ -2028,6 +2037,16 @@
   public abstract static class HdmiRecordSources.RecordSource {
   }
 
+  public class HdmiSwitchClient extends android.hardware.hdmi.HdmiClient {
+    method public int getDeviceType();
+    method public void selectPort(int, @NonNull android.hardware.hdmi.HdmiSwitchClient.OnSelectListener);
+    method public void selectPort(int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.hdmi.HdmiSwitchClient.OnSelectListener);
+  }
+
+  public static interface HdmiSwitchClient.OnSelectListener {
+    method public void onSelect(@android.hardware.hdmi.HdmiControlManager.ControlCallbackResult int);
+  }
+
   public class HdmiTimerRecordSources {
     method public static boolean checkTimerRecordSource(int, byte[]);
     method public static android.hardware.hdmi.HdmiTimerRecordSources.Duration durationOf(int, int);
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index a98b31a..56020b2 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -18,6 +18,7 @@
 
 import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.RequiresFeature;
 import android.annotation.RequiresPermission;
@@ -33,6 +34,8 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.List;
 
 /**
@@ -106,9 +109,24 @@
     public static final int POWER_STATUS_TRANSIENT_TO_ON = 2;
     public static final int POWER_STATUS_TRANSIENT_TO_STANDBY = 3;
 
+    @IntDef ({
+        RESULT_SUCCESS,
+        RESULT_TIMEOUT,
+        RESULT_SOURCE_NOT_AVAILABLE,
+        RESULT_TARGET_NOT_AVAILABLE,
+        RESULT_ALREADY_IN_PROGRESS,
+        RESULT_EXCEPTION,
+        RESULT_INCORRECT_MODE,
+        RESULT_COMMUNICATION_FAILED,
+    })
+    public @interface ControlCallbackResult {}
+
+    /** Control operation is successfully handled by the framework. */
     public static final int RESULT_SUCCESS = 0;
     public static final int RESULT_TIMEOUT = 1;
+    /** Source device that the application is using is not available. */
     public static final int RESULT_SOURCE_NOT_AVAILABLE = 2;
+    /** Target device that the application is controlling is not available. */
     public static final int RESULT_TARGET_NOT_AVAILABLE = 3;
 
     @Deprecated public static final int RESULT_ALREADY_IN_PROGRESS = 4;
@@ -394,16 +412,15 @@
     /**
      * Gets an object that represents an HDMI-CEC logical device of type switch on the system.
      *
-     * <p>Used to send HDMI control messages to other devices like TV through HDMI bus. It is also
-     * possible to communicate with other logical devices hosted in the same system if the system is
-     * configured to host more than one type of HDMI-CEC logical devices.
+     * <p>Used to send HDMI control messages to other devices (e.g. TVs) through HDMI bus.
+     * It is also possible to communicate with other logical devices hosted in the same
+     * system if the system is configured to host more than one type of HDMI-CEC logical device.
      *
      * @return {@link HdmiSwitchClient} instance. {@code null} on failure.
-     *
-     * TODO(b/110094868): unhide for Q
      * @hide
      */
     @Nullable
+    @SystemApi
     @SuppressLint("Doclava125")
     public HdmiSwitchClient getSwitchClient() {
         return (HdmiSwitchClient) getClient(HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH);
@@ -412,11 +429,15 @@
     /**
      * Get a snapshot of the real-time status of the remote devices.
      *
-     * @return a list of {@link HdmiDeviceInfo} of the devices connected to the current device.
+     * <p>This only applies to devices with multiple HDMI inputs.
      *
-     * TODO(b/110094868): unhide for Q
+     * @return a list of {@link HdmiDeviceInfo} of the connected CEC devices. An empty
+     * list will be returned if there is none.
+     *
      * @hide
      */
+    @SystemApi
+    @Nullable
     public List<HdmiDeviceInfo> getConnectedDevicesList() {
         try {
             return mService.getDeviceList();
@@ -426,14 +447,17 @@
     }
 
     /**
-     * Power off the target device.
+     * Power off the target device by sending CEC commands.
      *
-     * @param deviceInfo HdmiDeviceInfo of the device to be powered off
+     * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}.
      *
-     * TODO(b/110094868): unhide for Q
+     * @param deviceInfo {@link HdmiDeviceInfo} of the device to be powered off.
+     *
      * @hide
      */
+    @SystemApi
     public void powerOffRemoteDevice(HdmiDeviceInfo deviceInfo) {
+        Preconditions.checkNotNull(deviceInfo);
         try {
             mService.powerOffRemoteDevice(
                     deviceInfo.getLogicalAddress(), deviceInfo.getDevicePowerStatus());
@@ -443,14 +467,16 @@
     }
 
     /**
-     * Power on the target device.
+     * Power on the target device by sending CEC commands.
      *
-     * @param deviceInfo HdmiDeviceInfo of the device to be powered on
+     * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}.
      *
-     * TODO(b/110094868): unhide for Q
+     * @param deviceInfo {@link HdmiDeviceInfo} of the device to be powered on.
+     *
      * @hide
      */
     public void powerOnRemoteDevice(HdmiDeviceInfo deviceInfo) {
+        Preconditions.checkNotNull(deviceInfo);
         try {
             mService.powerOnRemoteDevice(
                     deviceInfo.getLogicalAddress(), deviceInfo.getDevicePowerStatus());
@@ -460,14 +486,17 @@
     }
 
     /**
-     * Ask the target device to be the new Active Source.
+     * Request the target device to be the new Active Source by sending CEC commands.
+     *
+     * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}.
      *
      * @param deviceInfo HdmiDeviceInfo of the target device
      *
-     * TODO(b/110094868): unhide for Q
      * @hide
      */
-    public void askRemoteDeviceToBecomeActiveSource(HdmiDeviceInfo deviceInfo) {
+    @SystemApi
+    public void requestRemoteDeviceToBecomeActiveSource(HdmiDeviceInfo deviceInfo) {
+        Preconditions.checkNotNull(deviceInfo);
         try {
             mService.askRemoteDeviceToBecomeActiveSource(deviceInfo.getPhysicalAddress());
         } catch (RemoteException e) {
@@ -506,8 +535,13 @@
     /**
      * Get the physical address of the device.
      *
+     * <p>Physical address needs to be automatically adjusted when devices are phyiscally or
+     * electrically added or removed from the device tree. Please see HDMI Specification Version
+     * 1.4b 8.7 Physical Address for more details on the address discovery proccess.
+     *
      * @hide
      */
+    @SystemApi
     public int getPhysicalAddress() {
         if (mPhysicalAddress != INVALID_PHYSICAL_ADDRESS) {
             return mPhysicalAddress;
@@ -521,16 +555,19 @@
     }
 
     /**
-     * Check if the target device is connected to the current device. The
-     * API also returns true if the current device is the target.
+     * Check if the target remote device is connected to the current device.
+     *
+     * <p>The API also returns true if the current device is the target.
      *
      * @param targetDevice {@link HdmiDeviceInfo} of the target device.
-     * @return true if {@code device} is directly or indirectly connected to the
+     * @return true if {@code targetDevice} is directly or indirectly
+     * connected to the current device.
      *
-     * TODO(b/110094868): unhide for Q
      * @hide
      */
-    public boolean isTargetDeviceConnected(HdmiDeviceInfo targetDevice) {
+    @SystemApi
+    public boolean isRemoteDeviceConnected(HdmiDeviceInfo targetDevice) {
+        Preconditions.checkNotNull(targetDevice);
         mPhysicalAddress = getPhysicalAddress();
         if (mPhysicalAddress == INVALID_PHYSICAL_ADDRESS) {
             return false;
diff --git a/core/java/android/hardware/hdmi/HdmiSwitchClient.java b/core/java/android/hardware/hdmi/HdmiSwitchClient.java
index 1ac2973..a036512 100644
--- a/core/java/android/hardware/hdmi/HdmiSwitchClient.java
+++ b/core/java/android/hardware/hdmi/HdmiSwitchClient.java
@@ -15,24 +15,30 @@
  */
 package android.hardware.hdmi;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.hardware.hdmi.HdmiControlManager.ControlCallbackResult;
+import android.os.Binder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.internal.util.Preconditions;
+
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
- * HdmiSwitchClient represents HDMI-CEC logical device of type Switch in the Android system which
- * acts as switch.
+ * An {@code HdmiSwitchClient} represents a HDMI-CEC switch device.
  *
- * <p>HdmiSwitchClient has a CEC device type of HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH,
- * but it is used by all Android TV devices that have multiple HDMI inputs,
- * even if it is not a "pure" swicth and has another device type like TV or Player.
+ * <p>HdmiSwitchClient has a CEC device type of HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH, but it is
+ * used by all Android devices that have multiple HDMI inputs, even if it is not a "pure" swicth
+ * and has another device type like TV or Player.
  *
  * @hide
- * TODO(b/110094868): unhide and add @SystemApi for Q
  */
+@SystemApi
 public class HdmiSwitchClient extends HdmiClient {
 
     private static final String TAG = "HdmiSwitchClient";
@@ -41,17 +47,15 @@
         super(service);
     }
 
-    private static IHdmiControlCallback getCallbackWrapper(final SelectCallback callback) {
+    private static IHdmiControlCallback getCallbackWrapper(final OnSelectListener listener) {
         return new IHdmiControlCallback.Stub() {
             @Override
             public void onComplete(int result) {
-                callback.onComplete(result);
+                listener.onSelect(result);
             }
         };
     }
 
-    /** @hide */
-    // TODO(b/110094868): unhide for Q
     @Override
     public int getDeviceType() {
         return HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH;
@@ -61,20 +65,17 @@
      * Selects a CEC logical device to be a new active source.
      *
      * @param logicalAddress logical address of the device to select
-     * @param callback callback to get the result with
-     * @throws {@link IllegalArgumentException} if the {@code callback} is null
+     * @param listener listener to get the result with
      *
      * @hide
-     * TODO(b/110094868): unhide and add @SystemApi for Q
      */
-    public void deviceSelect(int logicalAddress, @NonNull SelectCallback callback) {
-        if (callback == null) {
-            throw new IllegalArgumentException("callback must not be null.");
-        }
+    public void selectDevice(int logicalAddress, @NonNull OnSelectListener listener) {
+        Preconditions.checkNotNull(listener);
         try {
-            mService.deviceSelect(logicalAddress, getCallbackWrapper(callback));
+            mService.deviceSelect(logicalAddress, getCallbackWrapper(listener));
         } catch (RemoteException e) {
             Log.e(TAG, "failed to select device: ", e);
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -82,20 +83,83 @@
      * Selects a HDMI port to be a new route path.
      *
      * @param portId HDMI port to select
-     * @param callback callback to get the result with
-     * @throws {@link IllegalArgumentException} if the {@code callback} is null
+     * @see {@link android.media.tv.TvInputHardwareInfo#getHdmiPortId()}
+     *     to get portId of a specific TV Input.
+     * @param listener listener to get the result with
      *
      * @hide
-     * TODO(b/110094868): unhide and add @SystemApi for Q
      */
-    public void portSelect(int portId, @NonNull SelectCallback callback) {
-        if (callback == null) {
-            throw new IllegalArgumentException("Callback must not be null");
-        }
+    @SystemApi
+    public void selectPort(int portId, @NonNull OnSelectListener listener) {
+        Preconditions.checkNotNull(listener);
         try {
-            mService.portSelect(portId, getCallbackWrapper(callback));
+            mService.portSelect(portId, getCallbackWrapper(listener));
         } catch (RemoteException e) {
             Log.e(TAG, "failed to select port: ", e);
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Selects a CEC logical device to be a new active source.
+     *
+     * @param logicalAddress logical address of the device to select
+     * @param executor executor to allow the develper to specify the thread upon which the listeners
+     *     will be invoked
+     * @param listener listener to get the result with
+     *
+     * @hide
+     */
+    public void selectDevice(
+            int logicalAddress,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnSelectListener listener) {
+        Preconditions.checkNotNull(listener);
+        try {
+            mService.deviceSelect(logicalAddress,
+                    new IHdmiControlCallback.Stub() {
+                            @Override
+                            public void onComplete(int result) {
+                                Binder.withCleanCallingIdentity(
+                                        () -> executor.execute(() -> listener.onSelect(result)));
+                            }
+                    }
+            );
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to select device: ", e);
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Selects a HDMI port to be a new route path.
+     *
+     * @param portId HDMI port to select
+     * @param executor executor to allow the develper to specify the thread upon which the listeners
+     *     will be invoked
+     * @param listener listener to get the result with
+     *
+     * @hide
+     */
+    @SystemApi
+    public void selectPort(
+            int portId,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnSelectListener listener) {
+        Preconditions.checkNotNull(listener);
+        try {
+            mService.portSelect(portId,
+                    new IHdmiControlCallback.Stub() {
+                            @Override
+                            public void onComplete(int result) {
+                                Binder.withCleanCallingIdentity(
+                                        () -> executor.execute(() -> listener.onSelect(result)));
+                            }
+                    }
+            );
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to select port: ", e);
+            throw e.rethrowFromSystemServer();
         }
     }
 
@@ -105,10 +169,9 @@
      * <p>This only applies to device with multiple HDMI inputs
      *
      * @return list of {@link HdmiDeviceInfo} for connected CEC devices. Empty list is returned if
-     * there is none.
+     *     there is none.
      *
      * @hide
-     * TODO(b/110094868): unhide and add @SystemApi for Q
      */
     public List<HdmiDeviceInfo> getDeviceList() {
         try {
@@ -120,18 +183,19 @@
     }
 
     /**
-     * Callback interface used to get the result of {@link #deviceSelect} or {@link #portSelect}.
+     * Listener interface used to get the result of {@link #deviceSelect} or {@link #portSelect}.
      *
      * @hide
-     * TODO(b/110094868): unhide and add @SystemApi for Q
      */
-    public interface SelectCallback {
+    @SystemApi
+    public interface OnSelectListener {
 
         /**
          * Called when the operation is finished.
          *
-         * @param result the result value of {@link #deviceSelect} or {@link #portSelect}.
+         * @param result callback result.
+         * @see {@link ControlCallbackResult}
          */
-        void onComplete(int result);
+        void onSelect(@ControlCallbackResult int result);
     }
 }