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 17 package android.hardware.hdmi; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.RequiresFeature; 23 import android.annotation.RequiresPermission; 24 import android.annotation.SdkConstant; 25 import android.annotation.SdkConstant.SdkConstantType; 26 import android.annotation.SuppressLint; 27 import android.annotation.SystemApi; 28 import android.annotation.SystemService; 29 import android.content.Context; 30 import android.content.pm.PackageManager; 31 import android.os.RemoteException; 32 import android.sysprop.HdmiProperties; 33 import android.util.ArrayMap; 34 import android.util.Log; 35 36 import com.android.internal.util.Preconditions; 37 38 import java.util.List; 39 40 /** 41 * The {@link HdmiControlManager} class is used to send HDMI control messages 42 * to attached CEC devices. 43 * 44 * <p>Provides various HDMI client instances that represent HDMI-CEC logical devices 45 * hosted in the system. {@link #getTvClient()}, for instance will return an 46 * {@link HdmiTvClient} object if the system is configured to host one. Android system 47 * can host more than one logical CEC devices. If multiple types are configured they 48 * all work as if they were independent logical devices running in the system. 49 * 50 * @hide 51 */ 52 @SystemApi 53 @SystemService(Context.HDMI_CONTROL_SERVICE) 54 @RequiresFeature(PackageManager.FEATURE_HDMI_CEC) 55 public final class HdmiControlManager { 56 private static final String TAG = "HdmiControlManager"; 57 58 @Nullable private final IHdmiControlService mService; 59 60 private static final int INVALID_PHYSICAL_ADDRESS = 0xFFFF; 61 62 private int mPhysicalAddress = INVALID_PHYSICAL_ADDRESS; 63 64 /** 65 * Broadcast Action: Display OSD message. 66 * <p>Send when the service has a message to display on screen for events 67 * that need user's attention such as ARC status change. 68 * <p>Always contains the extra fields {@link #EXTRA_MESSAGE_ID}. 69 * <p>Requires {@link android.Manifest.permission#HDMI_CEC} to receive. 70 */ 71 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 72 public static final String ACTION_OSD_MESSAGE = "android.hardware.hdmi.action.OSD_MESSAGE"; 73 74 // --- Messages for ACTION_OSD_MESSAGE --- 75 /** 76 * Message that ARC enabled device is connected to invalid port (non-ARC port). 77 */ 78 public static final int OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT = 1; 79 80 /** 81 * Message used by TV to receive volume status from Audio Receiver. It should check volume value 82 * that is retrieved from extra value with the key {@link #EXTRA_MESSAGE_EXTRA_PARAM1}. If the 83 * value is in range of [0,100], it is current volume of Audio Receiver. And there is another 84 * value, {@link #AVR_VOLUME_MUTED}, which is used to inform volume mute. 85 */ 86 public static final int OSD_MESSAGE_AVR_VOLUME_CHANGED = 2; 87 88 /** 89 * Used as an extra field in the intent {@link #ACTION_OSD_MESSAGE}. Contains the ID of 90 * the message to display on screen. 91 */ 92 public static final String EXTRA_MESSAGE_ID = "android.hardware.hdmi.extra.MESSAGE_ID"; 93 /** 94 * Used as an extra field in the intent {@link #ACTION_OSD_MESSAGE}. Contains the extra value 95 * of the message. 96 */ 97 public static final String EXTRA_MESSAGE_EXTRA_PARAM1 = 98 "android.hardware.hdmi.extra.MESSAGE_EXTRA_PARAM1"; 99 100 /** 101 * Volume value for mute state. 102 */ 103 public static final int AVR_VOLUME_MUTED = 101; 104 105 public static final int POWER_STATUS_UNKNOWN = -1; 106 public static final int POWER_STATUS_ON = 0; 107 public static final int POWER_STATUS_STANDBY = 1; 108 public static final int POWER_STATUS_TRANSIENT_TO_ON = 2; 109 public static final int POWER_STATUS_TRANSIENT_TO_STANDBY = 3; 110 111 @IntDef ({ 112 RESULT_SUCCESS, 113 RESULT_TIMEOUT, 114 RESULT_SOURCE_NOT_AVAILABLE, 115 RESULT_TARGET_NOT_AVAILABLE, 116 RESULT_ALREADY_IN_PROGRESS, 117 RESULT_EXCEPTION, 118 RESULT_INCORRECT_MODE, 119 RESULT_COMMUNICATION_FAILED, 120 }) 121 public @interface ControlCallbackResult {} 122 123 /** Control operation is successfully handled by the framework. */ 124 public static final int RESULT_SUCCESS = 0; 125 public static final int RESULT_TIMEOUT = 1; 126 /** Source device that the application is using is not available. */ 127 public static final int RESULT_SOURCE_NOT_AVAILABLE = 2; 128 /** Target device that the application is controlling is not available. */ 129 public static final int RESULT_TARGET_NOT_AVAILABLE = 3; 130 131 @Deprecated public static final int RESULT_ALREADY_IN_PROGRESS = 4; 132 public static final int RESULT_EXCEPTION = 5; 133 public static final int RESULT_INCORRECT_MODE = 6; 134 public static final int RESULT_COMMUNICATION_FAILED = 7; 135 136 public static final int DEVICE_EVENT_ADD_DEVICE = 1; 137 public static final int DEVICE_EVENT_REMOVE_DEVICE = 2; 138 public static final int DEVICE_EVENT_UPDATE_DEVICE = 3; 139 140 // --- One Touch Recording success result 141 /** Recording currently selected source. Indicates the status of a recording. */ 142 public static final int ONE_TOUCH_RECORD_RECORDING_CURRENTLY_SELECTED_SOURCE = 0x01; 143 /** Recording Digital Service. Indicates the status of a recording. */ 144 public static final int ONE_TOUCH_RECORD_RECORDING_DIGITAL_SERVICE = 0x02; 145 /** Recording Analogue Service. Indicates the status of a recording. */ 146 public static final int ONE_TOUCH_RECORD_RECORDING_ANALOGUE_SERVICE = 0x03; 147 /** Recording External input. Indicates the status of a recording. */ 148 public static final int ONE_TOUCH_RECORD_RECORDING_EXTERNAL_INPUT = 0x04; 149 150 // --- One Touch Record failure result 151 /** No recording – unable to record Digital Service. No suitable tuner. */ 152 public static final int ONE_TOUCH_RECORD_UNABLE_DIGITAL_SERVICE = 0x05; 153 /** No recording – unable to record Analogue Service. No suitable tuner. */ 154 public static final int ONE_TOUCH_RECORD_UNABLE_ANALOGUE_SERVICE = 0x06; 155 /** 156 * No recording – unable to select required service. as suitable tuner, but the requested 157 * parameters are invalid or out of range for that tuner. 158 */ 159 public static final int ONE_TOUCH_RECORD_UNABLE_SELECTED_SERVICE = 0x07; 160 /** No recording – invalid External plug number */ 161 public static final int ONE_TOUCH_RECORD_INVALID_EXTERNAL_PLUG_NUMBER = 0x09; 162 /** No recording – invalid External Physical Address */ 163 public static final int ONE_TOUCH_RECORD_INVALID_EXTERNAL_PHYSICAL_ADDRESS = 0x0A; 164 /** No recording – CA system not supported */ 165 public static final int ONE_TOUCH_RECORD_UNSUPPORTED_CA = 0x0B; 166 /** No Recording – No or Insufficient CA Entitlements” */ 167 public static final int ONE_TOUCH_RECORD_NO_OR_INSUFFICIENT_CA_ENTITLEMENTS = 0x0C; 168 /** No recording – Not allowed to copy source. Source is “copy never”. */ 169 public static final int ONE_TOUCH_RECORD_DISALLOW_TO_COPY = 0x0D; 170 /** No recording – No further copies allowed */ 171 public static final int ONE_TOUCH_RECORD_DISALLOW_TO_FUTHER_COPIES = 0x0E; 172 /** No recording – No media */ 173 public static final int ONE_TOUCH_RECORD_NO_MEDIA = 0x10; 174 /** No recording – playing */ 175 public static final int ONE_TOUCH_RECORD_PLAYING = 0x11; 176 /** No recording – already recording */ 177 public static final int ONE_TOUCH_RECORD_ALREADY_RECORDING = 0x12; 178 /** No recording – media protected */ 179 public static final int ONE_TOUCH_RECORD_MEDIA_PROTECTED = 0x13; 180 /** No recording – no source signal */ 181 public static final int ONE_TOUCH_RECORD_NO_SOURCE_SIGNAL = 0x14; 182 /** No recording – media problem */ 183 public static final int ONE_TOUCH_RECORD_MEDIA_PROBLEM = 0x15; 184 /** No recording – not enough space available */ 185 public static final int ONE_TOUCH_RECORD_NOT_ENOUGH_SPACE = 0x16; 186 /** No recording – Parental Lock On */ 187 public static final int ONE_TOUCH_RECORD_PARENT_LOCK_ON = 0x17; 188 /** Recording terminated normally */ 189 public static final int ONE_TOUCH_RECORD_RECORDING_TERMINATED_NORMALLY = 0x1A; 190 /** Recording has already terminated */ 191 public static final int ONE_TOUCH_RECORD_RECORDING_ALREADY_TERMINATED = 0x1B; 192 /** No recording – other reason */ 193 public static final int ONE_TOUCH_RECORD_OTHER_REASON = 0x1F; 194 // From here extra message for recording that is not mentioned in CEC spec 195 /** No recording. Previous recording request in progress. */ 196 public static final int ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS = 0x30; 197 /** No recording. Please check recorder and connection. */ 198 public static final int ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION = 0x31; 199 /** Cannot record currently displayed source. */ 200 public static final int ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN = 0x32; 201 /** CEC is disabled. */ 202 public static final int ONE_TOUCH_RECORD_CEC_DISABLED = 0x33; 203 204 // --- Types for timer recording 205 /** Timer recording type for digital service source. */ 206 public static final int TIMER_RECORDING_TYPE_DIGITAL = 1; 207 /** Timer recording type for analogue service source. */ 208 public static final int TIMER_RECORDING_TYPE_ANALOGUE = 2; 209 /** Timer recording type for external source. */ 210 public static final int TIMER_RECORDING_TYPE_EXTERNAL = 3; 211 212 // --- Timer Status Data 213 /** [Timer Status Data/Media Info] - Media present and not protected. */ 214 public static final int TIMER_STATUS_MEDIA_INFO_PRESENT_NOT_PROTECTED = 0x0; 215 /** [Timer Status Data/Media Info] - Media present, but protected. */ 216 public static final int TIMER_STATUS_MEDIA_INFO_PRESENT_PROTECTED = 0x1; 217 /** [Timer Status Data/Media Info] - Media not present. */ 218 public static final int TIMER_STATUS_MEDIA_INFO_NOT_PRESENT = 0x2; 219 220 /** [Timer Status Data/Programmed Info] - Enough space available for recording. */ 221 public static final int TIMER_STATUS_PROGRAMMED_INFO_ENOUGH_SPACE = 0x8; 222 /** [Timer Status Data/Programmed Info] - Not enough space available for recording. */ 223 public static final int TIMER_STATUS_PROGRAMMED_INFO_NOT_ENOUGH_SPACE = 0x9; 224 /** [Timer Status Data/Programmed Info] - Might not enough space available for recording. */ 225 public static final int TIMER_STATUS_PROGRAMMED_INFO_MIGHT_NOT_ENOUGH_SPACE = 0xB; 226 /** [Timer Status Data/Programmed Info] - No media info available. */ 227 public static final int TIMER_STATUS_PROGRAMMED_INFO_NO_MEDIA_INFO = 0xA; 228 229 /** [Timer Status Data/Not Programmed Error Info] - No free timer available. */ 230 public static final int TIMER_STATUS_NOT_PROGRAMMED_NO_FREE_TIME = 0x1; 231 /** [Timer Status Data/Not Programmed Error Info] - Date out of range. */ 232 public static final int TIMER_STATUS_NOT_PROGRAMMED_DATE_OUT_OF_RANGE = 0x2; 233 /** [Timer Status Data/Not Programmed Error Info] - Recording Sequence error. */ 234 public static final int TIMER_STATUS_NOT_PROGRAMMED_INVALID_SEQUENCE = 0x3; 235 /** [Timer Status Data/Not Programmed Error Info] - Invalid External Plug Number. */ 236 public static final int TIMER_STATUS_NOT_PROGRAMMED_INVALID_EXTERNAL_PLUG_NUMBER = 0x4; 237 /** [Timer Status Data/Not Programmed Error Info] - Invalid External Physical Address. */ 238 public static final int TIMER_STATUS_NOT_PROGRAMMED_INVALID_EXTERNAL_PHYSICAL_NUMBER = 0x5; 239 /** [Timer Status Data/Not Programmed Error Info] - CA system not supported. */ 240 public static final int TIMER_STATUS_NOT_PROGRAMMED_CA_NOT_SUPPORTED = 0x6; 241 /** [Timer Status Data/Not Programmed Error Info] - No or insufficient CA Entitlements. */ 242 public static final int TIMER_STATUS_NOT_PROGRAMMED_NO_CA_ENTITLEMENTS = 0x7; 243 /** [Timer Status Data/Not Programmed Error Info] - Does not support resolution. */ 244 public static final int TIMER_STATUS_NOT_PROGRAMMED_UNSUPPORTED_RESOLUTION = 0x8; 245 /** [Timer Status Data/Not Programmed Error Info] - Parental Lock On. */ 246 public static final int TIMER_STATUS_NOT_PROGRAMMED_PARENTAL_LOCK_ON= 0x9; 247 /** [Timer Status Data/Not Programmed Error Info] - Clock Failure. */ 248 public static final int TIMER_STATUS_NOT_PROGRAMMED_CLOCK_FAILURE = 0xA; 249 /** [Timer Status Data/Not Programmed Error Info] - Duplicate: already programmed. */ 250 public static final int TIMER_STATUS_NOT_PROGRAMMED_DUPLICATED = 0xE; 251 252 // --- Extra result value for timer recording. 253 /** No extra error. */ 254 public static final int TIMER_RECORDING_RESULT_EXTRA_NO_ERROR = 0x00; 255 /** No timer recording - check recorder and connection. */ 256 public static final int TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION = 0x01; 257 /** No timer recording - cannot record selected source. */ 258 public static final int TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE = 0x02; 259 /** CEC is disabled. */ 260 public static final int TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED = 0x03; 261 262 // -- Timer cleared status data code used for result of onClearTimerRecordingResult. 263 /** Timer not cleared – recording. */ 264 public static final int CLEAR_TIMER_STATUS_TIMER_NOT_CLEARED_RECORDING = 0x00; 265 /** Timer not cleared – no matching. */ 266 public static final int CLEAR_TIMER_STATUS_TIMER_NOT_CLEARED_NO_MATCHING = 0x01; 267 /** Timer not cleared – no info available. */ 268 public static final int CLEAR_TIMER_STATUS_TIMER_NOT_CLEARED_NO_INFO_AVAILABLE = 0x02; 269 /** Timer cleared. */ 270 public static final int CLEAR_TIMER_STATUS_TIMER_CLEARED = 0x80; 271 /** Clear timer error - check recorder and connection. */ 272 public static final int CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION = 0xA0; 273 /** Clear timer error - cannot clear timer for selected source. */ 274 public static final int CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE = 0xA1; 275 /** Clear timer error - CEC is disabled. */ 276 public static final int CLEAR_TIMER_STATUS_CEC_DISABLE = 0xA2; 277 278 /** The HdmiControlService is started. */ 279 public static final int CONTROL_STATE_CHANGED_REASON_START = 0; 280 /** The state of HdmiControlService is changed by changing of settings. */ 281 public static final int CONTROL_STATE_CHANGED_REASON_SETTING = 1; 282 /** The HdmiControlService is enabled to wake up. */ 283 public static final int CONTROL_STATE_CHANGED_REASON_WAKEUP = 2; 284 /** The HdmiControlService will be disabled to standby. */ 285 public static final int CONTROL_STATE_CHANGED_REASON_STANDBY = 3; 286 287 // True if we have a logical device of type playback hosted in the system. 288 private final boolean mHasPlaybackDevice; 289 // True if we have a logical device of type TV hosted in the system. 290 private final boolean mHasTvDevice; 291 // True if we have a logical device of type audio system hosted in the system. 292 private final boolean mHasAudioSystemDevice; 293 // True if we have a logical device of type audio system hosted in the system. 294 private final boolean mHasSwitchDevice; 295 // True if it's a switch device. 296 private final boolean mIsSwitchDevice; 297 298 /** 299 * {@hide} - hide this constructor because it has a parameter of type IHdmiControlService, 300 * which is a system private class. The right way to create an instance of this class is 301 * using the factory Context.getSystemService. 302 */ HdmiControlManager(IHdmiControlService service)303 public HdmiControlManager(IHdmiControlService service) { 304 mService = service; 305 int[] types = null; 306 if (mService != null) { 307 try { 308 types = mService.getSupportedTypes(); 309 } catch (RemoteException e) { 310 throw e.rethrowFromSystemServer(); 311 } 312 } 313 mHasTvDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_TV); 314 mHasPlaybackDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_PLAYBACK); 315 mHasAudioSystemDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); 316 mHasSwitchDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH); 317 mIsSwitchDevice = HdmiProperties.is_switch().orElse(false); 318 } 319 hasDeviceType(int[] types, int type)320 private static boolean hasDeviceType(int[] types, int type) { 321 if (types == null) { 322 return false; 323 } 324 for (int t : types) { 325 if (t == type) { 326 return true; 327 } 328 } 329 return false; 330 } 331 332 /** 333 * Gets an object that represents an HDMI-CEC logical device of a specified type. 334 * 335 * @param type CEC device type 336 * @return {@link HdmiClient} instance. {@code null} on failure. 337 * See {@link HdmiDeviceInfo#DEVICE_PLAYBACK} 338 * See {@link HdmiDeviceInfo#DEVICE_TV} 339 * See {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} 340 */ 341 @Nullable 342 @SuppressLint("Doclava125") getClient(int type)343 public HdmiClient getClient(int type) { 344 if (mService == null) { 345 return null; 346 } 347 switch (type) { 348 case HdmiDeviceInfo.DEVICE_TV: 349 return mHasTvDevice ? new HdmiTvClient(mService) : null; 350 case HdmiDeviceInfo.DEVICE_PLAYBACK: 351 return mHasPlaybackDevice ? new HdmiPlaybackClient(mService) : null; 352 case HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM: 353 return mHasAudioSystemDevice ? new HdmiAudioSystemClient(mService) : null; 354 case HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH: 355 return (mHasSwitchDevice || mIsSwitchDevice) 356 ? new HdmiSwitchClient(mService) : null; 357 default: 358 return null; 359 } 360 } 361 362 /** 363 * Gets an object that represents an HDMI-CEC logical device of type playback on the system. 364 * 365 * <p>Used to send HDMI control messages to other devices like TV or audio amplifier through 366 * HDMI bus. It is also possible to communicate with other logical devices hosted in the same 367 * system if the system is configured to host more than one type of HDMI-CEC logical devices. 368 * 369 * @return {@link HdmiPlaybackClient} instance. {@code null} on failure. 370 */ 371 @Nullable 372 @SuppressLint("Doclava125") getPlaybackClient()373 public HdmiPlaybackClient getPlaybackClient() { 374 return (HdmiPlaybackClient) getClient(HdmiDeviceInfo.DEVICE_PLAYBACK); 375 } 376 377 /** 378 * Gets an object that represents an HDMI-CEC logical device of type TV on the system. 379 * 380 * <p>Used to send HDMI control messages to other devices and manage them through 381 * HDMI bus. It is also possible to communicate with other logical devices hosted in the same 382 * system if the system is configured to host more than one type of HDMI-CEC logical devices. 383 * 384 * @return {@link HdmiTvClient} instance. {@code null} on failure. 385 */ 386 @Nullable 387 @SuppressLint("Doclava125") getTvClient()388 public HdmiTvClient getTvClient() { 389 return (HdmiTvClient) getClient(HdmiDeviceInfo.DEVICE_TV); 390 } 391 392 /** 393 * Gets an object that represents an HDMI-CEC logical device of type audio system on the system. 394 * 395 * <p>Used to send HDMI control messages to other devices like TV through HDMI bus. It is also 396 * possible to communicate with other logical devices hosted in the same system if the system is 397 * configured to host more than one type of HDMI-CEC logical devices. 398 * 399 * @return {@link HdmiAudioSystemClient} instance. {@code null} on failure. 400 * 401 * TODO(b/110094868): unhide for Q 402 * @hide 403 */ 404 @Nullable 405 @SuppressLint("Doclava125") getAudioSystemClient()406 public HdmiAudioSystemClient getAudioSystemClient() { 407 return (HdmiAudioSystemClient) getClient(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); 408 } 409 410 /** 411 * Gets an object that represents an HDMI-CEC logical device of type switch on the system. 412 * 413 * <p>Used to send HDMI control messages to other devices (e.g. TVs) through HDMI bus. 414 * It is also possible to communicate with other logical devices hosted in the same 415 * system if the system is configured to host more than one type of HDMI-CEC logical device. 416 * 417 * @return {@link HdmiSwitchClient} instance. {@code null} on failure. 418 * @hide 419 */ 420 @Nullable 421 @SystemApi 422 @SuppressLint("Doclava125") getSwitchClient()423 public HdmiSwitchClient getSwitchClient() { 424 return (HdmiSwitchClient) getClient(HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH); 425 } 426 427 /** 428 * Get a snapshot of the real-time status of the devices on the CEC bus. 429 * 430 * <p>This only applies to devices with switch functionality, which are devices with one 431 * or more than one HDMI inputs. 432 * 433 * @return a list of {@link HdmiDeviceInfo} of the connected CEC devices on the CEC bus. An 434 * empty list will be returned if there is none. 435 * 436 * @hide 437 */ 438 @NonNull 439 @SystemApi getConnectedDevices()440 public List<HdmiDeviceInfo> getConnectedDevices() { 441 try { 442 return mService.getDeviceList(); 443 } catch (RemoteException e) { 444 throw e.rethrowFromSystemServer(); 445 } 446 } 447 448 /** 449 * @removed 450 * @hide 451 * @deprecated Please use {@link #getConnectedDevices()} instead. 452 */ 453 @Deprecated 454 @SystemApi getConnectedDevicesList()455 public List<HdmiDeviceInfo> getConnectedDevicesList() { 456 try { 457 return mService.getDeviceList(); 458 } catch (RemoteException e) { 459 throw e.rethrowFromSystemServer(); 460 } 461 } 462 463 /** 464 * Power off the target device by sending CEC commands. Note that this device can't be the 465 * current device itself. 466 * 467 * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}. 468 * 469 * @param deviceInfo {@link HdmiDeviceInfo} of the device to be powered off. 470 * 471 * @hide 472 */ 473 @SystemApi powerOffDevice(@onNull HdmiDeviceInfo deviceInfo)474 public void powerOffDevice(@NonNull HdmiDeviceInfo deviceInfo) { 475 Preconditions.checkNotNull(deviceInfo); 476 try { 477 mService.powerOffRemoteDevice( 478 deviceInfo.getLogicalAddress(), deviceInfo.getDevicePowerStatus()); 479 } catch (RemoteException e) { 480 throw e.rethrowFromSystemServer(); 481 } 482 } 483 484 /** 485 * @removed 486 * @hide 487 * @deprecated Please use {@link #powerOffDevice(deviceInfo)} instead. 488 */ 489 @Deprecated 490 @SystemApi powerOffRemoteDevice(@onNull HdmiDeviceInfo deviceInfo)491 public void powerOffRemoteDevice(@NonNull HdmiDeviceInfo deviceInfo) { 492 Preconditions.checkNotNull(deviceInfo); 493 try { 494 mService.powerOffRemoteDevice( 495 deviceInfo.getLogicalAddress(), deviceInfo.getDevicePowerStatus()); 496 } catch (RemoteException e) { 497 throw e.rethrowFromSystemServer(); 498 } 499 } 500 501 /** 502 * Power on the target device by sending CEC commands. Note that this device can't be the 503 * current device itself. 504 * 505 * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}. 506 * 507 * @param deviceInfo {@link HdmiDeviceInfo} of the device to be powered on. 508 * 509 * @hide 510 */ powerOnDevice(HdmiDeviceInfo deviceInfo)511 public void powerOnDevice(HdmiDeviceInfo deviceInfo) { 512 Preconditions.checkNotNull(deviceInfo); 513 try { 514 mService.powerOnRemoteDevice( 515 deviceInfo.getLogicalAddress(), deviceInfo.getDevicePowerStatus()); 516 } catch (RemoteException e) { 517 throw e.rethrowFromSystemServer(); 518 } 519 } 520 521 /** 522 * @removed 523 * @hide 524 * @deprecated Please use {@link #powerOnDevice(deviceInfo)} instead. 525 */ 526 @Deprecated 527 @SystemApi powerOnRemoteDevice(HdmiDeviceInfo deviceInfo)528 public void powerOnRemoteDevice(HdmiDeviceInfo deviceInfo) { 529 Preconditions.checkNotNull(deviceInfo); 530 try { 531 mService.powerOnRemoteDevice( 532 deviceInfo.getLogicalAddress(), deviceInfo.getDevicePowerStatus()); 533 } catch (RemoteException e) { 534 throw e.rethrowFromSystemServer(); 535 } 536 } 537 538 /** 539 * Request the target device to be the new Active Source by sending CEC commands. Note that 540 * this device can't be the current device itself. 541 * 542 * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}. 543 * 544 * <p>If the target device responds to the command, the users should see the target device 545 * streaming on their TVs. 546 * 547 * @param deviceInfo HdmiDeviceInfo of the target device 548 * 549 * @hide 550 */ 551 @SystemApi setActiveSource(@onNull HdmiDeviceInfo deviceInfo)552 public void setActiveSource(@NonNull HdmiDeviceInfo deviceInfo) { 553 Preconditions.checkNotNull(deviceInfo); 554 try { 555 mService.askRemoteDeviceToBecomeActiveSource(deviceInfo.getPhysicalAddress()); 556 } catch (RemoteException e) { 557 throw e.rethrowFromSystemServer(); 558 } 559 } 560 561 /** 562 * @removed 563 * @hide 564 * @deprecated Please use {@link #setActiveSource(deviceInfo)} instead. 565 */ 566 @Deprecated 567 @SystemApi requestRemoteDeviceToBecomeActiveSource(@onNull HdmiDeviceInfo deviceInfo)568 public void requestRemoteDeviceToBecomeActiveSource(@NonNull HdmiDeviceInfo deviceInfo) { 569 Preconditions.checkNotNull(deviceInfo); 570 try { 571 mService.askRemoteDeviceToBecomeActiveSource(deviceInfo.getPhysicalAddress()); 572 } catch (RemoteException e) { 573 throw e.rethrowFromSystemServer(); 574 } 575 } 576 577 /** 578 * Controls standby mode of the system. It will also try to turn on/off the connected devices if 579 * necessary. 580 * 581 * @param isStandbyModeOn target status of the system's standby mode 582 */ 583 @RequiresPermission(android.Manifest.permission.HDMI_CEC) setStandbyMode(boolean isStandbyModeOn)584 public void setStandbyMode(boolean isStandbyModeOn) { 585 try { 586 mService.setStandbyMode(isStandbyModeOn); 587 } catch (RemoteException e) { 588 throw e.rethrowFromSystemServer(); 589 } 590 } 591 592 /** 593 * Gets whether the system is in system audio mode. 594 * 595 * @hide 596 */ getSystemAudioMode()597 public boolean getSystemAudioMode() { 598 try { 599 return mService.getSystemAudioMode(); 600 } catch (RemoteException e) { 601 throw e.rethrowFromSystemServer(); 602 } 603 } 604 605 /** 606 * Get the physical address of the device. 607 * 608 * <p>Physical address needs to be automatically adjusted when devices are phyiscally or 609 * electrically added or removed from the device tree. Please see HDMI Specification Version 610 * 1.4b 8.7 Physical Address for more details on the address discovery proccess. 611 * 612 * @hide 613 */ 614 @SystemApi getPhysicalAddress()615 public int getPhysicalAddress() { 616 if (mPhysicalAddress != INVALID_PHYSICAL_ADDRESS) { 617 return mPhysicalAddress; 618 } 619 try { 620 mPhysicalAddress = mService.getPhysicalAddress(); 621 return mPhysicalAddress; 622 } catch (RemoteException e) { 623 throw e.rethrowFromSystemServer(); 624 } 625 } 626 627 /** 628 * Check if the target device is connected to the current device. 629 * 630 * <p>The API also returns true if the current device is the target. 631 * 632 * @param targetDevice {@link HdmiDeviceInfo} of the target device. 633 * @return true if {@code targetDevice} is directly or indirectly 634 * connected to the current device. 635 * 636 * @hide 637 */ 638 @SystemApi isDeviceConnected(@onNull HdmiDeviceInfo targetDevice)639 public boolean isDeviceConnected(@NonNull HdmiDeviceInfo targetDevice) { 640 Preconditions.checkNotNull(targetDevice); 641 mPhysicalAddress = getPhysicalAddress(); 642 if (mPhysicalAddress == INVALID_PHYSICAL_ADDRESS) { 643 return false; 644 } 645 int targetPhysicalAddress = targetDevice.getPhysicalAddress(); 646 if (targetPhysicalAddress == INVALID_PHYSICAL_ADDRESS) { 647 return false; 648 } 649 return HdmiUtils.getLocalPortFromPhysicalAddress(targetPhysicalAddress, mPhysicalAddress) 650 != HdmiUtils.TARGET_NOT_UNDER_LOCAL_DEVICE; 651 } 652 653 /** 654 * @removed 655 * @hide 656 * @deprecated Please use {@link #isDeviceConnected(targetDevice)} instead. 657 */ 658 @Deprecated 659 @SystemApi isRemoteDeviceConnected(@onNull HdmiDeviceInfo targetDevice)660 public boolean isRemoteDeviceConnected(@NonNull HdmiDeviceInfo targetDevice) { 661 Preconditions.checkNotNull(targetDevice); 662 mPhysicalAddress = getPhysicalAddress(); 663 if (mPhysicalAddress == INVALID_PHYSICAL_ADDRESS) { 664 return false; 665 } 666 int targetPhysicalAddress = targetDevice.getPhysicalAddress(); 667 if (targetPhysicalAddress == INVALID_PHYSICAL_ADDRESS) { 668 return false; 669 } 670 return HdmiUtils.getLocalPortFromPhysicalAddress(targetPhysicalAddress, mPhysicalAddress) 671 != HdmiUtils.TARGET_NOT_UNDER_LOCAL_DEVICE; 672 } 673 674 /** 675 * Listener used to get hotplug event from HDMI port. 676 */ 677 public interface HotplugEventListener { onReceived(HdmiHotplugEvent event)678 void onReceived(HdmiHotplugEvent event); 679 } 680 681 private final ArrayMap<HotplugEventListener, IHdmiHotplugEventListener> 682 mHotplugEventListeners = new ArrayMap<>(); 683 684 /** 685 * Listener used to get HDMI Control (CEC) status (enabled/disabled) and the connected display 686 * status. 687 * @hide 688 */ 689 public interface HdmiControlStatusChangeListener { 690 /** 691 * Called when HDMI Control (CEC) is enabled/disabled. 692 * 693 * @param isCecEnabled status of HDMI Control 694 * {@link android.provider.Settings.Global#HDMI_CONTROL_ENABLED}: {@code true} if enabled. 695 * @param isCecAvailable status of CEC support of the connected display (the TV). 696 * {@code true} if supported. 697 * 698 * Note: Value of isCecAvailable is only valid when isCecEnabled is true. 699 **/ onStatusChange(boolean isCecEnabled, boolean isCecAvailable)700 void onStatusChange(boolean isCecEnabled, boolean isCecAvailable); 701 } 702 703 private final ArrayMap<HdmiControlStatusChangeListener, IHdmiControlStatusChangeListener> 704 mHdmiControlStatusChangeListeners = new ArrayMap<>(); 705 706 /** 707 * Listener used to get vendor-specific commands. 708 */ 709 public interface VendorCommandListener { 710 /** 711 * Called when a vendor command is received. 712 * 713 * @param srcAddress source logical address 714 * @param destAddress destination logical address 715 * @param params vendor-specific parameters 716 * @param hasVendorId {@code true} if the command is <Vendor Command 717 * With ID>. The first 3 bytes of params is vendor id. 718 */ onReceived(int srcAddress, int destAddress, byte[] params, boolean hasVendorId)719 void onReceived(int srcAddress, int destAddress, byte[] params, boolean hasVendorId); 720 721 /** 722 * The callback is called: 723 * <ul> 724 * <li> before HdmiControlService is disabled. 725 * <li> after HdmiControlService is enabled and the local address is assigned. 726 * </ul> 727 * The client shouldn't hold the thread too long since this is a blocking call. 728 * 729 * @param enabled {@code true} if HdmiControlService is enabled. 730 * @param reason the reason code why the state of HdmiControlService is changed. 731 * @see #CONTROL_STATE_CHANGED_REASON_START 732 * @see #CONTROL_STATE_CHANGED_REASON_SETTING 733 * @see #CONTROL_STATE_CHANGED_REASON_WAKEUP 734 * @see #CONTROL_STATE_CHANGED_REASON_STANDBY 735 */ onControlStateChanged(boolean enabled, int reason)736 void onControlStateChanged(boolean enabled, int reason); 737 } 738 739 /** 740 * Adds a listener to get informed of {@link HdmiHotplugEvent}. 741 * 742 * <p>To stop getting the notification, 743 * use {@link #removeHotplugEventListener(HotplugEventListener)}. 744 * 745 * @param listener {@link HotplugEventListener} instance 746 * @see HdmiControlManager#removeHotplugEventListener(HotplugEventListener) 747 */ 748 @RequiresPermission(android.Manifest.permission.HDMI_CEC) addHotplugEventListener(HotplugEventListener listener)749 public void addHotplugEventListener(HotplugEventListener listener) { 750 if (mService == null) { 751 Log.e(TAG, "HdmiControlService is not available"); 752 return; 753 } 754 if (mHotplugEventListeners.containsKey(listener)) { 755 Log.e(TAG, "listener is already registered"); 756 return; 757 } 758 IHdmiHotplugEventListener wrappedListener = getHotplugEventListenerWrapper(listener); 759 mHotplugEventListeners.put(listener, wrappedListener); 760 try { 761 mService.addHotplugEventListener(wrappedListener); 762 } catch (RemoteException e) { 763 throw e.rethrowFromSystemServer(); 764 } 765 } 766 767 /** 768 * Removes a listener to stop getting informed of {@link HdmiHotplugEvent}. 769 * 770 * @param listener {@link HotplugEventListener} instance to be removed 771 */ 772 @RequiresPermission(android.Manifest.permission.HDMI_CEC) removeHotplugEventListener(HotplugEventListener listener)773 public void removeHotplugEventListener(HotplugEventListener listener) { 774 if (mService == null) { 775 Log.e(TAG, "HdmiControlService is not available"); 776 return; 777 } 778 IHdmiHotplugEventListener wrappedListener = mHotplugEventListeners.remove(listener); 779 if (wrappedListener == null) { 780 Log.e(TAG, "tried to remove not-registered listener"); 781 return; 782 } 783 try { 784 mService.removeHotplugEventListener(wrappedListener); 785 } catch (RemoteException e) { 786 throw e.rethrowFromSystemServer(); 787 } 788 } 789 getHotplugEventListenerWrapper( final HotplugEventListener listener)790 private IHdmiHotplugEventListener getHotplugEventListenerWrapper( 791 final HotplugEventListener listener) { 792 return new IHdmiHotplugEventListener.Stub() { 793 @Override 794 public void onReceived(HdmiHotplugEvent event) { 795 listener.onReceived(event);; 796 } 797 }; 798 } 799 800 /** 801 * Adds a listener to get informed of {@link HdmiControlStatusChange}. 802 * 803 * <p>To stop getting the notification, 804 * use {@link #removeHdmiControlStatusChangeListener(HdmiControlStatusChangeListener)}. 805 * 806 * @param listener {@link HdmiControlStatusChangeListener} instance 807 * @see HdmiControlManager#removeHdmiControlStatusChangeListener( 808 * HdmiControlStatusChangeListener) 809 * 810 * @hide 811 */ 812 @RequiresPermission(android.Manifest.permission.HDMI_CEC) 813 public void addHdmiControlStatusChangeListener(HdmiControlStatusChangeListener listener) { 814 if (mService == null) { 815 Log.e(TAG, "HdmiControlService is not available"); 816 return; 817 } 818 if (mHdmiControlStatusChangeListeners.containsKey(listener)) { 819 Log.e(TAG, "listener is already registered"); 820 return; 821 } 822 IHdmiControlStatusChangeListener wrappedListener = 823 getHdmiControlStatusChangeListenerWrapper(listener); 824 mHdmiControlStatusChangeListeners.put(listener, wrappedListener); 825 try { 826 mService.addHdmiControlStatusChangeListener(wrappedListener); 827 } catch (RemoteException e) { 828 throw e.rethrowFromSystemServer(); 829 } 830 } 831 832 /** 833 * Removes a listener to stop getting informed of {@link HdmiControlStatusChange}. 834 * 835 * @param listener {@link HdmiControlStatusChangeListener} instance to be removed 836 * 837 * @hide 838 */ 839 @RequiresPermission(android.Manifest.permission.HDMI_CEC) 840 public void removeHdmiControlStatusChangeListener(HdmiControlStatusChangeListener listener) { 841 if (mService == null) { 842 Log.e(TAG, "HdmiControlService is not available"); 843 return; 844 } 845 IHdmiControlStatusChangeListener wrappedListener = 846 mHdmiControlStatusChangeListeners.remove(listener); 847 if (wrappedListener == null) { 848 Log.e(TAG, "tried to remove not-registered listener"); 849 return; 850 } 851 try { 852 mService.removeHdmiControlStatusChangeListener(wrappedListener); 853 } catch (RemoteException e) { 854 throw e.rethrowFromSystemServer(); 855 } 856 } 857 858 private IHdmiControlStatusChangeListener getHdmiControlStatusChangeListenerWrapper( 859 final HdmiControlStatusChangeListener listener) { 860 return new IHdmiControlStatusChangeListener.Stub() { 861 @Override 862 public void onStatusChange(boolean isCecEnabled, boolean isCecAvailable) { 863 listener.onStatusChange(isCecEnabled, isCecAvailable); 864 } 865 }; 866 } 867 868 } 869