1 package android.hardware.hdmi;
2 
3 import android.annotation.CallbackExecutor;
4 import android.annotation.NonNull;
5 import android.annotation.SuppressLint;
6 import android.annotation.SystemApi;
7 import android.hardware.hdmi.HdmiControlManager.VendorCommandListener;
8 import android.os.Binder;
9 import android.os.RemoteException;
10 import android.util.Log;
11 
12 import java.util.concurrent.Executor;
13 
14 /**
15  * Parent for classes of various HDMI-CEC device type used to access
16  * the HDMI control system service. Contains methods and data used in common.
17  *
18  * @hide
19  */
20 @SystemApi
21 public abstract class HdmiClient {
22     private static final String TAG = "HdmiClient";
23 
24     private static final int UNKNOWN_VENDOR_ID = 0xFFFFFF;
25 
26     /* package */ final IHdmiControlService mService;
27 
28     private IHdmiVendorCommandListener mIHdmiVendorCommandListener;
29 
getDeviceType()30     /* package */ abstract int getDeviceType();
31 
HdmiClient(IHdmiControlService service)32     /* package */ HdmiClient(IHdmiControlService service) {
33         mService = service;
34     }
35 
36     /**
37      * Listener interface used to get the result of {@link #selectDevice}.
38      */
39     public interface OnDeviceSelectedListener {
40         /**
41          * Called when the operation is finished.
42          * @param result the result value of {@link #selectDevice} and can have the values mentioned
43          *               in {@link HdmiControlShellCommand#getResultString}
44          * @param logicalAddress logical address of the selected device
45          */
onDeviceSelected(@dmiControlManager.ControlCallbackResult int result, int logicalAddress)46         void onDeviceSelected(@HdmiControlManager.ControlCallbackResult int result,
47                 int logicalAddress);
48     }
49 
50     /**
51      * Selects a CEC logical device to be a new active source.
52      *
53      * <p> Multiple calls to this method are handled in parallel and independently, with no
54      * guarantees about the execution order. The caller receives a callback for each call,
55      * containing the result of that call only.
56      *
57      * @param logicalAddress logical address of the device to select
58      * @param listener listener to get the result with
59      * @throws {@link IllegalArgumentException} if the {@code listener} is null
60      */
selectDevice( int logicalAddress, @NonNull @CallbackExecutor Executor executor, @NonNull OnDeviceSelectedListener listener)61     public void selectDevice(
62             int logicalAddress,
63             @NonNull @CallbackExecutor Executor executor,
64             @NonNull OnDeviceSelectedListener listener) {
65         if (listener == null) {
66             throw new IllegalArgumentException("listener must not be null.");
67         }
68         if (executor == null) {
69             throw new IllegalArgumentException("executor must not be null.");
70         }
71         try {
72             mService.deviceSelect(logicalAddress,
73                     getCallbackWrapper(logicalAddress, executor, listener));
74         } catch (RemoteException e) {
75             Log.e(TAG, "failed to select device: ", e);
76         }
77     }
78 
79     /**
80      * @hide
81      */
getCallbackWrapper(int logicalAddress, final Executor executor, final OnDeviceSelectedListener listener)82     private static IHdmiControlCallback getCallbackWrapper(int logicalAddress,
83             final Executor executor, final OnDeviceSelectedListener listener) {
84         return new IHdmiControlCallback.Stub() {
85             @Override
86             public void onComplete(int result) {
87                 Binder.withCleanCallingIdentity(
88                         () -> executor.execute(() -> listener.onDeviceSelected(result,
89                                 logicalAddress)));
90             }
91         };
92     }
93 
94     /**
95      * Returns the active source information.
96      *
97      * @return {@link HdmiDeviceInfo} object that describes the active source
98      *         or active routing path
99      */
100     public HdmiDeviceInfo getActiveSource() {
101         try {
102             return mService.getActiveSource();
103         } catch (RemoteException e) {
104             Log.e(TAG, "getActiveSource threw exception ", e);
105         }
106         return null;
107     }
108 
109     /**
110      * Sends a key event to other logical device.
111      *
112      * @param keyCode key code to send. Defined in {@link android.view.KeyEvent}.
113      * @param isPressed true if this is key press event
114      */
115     public void sendKeyEvent(int keyCode, boolean isPressed) {
116         try {
117             mService.sendKeyEvent(getDeviceType(), keyCode, isPressed);
118         } catch (RemoteException e) {
119             Log.e(TAG, "sendKeyEvent threw exception ", e);
120         }
121     }
122 
123     /**
124      * Sends a volume key event to the primary audio receiver in the system. This method should only
125      * be called when the volume key is not handled by the local device. HDMI framework handles the
126      * logic of finding the address of the receiver.
127      *
128      * @param keyCode key code to send. Defined in {@link android.view.KeyEvent}.
129      * @param isPressed true if this is key press event
130      *
131      * @hide
132      */
133     public void sendVolumeKeyEvent(int keyCode, boolean isPressed) {
134         try {
135             mService.sendVolumeKeyEvent(getDeviceType(), keyCode, isPressed);
136         } catch (RemoteException e) {
137             Log.e(TAG, "sendVolumeKeyEvent threw exception ", e);
138             throw e.rethrowFromSystemServer();
139         }
140     }
141 
142     /**
143      * Sends vendor-specific command.
144      *
145      * @param targetAddress address of the target device
146      * @param params vendor-specific parameter. For &lt;Vendor Command With ID&gt; do not
147      *               include the first 3 bytes (vendor ID).
148      * @param hasVendorId {@code true} if the command type will be &lt;Vendor Command With ID&gt;.
149      *                    {@code false} if the command will be &lt;Vendor Command&gt;
150      */
151     public void sendVendorCommand(int targetAddress,
152             @SuppressLint("MissingNullability") byte[] params, boolean hasVendorId) {
153         try {
154             mService.sendVendorCommand(getDeviceType(), targetAddress, params, hasVendorId);
155         } catch (RemoteException e) {
156             Log.e(TAG, "failed to send vendor command: ", e);
157         }
158     }
159 
160     /**
161      * Sets a listener used to receive incoming vendor-specific command. This listener will only
162      * receive {@code <Vendor Command>} but will not receive any {@code <Vendor Command with ID>}
163      * messages.
164      *
165      * @param listener listener object
166      */
167     public void setVendorCommandListener(@NonNull VendorCommandListener listener) {
168         // Set the vendor ID to INVALID_VENDOR_ID.
169         setVendorCommandListener(listener, UNKNOWN_VENDOR_ID);
170     }
171 
172     /**
173      * Sets a listener used to receive incoming vendor-specific command.
174      *
175      * @param listener listener object
176      * @param vendorId The listener is interested in {@code <Vendor Command with ID>} received with
177      *     this vendorId and all {@code <Vendor Command>} messages.
178      */
179     public void setVendorCommandListener(@NonNull VendorCommandListener listener, int vendorId) {
180         if (listener == null) {
181             throw new IllegalArgumentException("listener cannot be null");
182         }
183         if (mIHdmiVendorCommandListener != null) {
184             throw new IllegalStateException("listener was already set");
185         }
186         try {
187             IHdmiVendorCommandListener wrappedListener = getListenerWrapper(listener);
188             mService.addVendorCommandListener(wrappedListener, vendorId);
189             mIHdmiVendorCommandListener = wrappedListener;
190         } catch (RemoteException e) {
191             Log.e(TAG, "failed to set vendor command listener: ", e);
192         }
193     }
194 
195     private static IHdmiVendorCommandListener getListenerWrapper(
196             final VendorCommandListener listener) {
197         return new IHdmiVendorCommandListener.Stub() {
198             @Override
199             public void onReceived(int srcAddress, int destAddress, byte[] params,
200                     boolean hasVendorId) {
201                 listener.onReceived(srcAddress, destAddress, params, hasVendorId);
202             }
203             @Override
204             public void onControlStateChanged(boolean enabled, int reason) {
205                 listener.onControlStateChanged(enabled, reason);
206             }
207         };
208     }
209 }
210