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