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