1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package android.hardware.hdmi; 17 18 import android.annotation.NonNull; 19 import android.annotation.SystemApi; 20 import android.hardware.hdmi.HdmiRecordSources.RecordSource; 21 import android.hardware.hdmi.HdmiTimerRecordSources.TimerRecordSource; 22 import android.os.RemoteException; 23 import android.util.Log; 24 25 import libcore.util.EmptyArray; 26 27 import java.util.Collections; 28 import java.util.List; 29 30 /** 31 * HdmiTvClient represents HDMI-CEC logical device of type TV in the Android system 32 * which acts as TV/Display. 33 * 34 * <p>HdmiTvClient provides methods that manage, interact with other devices on the CEC bus. 35 * 36 * @hide 37 */ 38 @SystemApi 39 public final class HdmiTvClient extends HdmiClient { 40 private static final String TAG = "HdmiTvClient"; 41 42 /** 43 * Size of MHL register for vendor command 44 */ 45 public static final int VENDOR_DATA_SIZE = 16; 46 HdmiTvClient(IHdmiControlService service)47 /* package */ HdmiTvClient(IHdmiControlService service) { 48 super(service); 49 } 50 51 // Factory method for HdmiTvClient. 52 // Declared package-private. Accessed by HdmiControlManager only. create(IHdmiControlService service)53 /* package */ static HdmiTvClient create(IHdmiControlService service) { 54 return new HdmiTvClient(service); 55 } 56 57 @Override getDeviceType()58 public int getDeviceType() { 59 return HdmiDeviceInfo.DEVICE_TV; 60 } 61 62 /** 63 * Callback interface used to get the result of {@link #portSelect} and 64 * {@link #setSystemAudioMode}. 65 */ 66 public interface SelectCallback { 67 /** 68 * Called when the operation is finished. 69 * 70 * @param result the result value of {@link #deviceSelect} 71 */ onComplete(int result)72 void onComplete(int result); 73 } 74 75 /** 76 * Selects a CEC logical device to be a new active source. 77 * 78 * @param logicalAddress logical address of the device to select 79 * @param callback callback to get the result with 80 * @throws {@link IllegalArgumentException} if the {@code callback} is null 81 * @deprecated Please use {@link HdmiClient#selectDevice} instead. 82 */ 83 @Deprecated deviceSelect(int logicalAddress, @NonNull SelectCallback callback)84 public void deviceSelect(int logicalAddress, @NonNull SelectCallback callback) { 85 if (callback == null) { 86 throw new IllegalArgumentException("callback must not be null."); 87 } 88 try { 89 mService.deviceSelect(logicalAddress, getCallbackWrapper(callback)); 90 } catch (RemoteException e) { 91 Log.e(TAG, "failed to select device: ", e); 92 } 93 } 94 getCallbackWrapper(final SelectCallback callback)95 private static IHdmiControlCallback getCallbackWrapper(final SelectCallback callback) { 96 return new IHdmiControlCallback.Stub() { 97 @Override 98 public void onComplete(int result) { 99 callback.onComplete(result); 100 } 101 }; 102 } 103 104 /** 105 * Selects a HDMI port to be a new route path. 106 * 107 * @param portId HDMI port to select 108 * @param callback callback to get the result with 109 * @throws {@link IllegalArgumentException} if the {@code callback} is null 110 */ 111 public void portSelect(int portId, @NonNull SelectCallback callback) { 112 if (callback == null) { 113 throw new IllegalArgumentException("Callback must not be null"); 114 } 115 try { 116 mService.portSelect(portId, getCallbackWrapper(callback)); 117 } catch (RemoteException e) { 118 Log.e(TAG, "failed to select port: ", e); 119 } 120 } 121 122 /** 123 * Callback interface used to get the input change event. 124 */ 125 public interface InputChangeListener { 126 /** 127 * Called when the input was changed. 128 * 129 * @param info newly selected HDMI input 130 */ 131 void onChanged(HdmiDeviceInfo info); 132 } 133 134 /** 135 * Sets the listener used to get informed of the input change event. 136 * 137 * @param listener listener object 138 */ 139 public void setInputChangeListener(InputChangeListener listener) { 140 if (listener == null) { 141 throw new IllegalArgumentException("listener must not be null."); 142 } 143 try { 144 mService.setInputChangeListener(getListenerWrapper(listener)); 145 } catch (RemoteException e) { 146 Log.e("TAG", "Failed to set InputChangeListener:", e); 147 } 148 } 149 150 private static IHdmiInputChangeListener getListenerWrapper(final InputChangeListener listener) { 151 return new IHdmiInputChangeListener.Stub() { 152 @Override 153 public void onChanged(HdmiDeviceInfo info) { 154 listener.onChanged(info); 155 } 156 }; 157 } 158 159 /** 160 * Returns all the CEC devices connected to TV. 161 * 162 * @return list of {@link HdmiDeviceInfo} for connected CEC devices. 163 * Empty list is returned if there is none. 164 * @deprecated Please use {@link HdmiControlManager#getConnectedDevices()} instead. 165 */ 166 @Deprecated 167 public List<HdmiDeviceInfo> getDeviceList() { 168 try { 169 return mService.getDeviceList(); 170 } catch (RemoteException e) { 171 Log.e("TAG", "Failed to call getDeviceList():", e); 172 return Collections.<HdmiDeviceInfo>emptyList(); 173 } 174 } 175 176 /** 177 * Sets system audio mode. 178 * 179 * @param enabled set to {@code true} to enable the mode; otherwise {@code false} 180 * @param callback callback to get the result with 181 * @throws {@link IllegalArgumentException} if the {@code callback} is null 182 */ 183 public void setSystemAudioMode(boolean enabled, SelectCallback callback) { 184 try { 185 mService.setSystemAudioMode(enabled, getCallbackWrapper(callback)); 186 } catch (RemoteException e) { 187 Log.e(TAG, "failed to set system audio mode:", e); 188 } 189 } 190 191 /** 192 * Sets system audio volume. 193 * 194 * @param oldIndex current volume index 195 * @param newIndex volume index to be set 196 * @param maxIndex maximum volume index 197 */ 198 public void setSystemAudioVolume(int oldIndex, int newIndex, int maxIndex) { 199 try { 200 mService.setSystemAudioVolume(oldIndex, newIndex, maxIndex); 201 } catch (RemoteException e) { 202 Log.e(TAG, "failed to set volume: ", e); 203 } 204 } 205 206 /** 207 * Sets system audio mute status. 208 * 209 * @param mute {@code true} if muted; otherwise, {@code false} 210 */ 211 public void setSystemAudioMute(boolean mute) { 212 try { 213 mService.setSystemAudioMute(mute); 214 } catch (RemoteException e) { 215 Log.e(TAG, "failed to set mute: ", e); 216 } 217 } 218 219 /** 220 * Sets record listener. 221 * 222 * @param listener 223 */ 224 public void setRecordListener(@NonNull HdmiRecordListener listener) { 225 if (listener == null) { 226 throw new IllegalArgumentException("listener must not be null."); 227 } 228 try { 229 mService.setHdmiRecordListener(getListenerWrapper(listener)); 230 } catch (RemoteException e) { 231 Log.e(TAG, "failed to set record listener.", e); 232 } 233 } 234 235 /** 236 * Sends a <Standby> command to other device. 237 * 238 * @param deviceId device id to send the command to 239 */ 240 public void sendStandby(int deviceId) { 241 try { 242 mService.sendStandby(getDeviceType(), deviceId); 243 } catch (RemoteException e) { 244 Log.e(TAG, "sendStandby threw exception ", e); 245 } 246 } 247 248 private static IHdmiRecordListener getListenerWrapper(final HdmiRecordListener callback) { 249 return new IHdmiRecordListener.Stub() { 250 @Override 251 public byte[] getOneTouchRecordSource(int recorderAddress) { 252 HdmiRecordSources.RecordSource source = 253 callback.onOneTouchRecordSourceRequested(recorderAddress); 254 if (source == null) { 255 return EmptyArray.BYTE; 256 } 257 byte[] data = new byte[source.getDataSize(true)]; 258 source.toByteArray(true, data, 0); 259 return data; 260 } 261 262 @Override 263 public void onOneTouchRecordResult(int recorderAddress, int result) { 264 callback.onOneTouchRecordResult(recorderAddress, result); 265 } 266 267 @Override 268 public void onTimerRecordingResult(int recorderAddress, int result) { 269 callback.onTimerRecordingResult(recorderAddress, 270 HdmiRecordListener.TimerStatusData.parseFrom(result)); 271 } 272 273 @Override 274 public void onClearTimerRecordingResult(int recorderAddress, int result) { 275 callback.onClearTimerRecordingResult(recorderAddress, result); 276 } 277 }; 278 } 279 280 /** 281 * Starts one touch recording with the given recorder address and recorder source. 282 * <p> 283 * Usage 284 * <pre> 285 * HdmiTvClient tvClient = ....; 286 * // for own source. 287 * OwnSource ownSource = HdmiRecordSources.ofOwnSource(); 288 * tvClient.startOneTouchRecord(recorderAddress, ownSource); 289 * </pre> 290 */ 291 public void startOneTouchRecord(int recorderAddress, @NonNull RecordSource source) { 292 if (source == null) { 293 throw new IllegalArgumentException("source must not be null."); 294 } 295 296 try { 297 byte[] data = new byte[source.getDataSize(true)]; 298 source.toByteArray(true, data, 0); 299 mService.startOneTouchRecord(recorderAddress, data); 300 } catch (RemoteException e) { 301 Log.e(TAG, "failed to start record: ", e); 302 } 303 } 304 305 /** 306 * Stops one touch record. 307 * 308 * @param recorderAddress recorder address where recoding will be stopped 309 */ 310 public void stopOneTouchRecord(int recorderAddress) { 311 try { 312 mService.stopOneTouchRecord(recorderAddress); 313 } catch (RemoteException e) { 314 Log.e(TAG, "failed to stop record: ", e); 315 } 316 } 317 318 /** 319 * Starts timer recording with the given recoder address and recorder source. 320 * <p> 321 * Usage 322 * <pre> 323 * HdmiTvClient tvClient = ....; 324 * // create timer info 325 * TimerInfo timerInfo = HdmiTimerRecourdSources.timerInfoOf(...); 326 * // for digital source. 327 * DigitalServiceSource recordSource = HdmiRecordSources.ofDigitalService(...); 328 * // create timer recording source. 329 * TimerRecordSource source = HdmiTimerRecourdSources.ofDigitalSource(timerInfo, recordSource); 330 * tvClient.startTimerRecording(recorderAddress, source); 331 * </pre> 332 * 333 * @param recorderAddress target recorder address 334 * @param sourceType type of record source. It should be one of 335 * {@link HdmiControlManager#TIMER_RECORDING_TYPE_DIGITAL}, 336 * {@link HdmiControlManager#TIMER_RECORDING_TYPE_ANALOGUE}, 337 * {@link HdmiControlManager#TIMER_RECORDING_TYPE_EXTERNAL}. 338 * @param source record source to be used 339 */ 340 public void startTimerRecording(int recorderAddress, int sourceType, TimerRecordSource source) { 341 if (source == null) { 342 throw new IllegalArgumentException("source must not be null."); 343 } 344 345 checkTimerRecordingSourceType(sourceType); 346 347 try { 348 byte[] data = new byte[source.getDataSize()]; 349 source.toByteArray(data, 0); 350 mService.startTimerRecording(recorderAddress, sourceType, data); 351 } catch (RemoteException e) { 352 Log.e(TAG, "failed to start record: ", e); 353 } 354 } 355 356 private void checkTimerRecordingSourceType(int sourceType) { 357 switch (sourceType) { 358 case HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL: 359 case HdmiControlManager.TIMER_RECORDING_TYPE_ANALOGUE: 360 case HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL: 361 break; 362 default: 363 throw new IllegalArgumentException("Invalid source type:" + sourceType); 364 } 365 } 366 367 /** 368 * Clears timer recording with the given recorder address and recording source. 369 * For more details, please refer {@link #startTimerRecording(int, int, TimerRecordSource)}. 370 */ 371 public void clearTimerRecording(int recorderAddress, int sourceType, TimerRecordSource source) { 372 if (source == null) { 373 throw new IllegalArgumentException("source must not be null."); 374 } 375 376 checkTimerRecordingSourceType(sourceType); 377 try { 378 byte[] data = new byte[source.getDataSize()]; 379 source.toByteArray(data, 0); 380 mService.clearTimerRecording(recorderAddress, sourceType, data); 381 } catch (RemoteException e) { 382 Log.e(TAG, "failed to start record: ", e); 383 } 384 } 385 386 /** 387 * Interface used to get incoming MHL vendor command. 388 */ 389 public interface HdmiMhlVendorCommandListener { 390 void onReceived(int portId, int offset, int length, byte[] data); 391 } 392 393 /** 394 * Sets {@link HdmiMhlVendorCommandListener} to get incoming MHL vendor command. 395 * 396 * @param listener to receive incoming MHL vendor command 397 */ 398 public void setHdmiMhlVendorCommandListener(HdmiMhlVendorCommandListener listener) { 399 if (listener == null) { 400 throw new IllegalArgumentException("listener must not be null."); 401 } 402 try { 403 mService.addHdmiMhlVendorCommandListener(getListenerWrapper(listener)); 404 } catch (RemoteException e) { 405 Log.e(TAG, "failed to set hdmi mhl vendor command listener: ", e); 406 } 407 } 408 409 private IHdmiMhlVendorCommandListener getListenerWrapper( 410 final HdmiMhlVendorCommandListener listener) { 411 return new IHdmiMhlVendorCommandListener.Stub() { 412 @Override 413 public void onReceived(int portId, int offset, int length, byte[] data) { 414 listener.onReceived(portId, offset, length, data); 415 } 416 }; 417 } 418 419 /** 420 * Sends MHL vendor command to the device connected to a port of the given portId. 421 * 422 * @param portId id of port to send MHL vendor command 423 * @param offset offset in the in given data 424 * @param length length of data. offset + length should be bound to length of data. 425 * @param data container for vendor command data. It should be 16 bytes. 426 * @throws IllegalArgumentException if the given parameters are invalid 427 */ 428 public void sendMhlVendorCommand(int portId, int offset, int length, byte[] data) { 429 if (data == null || data.length != VENDOR_DATA_SIZE) { 430 throw new IllegalArgumentException("Invalid vendor command data."); 431 } 432 if (offset < 0 || offset >= VENDOR_DATA_SIZE) { 433 throw new IllegalArgumentException("Invalid offset:" + offset); 434 } 435 if (length < 0 || offset + length > VENDOR_DATA_SIZE) { 436 throw new IllegalArgumentException("Invalid length:" + length); 437 } 438 439 try { 440 mService.sendMhlVendorCommand(portId, offset, length, data); 441 } catch (RemoteException e) { 442 Log.e(TAG, "failed to send vendor command: ", e); 443 } 444 } 445 } 446