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 &lt;Vendor Command
869          *        With ID&gt;. 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