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 <Vendor Command With ID> do not 147 * include the first 3 bytes (vendor ID). 148 * @param hasVendorId {@code true} if the command type will be <Vendor Command With ID>. 149 * {@code false} if the command will be <Vendor Command> 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