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 HdmiTvClient(IHdmiControlService service)46 /* package */ HdmiTvClient(IHdmiControlService service) { 47 super(service); 48 } 49 50 // Factory method for HdmiTvClient. 51 // Declared package-private. Accessed by HdmiControlManager only. create(IHdmiControlService service)52 /* package */ static HdmiTvClient create(IHdmiControlService service) { 53 return new HdmiTvClient(service); 54 } 55 56 @Override getDeviceType()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 */ onComplete(int result)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 */ deviceSelect(int logicalAddress, @NonNull SelectCallback callback)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 getCallbackWrapper(final SelectCallback callback)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 volume 172 * 173 * @param oldIndex current volume index 174 * @param newIndex volume index to be set 175 * @param maxIndex maximum volume index 176 */ 177 public void setSystemAudioVolume(int oldIndex, int newIndex, int maxIndex) { 178 try { 179 mService.setSystemAudioVolume(oldIndex, newIndex, maxIndex); 180 } catch (RemoteException e) { 181 Log.e(TAG, "failed to set volume: ", e); 182 } 183 } 184 185 /** 186 * Sets system audio mute status 187 * 188 * @param mute {@code true} if muted; otherwise, {@code false} 189 */ 190 public void setSystemAudioMute(boolean mute) { 191 try { 192 mService.setSystemAudioMute(mute); 193 } catch (RemoteException e) { 194 Log.e(TAG, "failed to set mute: ", e); 195 } 196 } 197 198 /** 199 * Sets record listener 200 * 201 * @param listener 202 */ 203 public void setRecordListener(@NonNull HdmiRecordListener listener) { 204 if (listener == null) { 205 throw new IllegalArgumentException("listener must not be null."); 206 } 207 try { 208 mService.setHdmiRecordListener(getListenerWrapper(listener)); 209 } catch (RemoteException e) { 210 Log.e(TAG, "failed to set record listener.", e); 211 } 212 } 213 214 private static IHdmiRecordListener getListenerWrapper(final HdmiRecordListener callback) { 215 return new IHdmiRecordListener.Stub() { 216 @Override 217 public byte[] getOneTouchRecordSource(int recorderAddress) { 218 HdmiRecordSources.RecordSource source = 219 callback.onOneTouchRecordSourceRequested(recorderAddress); 220 if (source == null) { 221 return EmptyArray.BYTE; 222 } 223 byte[] data = new byte[source.getDataSize(true)]; 224 source.toByteArray(true, data, 0); 225 return data; 226 } 227 228 @Override 229 public void onOneTouchRecordResult(int recorderAddress, int result) { 230 callback.onOneTouchRecordResult(recorderAddress, result); 231 } 232 233 @Override 234 public void onTimerRecordingResult(int recorderAddress, int result) { 235 callback.onTimerRecordingResult(recorderAddress, 236 HdmiRecordListener.TimerStatusData.parseFrom(result)); 237 } 238 239 @Override 240 public void onClearTimerRecordingResult(int recorderAddress, int result) { 241 callback.onClearTimerRecordingResult(recorderAddress, result); 242 } 243 }; 244 } 245 246 /** 247 * Starts one touch recording with the given recorder address and recorder source. 248 * <p> 249 * Usage 250 * <pre> 251 * HdmiTvClient tvClient = ....; 252 * // for own source. 253 * OwnSource ownSource = HdmiRecordSources.ofOwnSource(); 254 * tvClient.startOneTouchRecord(recorderAddress, ownSource); 255 * </pre> 256 */ 257 public void startOneTouchRecord(int recorderAddress, @NonNull RecordSource source) { 258 if (source == null) { 259 throw new IllegalArgumentException("source must not be null."); 260 } 261 262 try { 263 byte[] data = new byte[source.getDataSize(true)]; 264 source.toByteArray(true, data, 0); 265 mService.startOneTouchRecord(recorderAddress, data); 266 } catch (RemoteException e) { 267 Log.e(TAG, "failed to start record: ", e); 268 } 269 } 270 271 /** 272 * Stops one touch record. 273 * 274 * @param recorderAddress recorder address where recoding will be stopped 275 */ 276 public void stopOneTouchRecord(int recorderAddress) { 277 try { 278 mService.stopOneTouchRecord(recorderAddress); 279 } catch (RemoteException e) { 280 Log.e(TAG, "failed to stop record: ", e); 281 } 282 } 283 284 /** 285 * Starts timer recording with the given recoder address and recorder source. 286 * <p> 287 * Usage 288 * <pre> 289 * HdmiTvClient tvClient = ....; 290 * // create timer info 291 * TimerInfo timerInfo = HdmiTimerRecourdSources.timerInfoOf(...); 292 * // for digital source. 293 * DigitalServiceSource recordSource = HdmiRecordSources.ofDigitalService(...); 294 * // create timer recording source. 295 * TimerRecordSource source = HdmiTimerRecourdSources.ofDigitalSource(timerInfo, recordSource); 296 * tvClient.startTimerRecording(recorderAddress, source); 297 * </pre> 298 * 299 * @param recorderAddress target recorder address 300 * @param sourceType type of record source. It should be one of 301 * {@link HdmiControlManager#TIMER_RECORDING_TYPE_DIGITAL}, 302 * {@link HdmiControlManager#TIMER_RECORDING_TYPE_ANALOGUE}, 303 * {@link HdmiControlManager#TIMER_RECORDING_TYPE_EXTERNAL}. 304 * @param source record source to be used 305 */ 306 public void startTimerRecording(int recorderAddress, int sourceType, TimerRecordSource source) { 307 if (source == null) { 308 throw new IllegalArgumentException("source must not be null."); 309 } 310 311 checkTimerRecordingSourceType(sourceType); 312 313 try { 314 byte[] data = new byte[source.getDataSize()]; 315 source.toByteArray(data, 0); 316 mService.startTimerRecording(recorderAddress, sourceType, data); 317 } catch (RemoteException e) { 318 Log.e(TAG, "failed to start record: ", e); 319 } 320 } 321 322 private void checkTimerRecordingSourceType(int sourceType) { 323 switch (sourceType) { 324 case HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL: 325 case HdmiControlManager.TIMER_RECORDING_TYPE_ANALOGUE: 326 case HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL: 327 break; 328 default: 329 throw new IllegalArgumentException("Invalid source type:" + sourceType); 330 } 331 } 332 333 /** 334 * Clears timer recording with the given recorder address and recording source. 335 * For more details, please refer {@link #startTimerRecording(int, int, TimerRecordSource)}. 336 */ 337 public void clearTimerRecording(int recorderAddress, int sourceType, TimerRecordSource source) { 338 if (source == null) { 339 throw new IllegalArgumentException("source must not be null."); 340 } 341 342 checkTimerRecordingSourceType(sourceType); 343 try { 344 byte[] data = new byte[source.getDataSize()]; 345 source.toByteArray(data, 0); 346 mService.clearTimerRecording(recorderAddress, sourceType, data); 347 } catch (RemoteException e) { 348 Log.e(TAG, "failed to start record: ", e); 349 } 350 } 351 352 /** 353 * Interface used to get incoming MHL vendor command. 354 */ 355 public interface HdmiMhlVendorCommandListener { 356 void onReceived(int portId, int offset, int length, byte[] data); 357 } 358 359 /** 360 * Sets {@link HdmiMhlVendorCommandListener} to get incoming MHL vendor command. 361 * 362 * @param listener to receive incoming MHL vendor command 363 */ 364 public void setHdmiMhlVendorCommandListener(HdmiMhlVendorCommandListener listener) { 365 if (listener == null) { 366 throw new IllegalArgumentException("listener must not be null."); 367 } 368 try { 369 mService.addHdmiMhlVendorCommandListener(getListenerWrapper(listener)); 370 } catch (RemoteException e) { 371 Log.e(TAG, "failed to set hdmi mhl vendor command listener: ", e); 372 } 373 } 374 375 private IHdmiMhlVendorCommandListener getListenerWrapper( 376 final HdmiMhlVendorCommandListener listener) { 377 return new IHdmiMhlVendorCommandListener.Stub() { 378 @Override 379 public void onReceived(int portId, int offset, int length, byte[] data) { 380 listener.onReceived(portId, offset, length, data); 381 } 382 }; 383 } 384 385 /** 386 * Sends MHL vendor command to the device connected to a port of the given portId. 387 * 388 * @param portId id of port to send MHL vendor command 389 * @param offset offset in the in given data 390 * @param length length of data. offset + length should be bound to length of data. 391 * @param data container for vendor command data. It should be 16 bytes. 392 * @throws IllegalArgumentException if the given parameters are invalid 393 */ 394 public void sendMhlVendorCommand(int portId, int offset, int length, byte[] data) { 395 if (data == null || data.length != VENDOR_DATA_SIZE) { 396 throw new IllegalArgumentException("Invalid vendor command data."); 397 } 398 if (offset < 0 || offset >= VENDOR_DATA_SIZE) { 399 throw new IllegalArgumentException("Invalid offset:" + offset); 400 } 401 if (length < 0 || offset + length > VENDOR_DATA_SIZE) { 402 throw new IllegalArgumentException("Invalid length:" + length); 403 } 404 405 try { 406 mService.sendMhlVendorCommand(portId, offset, length, data); 407 } catch (RemoteException e) { 408 Log.e(TAG, "failed to send vendor command: ", e); 409 } 410 } 411 } 412