1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.hardware.hdmi;
18 
19 import android.annotation.CallbackExecutor;
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.RequiresFeature;
24 import android.annotation.RequiresPermission;
25 import android.annotation.SdkConstant;
26 import android.annotation.SdkConstant.SdkConstantType;
27 import android.annotation.StringDef;
28 import android.annotation.SuppressLint;
29 import android.annotation.SystemApi;
30 import android.annotation.SystemService;
31 import android.content.Context;
32 import android.content.pm.PackageManager;
33 import android.os.Binder;
34 import android.os.RemoteException;
35 import android.sysprop.HdmiProperties;
36 import android.util.ArrayMap;
37 import android.util.Log;
38 
39 import com.android.internal.annotations.GuardedBy;
40 import com.android.internal.util.ConcurrentUtils;
41 
42 import java.lang.annotation.Retention;
43 import java.lang.annotation.RetentionPolicy;
44 import java.util.ArrayList;
45 import java.util.Arrays;
46 import java.util.List;
47 import java.util.Objects;
48 import java.util.concurrent.Executor;
49 import java.util.stream.Collectors;
50 
51 /**
52  * The {@link HdmiControlManager} class is used to send HDMI control messages
53  * to attached CEC devices.
54  *
55  * <p>Provides various HDMI client instances that represent HDMI-CEC logical devices
56  * hosted in the system. {@link #getTvClient()}, for instance will return an
57  * {@link HdmiTvClient} object if the system is configured to host one. Android system
58  * can host more than one logical CEC devices. If multiple types are configured they
59  * all work as if they were independent logical devices running in the system.
60  *
61  * @hide
62  */
63 @SystemApi
64 @SystemService(Context.HDMI_CONTROL_SERVICE)
65 @RequiresFeature(PackageManager.FEATURE_HDMI_CEC)
66 public final class HdmiControlManager {
67     private static final String TAG = "HdmiControlManager";
68 
69     @Nullable private final IHdmiControlService mService;
70 
71     private static final int INVALID_PHYSICAL_ADDRESS = 0xFFFF;
72 
73     /**
74      * A cache of the current device's physical address. When device's HDMI out port
75      * is not connected to any device, it is set to {@link #INVALID_PHYSICAL_ADDRESS}.
76      *
77      * <p>Otherwise it is updated by the {@link ClientHotplugEventListener} registered
78      * with {@link com.android.server.hdmi.HdmiControlService} by the
79      * {@link #addHotplugEventListener(HotplugEventListener)} and the address is from
80      * {@link com.android.server.hdmi.HdmiControlService#getPortInfo()}
81      */
82     @GuardedBy("mLock")
83     private int mLocalPhysicalAddress = INVALID_PHYSICAL_ADDRESS;
84 
setLocalPhysicalAddress(int physicalAddress)85     private void setLocalPhysicalAddress(int physicalAddress) {
86         synchronized (mLock) {
87             mLocalPhysicalAddress = physicalAddress;
88         }
89     }
90 
getLocalPhysicalAddress()91     private int getLocalPhysicalAddress() {
92         synchronized (mLock) {
93             return mLocalPhysicalAddress;
94         }
95     }
96 
97     private final Object mLock = new Object();
98 
99     /**
100      * Broadcast Action: Display OSD message.
101      * <p>Send when the service has a message to display on screen for events
102      * that need user's attention such as ARC status change.
103      * <p>Always contains the extra fields {@link #EXTRA_MESSAGE_ID}.
104      * <p>Requires {@link android.Manifest.permission#HDMI_CEC} to receive.
105      */
106     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
107     public static final String ACTION_OSD_MESSAGE = "android.hardware.hdmi.action.OSD_MESSAGE";
108 
109     // --- Messages for ACTION_OSD_MESSAGE ---
110     /**
111      * Message that ARC enabled device is connected to invalid port (non-ARC port).
112      */
113     public static final int OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT = 1;
114 
115     /**
116      * Message used by TV to receive volume status from Audio Receiver. It should check volume value
117      * that is retrieved from extra value with the key {@link #EXTRA_MESSAGE_EXTRA_PARAM1}. If the
118      * value is in range of [0,100], it is current volume of Audio Receiver. And there is another
119      * value, {@link #AVR_VOLUME_MUTED}, which is used to inform volume mute.
120      */
121     public static final int OSD_MESSAGE_AVR_VOLUME_CHANGED = 2;
122 
123     /**
124      * Used as an extra field in the intent {@link #ACTION_OSD_MESSAGE}. Contains the ID of
125      * the message to display on screen.
126      */
127     public static final String EXTRA_MESSAGE_ID = "android.hardware.hdmi.extra.MESSAGE_ID";
128     /**
129      * Used as an extra field in the intent {@link #ACTION_OSD_MESSAGE}. Contains the extra value
130      * of the message.
131      */
132     public static final String EXTRA_MESSAGE_EXTRA_PARAM1 =
133             "android.hardware.hdmi.extra.MESSAGE_EXTRA_PARAM1";
134 
135     /**
136      * Volume value for mute state.
137      */
138     public static final int AVR_VOLUME_MUTED = 101;
139 
140     public static final int POWER_STATUS_UNKNOWN = -1;
141     public static final int POWER_STATUS_ON = 0;
142     public static final int POWER_STATUS_STANDBY = 1;
143     public static final int POWER_STATUS_TRANSIENT_TO_ON = 2;
144     public static final int POWER_STATUS_TRANSIENT_TO_STANDBY = 3;
145 
146     /** @hide */
147     @SystemApi
148     @IntDef ({
149         RESULT_SUCCESS,
150         RESULT_TIMEOUT,
151         RESULT_SOURCE_NOT_AVAILABLE,
152         RESULT_TARGET_NOT_AVAILABLE,
153         RESULT_ALREADY_IN_PROGRESS,
154         RESULT_EXCEPTION,
155         RESULT_INCORRECT_MODE,
156         RESULT_COMMUNICATION_FAILED,
157     })
158     public @interface ControlCallbackResult {}
159 
160     /** Control operation is successfully handled by the framework. */
161     public static final int RESULT_SUCCESS = 0;
162     public static final int RESULT_TIMEOUT = 1;
163     /** Source device that the application is using is not available. */
164     public static final int RESULT_SOURCE_NOT_AVAILABLE = 2;
165     /** Target device that the application is controlling is not available. */
166     public static final int RESULT_TARGET_NOT_AVAILABLE = 3;
167 
168     @Deprecated public static final int RESULT_ALREADY_IN_PROGRESS = 4;
169     public static final int RESULT_EXCEPTION = 5;
170     public static final int RESULT_INCORRECT_MODE = 6;
171     public static final int RESULT_COMMUNICATION_FAILED = 7;
172 
173     public static final int DEVICE_EVENT_ADD_DEVICE = 1;
174     public static final int DEVICE_EVENT_REMOVE_DEVICE = 2;
175     public static final int DEVICE_EVENT_UPDATE_DEVICE = 3;
176 
177     // --- One Touch Recording success result
178     /** Recording currently selected source. Indicates the status of a recording. */
179     public static final int ONE_TOUCH_RECORD_RECORDING_CURRENTLY_SELECTED_SOURCE = 0x01;
180     /** Recording Digital Service. Indicates the status of a recording. */
181     public static final int ONE_TOUCH_RECORD_RECORDING_DIGITAL_SERVICE = 0x02;
182     /** Recording Analogue Service. Indicates the status of a recording. */
183     public static final int ONE_TOUCH_RECORD_RECORDING_ANALOGUE_SERVICE = 0x03;
184     /** Recording External input. Indicates the status of a recording. */
185     public static final int ONE_TOUCH_RECORD_RECORDING_EXTERNAL_INPUT = 0x04;
186 
187     // --- One Touch Record failure result
188     /** No recording – unable to record Digital Service. No suitable tuner. */
189     public static final int ONE_TOUCH_RECORD_UNABLE_DIGITAL_SERVICE = 0x05;
190     /** No recording – unable to record Analogue Service. No suitable tuner. */
191     public static final int ONE_TOUCH_RECORD_UNABLE_ANALOGUE_SERVICE = 0x06;
192     /**
193      * No recording – unable to select required service. as suitable tuner, but the requested
194      * parameters are invalid or out of range for that tuner.
195      */
196     public static final int ONE_TOUCH_RECORD_UNABLE_SELECTED_SERVICE = 0x07;
197     /** No recording – invalid External plug number */
198     public static final int ONE_TOUCH_RECORD_INVALID_EXTERNAL_PLUG_NUMBER = 0x09;
199     /** No recording – invalid External Physical Address */
200     public static final int ONE_TOUCH_RECORD_INVALID_EXTERNAL_PHYSICAL_ADDRESS = 0x0A;
201     /** No recording – CA system not supported */
202     public static final int ONE_TOUCH_RECORD_UNSUPPORTED_CA = 0x0B;
203     /** No Recording – No or Insufficient CA Entitlements” */
204     public static final int ONE_TOUCH_RECORD_NO_OR_INSUFFICIENT_CA_ENTITLEMENTS = 0x0C;
205     /** No recording – Not allowed to copy source. Source is “copy never”. */
206     public static final int ONE_TOUCH_RECORD_DISALLOW_TO_COPY = 0x0D;
207     /** No recording – No further copies allowed */
208     public static final int ONE_TOUCH_RECORD_DISALLOW_TO_FUTHER_COPIES = 0x0E;
209     /** No recording – No media */
210     public static final int ONE_TOUCH_RECORD_NO_MEDIA = 0x10;
211     /** No recording – playing */
212     public static final int ONE_TOUCH_RECORD_PLAYING = 0x11;
213     /** No recording – already recording */
214     public static final int ONE_TOUCH_RECORD_ALREADY_RECORDING = 0x12;
215     /** No recording – media protected */
216     public static final int ONE_TOUCH_RECORD_MEDIA_PROTECTED = 0x13;
217     /** No recording – no source signal */
218     public static final int ONE_TOUCH_RECORD_NO_SOURCE_SIGNAL = 0x14;
219     /** No recording – media problem */
220     public static final int ONE_TOUCH_RECORD_MEDIA_PROBLEM = 0x15;
221     /** No recording – not enough space available */
222     public static final int ONE_TOUCH_RECORD_NOT_ENOUGH_SPACE = 0x16;
223     /** No recording – Parental Lock On */
224     public static final int ONE_TOUCH_RECORD_PARENT_LOCK_ON = 0x17;
225     /** Recording terminated normally */
226     public static final int ONE_TOUCH_RECORD_RECORDING_TERMINATED_NORMALLY = 0x1A;
227     /** Recording has already terminated */
228     public static final int ONE_TOUCH_RECORD_RECORDING_ALREADY_TERMINATED = 0x1B;
229     /** No recording – other reason */
230     public static final int ONE_TOUCH_RECORD_OTHER_REASON = 0x1F;
231     // From here extra message for recording that is not mentioned in CEC spec
232     /** No recording. Previous recording request in progress. */
233     public static final int ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS = 0x30;
234     /** No recording. Please check recorder and connection. */
235     public static final int ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION = 0x31;
236     /** Cannot record currently displayed source. */
237     public static final int ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN = 0x32;
238     /** CEC is disabled. */
239     public static final int ONE_TOUCH_RECORD_CEC_DISABLED = 0x33;
240 
241     // --- Types for timer recording
242     /** Timer recording type for digital service source. */
243     public static final int TIMER_RECORDING_TYPE_DIGITAL = 1;
244     /** Timer recording type for analogue service source. */
245     public static final int TIMER_RECORDING_TYPE_ANALOGUE = 2;
246     /** Timer recording type for external source. */
247     public static final int TIMER_RECORDING_TYPE_EXTERNAL = 3;
248 
249     // --- Timer Status Data
250     /** [Timer Status Data/Media Info] - Media present and not protected. */
251     public static final int TIMER_STATUS_MEDIA_INFO_PRESENT_NOT_PROTECTED = 0x0;
252     /** [Timer Status Data/Media Info] - Media present, but protected. */
253     public static final int TIMER_STATUS_MEDIA_INFO_PRESENT_PROTECTED = 0x1;
254     /** [Timer Status Data/Media Info] - Media not present. */
255     public static final int TIMER_STATUS_MEDIA_INFO_NOT_PRESENT = 0x2;
256 
257     /** [Timer Status Data/Programmed Info] - Enough space available for recording. */
258     public static final int TIMER_STATUS_PROGRAMMED_INFO_ENOUGH_SPACE = 0x8;
259     /** [Timer Status Data/Programmed Info] - Not enough space available for recording. */
260     public static final int TIMER_STATUS_PROGRAMMED_INFO_NOT_ENOUGH_SPACE = 0x9;
261     /** [Timer Status Data/Programmed Info] - Might not enough space available for recording. */
262     public static final int TIMER_STATUS_PROGRAMMED_INFO_MIGHT_NOT_ENOUGH_SPACE = 0xB;
263     /** [Timer Status Data/Programmed Info] - No media info available. */
264     public static final int TIMER_STATUS_PROGRAMMED_INFO_NO_MEDIA_INFO = 0xA;
265 
266     /** [Timer Status Data/Not Programmed Error Info] - No free timer available. */
267     public static final int TIMER_STATUS_NOT_PROGRAMMED_NO_FREE_TIME = 0x1;
268     /** [Timer Status Data/Not Programmed Error Info] - Date out of range. */
269     public static final int TIMER_STATUS_NOT_PROGRAMMED_DATE_OUT_OF_RANGE = 0x2;
270     /** [Timer Status Data/Not Programmed Error Info] - Recording Sequence error. */
271     public static final int TIMER_STATUS_NOT_PROGRAMMED_INVALID_SEQUENCE = 0x3;
272     /** [Timer Status Data/Not Programmed Error Info] - Invalid External Plug Number. */
273     public static final int TIMER_STATUS_NOT_PROGRAMMED_INVALID_EXTERNAL_PLUG_NUMBER = 0x4;
274     /** [Timer Status Data/Not Programmed Error Info] - Invalid External Physical Address. */
275     public static final int TIMER_STATUS_NOT_PROGRAMMED_INVALID_EXTERNAL_PHYSICAL_NUMBER = 0x5;
276     /** [Timer Status Data/Not Programmed Error Info] - CA system not supported. */
277     public static final int TIMER_STATUS_NOT_PROGRAMMED_CA_NOT_SUPPORTED = 0x6;
278     /** [Timer Status Data/Not Programmed Error Info] - No or insufficient CA Entitlements. */
279     public static final int TIMER_STATUS_NOT_PROGRAMMED_NO_CA_ENTITLEMENTS = 0x7;
280     /** [Timer Status Data/Not Programmed Error Info] - Does not support resolution. */
281     public static final int TIMER_STATUS_NOT_PROGRAMMED_UNSUPPORTED_RESOLUTION = 0x8;
282     /** [Timer Status Data/Not Programmed Error Info] - Parental Lock On. */
283     public static final int TIMER_STATUS_NOT_PROGRAMMED_PARENTAL_LOCK_ON= 0x9;
284     /** [Timer Status Data/Not Programmed Error Info] - Clock Failure. */
285     public static final int TIMER_STATUS_NOT_PROGRAMMED_CLOCK_FAILURE = 0xA;
286     /** [Timer Status Data/Not Programmed Error Info] - Duplicate: already programmed. */
287     public static final int TIMER_STATUS_NOT_PROGRAMMED_DUPLICATED = 0xE;
288 
289     // --- Extra result value for timer recording.
290     /** No extra error. */
291     public static final int TIMER_RECORDING_RESULT_EXTRA_NO_ERROR = 0x00;
292     /** No timer recording - check recorder and connection. */
293     public static final int TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION = 0x01;
294     /** No timer recording - cannot record selected source. */
295     public static final int TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE = 0x02;
296     /** CEC is disabled. */
297     public static final int TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED = 0x03;
298 
299     // -- Timer cleared status data code used for result of onClearTimerRecordingResult.
300     /** Timer not cleared – recording. */
301     public static final int CLEAR_TIMER_STATUS_TIMER_NOT_CLEARED_RECORDING = 0x00;
302     /** Timer not cleared – no matching. */
303     public static final int CLEAR_TIMER_STATUS_TIMER_NOT_CLEARED_NO_MATCHING = 0x01;
304     /** Timer not cleared – no info available. */
305     public static final int CLEAR_TIMER_STATUS_TIMER_NOT_CLEARED_NO_INFO_AVAILABLE = 0x02;
306     /** Timer cleared. */
307     public static final int CLEAR_TIMER_STATUS_TIMER_CLEARED = 0x80;
308     /** Clear timer error - check recorder and connection. */
309     public static final int CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION = 0xA0;
310     /** Clear timer error - cannot clear timer for selected source. */
311     public static final int CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE = 0xA1;
312     /** Clear timer error - CEC is disabled. */
313     public static final int CLEAR_TIMER_STATUS_CEC_DISABLE = 0xA2;
314 
315     /** The HdmiControlService is started. */
316     public static final int CONTROL_STATE_CHANGED_REASON_START = 0;
317     /** The state of HdmiControlService is changed by changing of settings. */
318     public static final int CONTROL_STATE_CHANGED_REASON_SETTING = 1;
319     /** The HdmiControlService is enabled to wake up. */
320     public static final int CONTROL_STATE_CHANGED_REASON_WAKEUP = 2;
321     /** The HdmiControlService will be disabled to standby. */
322     public static final int CONTROL_STATE_CHANGED_REASON_STANDBY = 3;
323 
324     // -- Whether the HDMI CEC is enabled or disabled.
325     /**
326      * HDMI CEC enabled.
327      *
328      * @hide
329      */
330     @SystemApi
331     public static final int HDMI_CEC_CONTROL_ENABLED = 1;
332     /**
333      * HDMI CEC disabled.
334      *
335      * @hide
336      */
337     @SystemApi
338     public static final int HDMI_CEC_CONTROL_DISABLED = 0;
339     /**
340      * @hide
341      */
342     @IntDef(prefix = { "HDMI_CEC_CONTROL_" }, value = {
343             HDMI_CEC_CONTROL_ENABLED,
344             HDMI_CEC_CONTROL_DISABLED
345     })
346     @Retention(RetentionPolicy.SOURCE)
347     public @interface HdmiCecControl {}
348 
349     // -- Supported HDM-CEC versions.
350     /**
351      * Version constant for HDMI-CEC v1.4b.
352      *
353      * @hide
354      */
355     @SystemApi
356     public static final int HDMI_CEC_VERSION_1_4_B = 0x05;
357     /**
358      * Version constant for HDMI-CEC v2.0.
359      *
360      * @hide
361      */
362     @SystemApi
363     public static final int HDMI_CEC_VERSION_2_0 = 0x06;
364     /**
365      * @hide
366      */
367     @IntDef(prefix = { "HDMI_CEC_VERSION_" }, value = {
368             HDMI_CEC_VERSION_1_4_B,
369             HDMI_CEC_VERSION_2_0
370     })
371     @Retention(RetentionPolicy.SOURCE)
372     public @interface HdmiCecVersion {}
373 
374     // -- Scope of CEC power control messages sent by a playback device.
375     /**
376      * Send CEC power control messages to TV only.
377      *
378      * @hide
379      */
380     @SystemApi
381     public static final String POWER_CONTROL_MODE_TV = "to_tv";
382     /**
383      * Broadcast CEC power control messages to all devices in the network.
384      *
385      * @hide
386      */
387     @SystemApi
388     public static final String POWER_CONTROL_MODE_BROADCAST = "broadcast";
389     /**
390      * Don't send any CEC power control messages.
391      *
392      * @hide
393      */
394     @SystemApi
395     public static final String POWER_CONTROL_MODE_NONE = "none";
396     /**
397      * @hide
398      */
399     @StringDef(prefix = { "POWER_CONTROL_MODE_" }, value = {
400             POWER_CONTROL_MODE_TV,
401             POWER_CONTROL_MODE_BROADCAST,
402             POWER_CONTROL_MODE_NONE
403     })
404     @Retention(RetentionPolicy.SOURCE)
405     public @interface PowerControlMode {}
406 
407     // -- Which power state action should be taken when Active Source is lost.
408     /**
409      * No action to be taken.
410      *
411      * @hide
412      */
413     @SystemApi
414     public static final String POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_NONE = "none";
415     /**
416      * Go to standby immediately.
417      *
418      * @hide
419      */
420     @SystemApi
421     public static final String POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW = "standby_now";
422     /**
423      * @hide
424      */
425     @StringDef(prefix = { "POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_" }, value = {
426             POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_NONE,
427             POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST_STANDBY_NOW
428     })
429     @Retention(RetentionPolicy.SOURCE)
430     public @interface ActiveSourceLostBehavior {}
431 
432     // -- Whether System Audio Mode muting is enabled or disabled.
433     /**
434      * System Audio Mode muting enabled.
435      *
436      * @hide
437      */
438     @SystemApi
439     public static final int SYSTEM_AUDIO_MODE_MUTING_ENABLED = 1;
440     /**
441      * System Audio Mode muting disabled.
442      *
443      * @hide
444      */
445     @SystemApi
446     public static final int SYSTEM_AUDIO_MODE_MUTING_DISABLED = 0;
447     /**
448      * @hide
449      */
450     @IntDef(prefix = { "SYSTEM_AUDIO_MODE_MUTING_" }, value = {
451             SYSTEM_AUDIO_MODE_MUTING_ENABLED,
452             SYSTEM_AUDIO_MODE_MUTING_DISABLED
453     })
454     @Retention(RetentionPolicy.SOURCE)
455     public @interface SystemAudioModeMuting {}
456 
457     // -- Whether the HDMI CEC volume control is enabled or disabled.
458     /**
459      * HDMI CEC enabled.
460      *
461      * @see HdmiControlManager#CEC_SETTING_NAME_VOLUME_CONTROL_MODE
462      * @hide
463      */
464     @SystemApi
465     public static final int VOLUME_CONTROL_ENABLED = 1;
466     /**
467      * HDMI CEC disabled.
468      *
469      * @see HdmiControlManager#CEC_SETTING_NAME_VOLUME_CONTROL_MODE
470      * @hide
471      */
472     @SystemApi
473     public static final int VOLUME_CONTROL_DISABLED = 0;
474     /**
475      * @see HdmiControlManager#CEC_SETTING_NAME_VOLUME_CONTROL_MODE
476      * @hide
477      */
478     @IntDef(prefix = { "VOLUME_CONTROL_" }, value = {
479             VOLUME_CONTROL_ENABLED,
480             VOLUME_CONTROL_DISABLED
481     })
482     @Retention(RetentionPolicy.SOURCE)
483     public @interface VolumeControl {}
484 
485     // -- Whether TV Wake on One Touch Play is enabled or disabled.
486     /**
487      * TV Wake on One Touch Play enabled.
488      *
489      * @hide
490      */
491     @SystemApi
492     public static final int TV_WAKE_ON_ONE_TOUCH_PLAY_ENABLED = 1;
493     /**
494      * TV Wake on One Touch Play disabled.
495      *
496      * @hide
497      */
498     @SystemApi
499     public static final int TV_WAKE_ON_ONE_TOUCH_PLAY_DISABLED = 0;
500     /**
501      * @hide
502      */
503     @IntDef(prefix = { "TV_WAKE_ON_ONE_TOUCH_PLAY_" }, value = {
504             TV_WAKE_ON_ONE_TOUCH_PLAY_ENABLED,
505             TV_WAKE_ON_ONE_TOUCH_PLAY_DISABLED
506     })
507     @Retention(RetentionPolicy.SOURCE)
508     public @interface TvWakeOnOneTouchPlay {}
509 
510     // -- Whether TV should send &lt;Standby&gt; on sleep.
511     /**
512      * Sending &lt;Standby&gt; on sleep.
513      *
514      * @hide
515      */
516     @SystemApi
517     public static final int TV_SEND_STANDBY_ON_SLEEP_ENABLED = 1;
518     /**
519      * Not sending &lt;Standby&gt; on sleep.
520      *
521      * @hide
522      */
523     @SystemApi
524     public static final int TV_SEND_STANDBY_ON_SLEEP_DISABLED = 0;
525     /**
526      * @hide
527      */
528     @IntDef(prefix = { "TV_SEND_STANDBY_ON_SLEEP_" }, value = {
529             TV_SEND_STANDBY_ON_SLEEP_ENABLED,
530             TV_SEND_STANDBY_ON_SLEEP_DISABLED
531     })
532     @Retention(RetentionPolicy.SOURCE)
533     public @interface TvSendStandbyOnSleep {}
534 
535     // -- The RC profile of a TV panel.
536     /**
537      * RC profile none.
538      *
539      * @hide
540      */
541     public static final int RC_PROFILE_TV_NONE = 0x0;
542     /**
543      * RC profile 1.
544      *
545      * @hide
546      */
547     public static final int RC_PROFILE_TV_ONE = 0x2;
548     /**
549      * RC profile 2.
550      *
551      * @hide
552      */
553     public static final int RC_PROFILE_TV_TWO = 0x6;
554     /**
555      * RC profile 3.
556      *
557      * @hide
558      */
559     public static final int RC_PROFILE_TV_THREE = 0xA;
560     /**
561      * RC profile 4.
562      *
563      * @hide
564      */
565     public static final int RC_PROFILE_TV_FOUR = 0xE;
566     /**
567      * @hide
568      */
569     @IntDef(prefix = { "RC_PROFILE_TV_" }, value = {
570             RC_PROFILE_TV_NONE,
571             RC_PROFILE_TV_ONE,
572             RC_PROFILE_TV_TWO,
573             RC_PROFILE_TV_THREE,
574             RC_PROFILE_TV_FOUR
575     })
576     @Retention(RetentionPolicy.SOURCE)
577     public @interface RcProfileTv {}
578 
579     // -- RC profile parameter defining if a source handles the root menu.
580     /**
581      * Handles the root menu.
582      *
583      * @hide
584      */
585     public static final int RC_PROFILE_SOURCE_ROOT_MENU_HANDLED = 1;
586     /**
587      * Doesn't handle the root menu.
588      *
589      * @hide
590      */
591     public static final int RC_PROFILE_SOURCE_ROOT_MENU_NOT_HANDLED = 0;
592     /**
593      * @hide
594      */
595     @IntDef(prefix = { "RC_PROFILE_SOURCE_ROOT_MENU_" }, value = {
596             RC_PROFILE_SOURCE_ROOT_MENU_HANDLED,
597             RC_PROFILE_SOURCE_ROOT_MENU_NOT_HANDLED
598     })
599     @Retention(RetentionPolicy.SOURCE)
600     public @interface RcProfileSourceHandlesRootMenu {}
601 
602     // -- RC profile parameter defining if a source handles the setup menu.
603     /**
604      * Handles the setup menu.
605      *
606      * @hide
607      */
608     public static final int RC_PROFILE_SOURCE_SETUP_MENU_HANDLED = 1;
609     /**
610      * Doesn't handle the setup menu.
611      *
612      * @hide
613      */
614     public static final int RC_PROFILE_SOURCE_SETUP_MENU_NOT_HANDLED = 0;
615     /**
616      * @hide
617      */
618     @IntDef(prefix = { "RC_PROFILE_SOURCE_SETUP_MENU_" }, value = {
619             RC_PROFILE_SOURCE_SETUP_MENU_HANDLED,
620             RC_PROFILE_SOURCE_SETUP_MENU_NOT_HANDLED
621     })
622     @Retention(RetentionPolicy.SOURCE)
623     public @interface RcProfileSourceHandlesSetupMenu {}
624 
625 
626     // -- RC profile parameter defining if a source handles the contents menu.
627     /**
628      * Handles the contents menu.
629      *
630      * @hide
631      */
632     public static final int RC_PROFILE_SOURCE_CONTENTS_MENU_HANDLED = 1;
633     /**
634      * Doesn't handle the contents menu.
635      *
636      * @hide
637      */
638     public static final int RC_PROFILE_SOURCE_CONTENTS_MENU_NOT_HANDLED = 0;
639     /**
640      * @hide
641      */
642     @IntDef(prefix = { "RC_PROFILE_SOURCE_CONTENTS_MENU_" }, value = {
643             RC_PROFILE_SOURCE_CONTENTS_MENU_HANDLED,
644             RC_PROFILE_SOURCE_CONTENTS_MENU_NOT_HANDLED
645     })
646     @Retention(RetentionPolicy.SOURCE)
647     public @interface RcProfileSourceHandlesContentsMenu {}
648 
649 
650     // -- RC profile parameter defining if a source handles the top menu.
651     /**
652      * Handles the top menu.
653      *
654      * @hide
655      */
656     public static final int RC_PROFILE_SOURCE_TOP_MENU_HANDLED = 1;
657     /**
658      * Doesn't handle the top menu.
659      *
660      * @hide
661      */
662     public static final int RC_PROFILE_SOURCE_TOP_MENU_NOT_HANDLED = 0;
663     /**
664      * @hide
665      */
666     @IntDef(prefix = { "RC_PROFILE_SOURCE_TOP_MENU_" }, value = {
667             RC_PROFILE_SOURCE_TOP_MENU_HANDLED,
668             RC_PROFILE_SOURCE_TOP_MENU_NOT_HANDLED
669     })
670     @Retention(RetentionPolicy.SOURCE)
671     public @interface RcProfileSourceHandlesTopMenu {}
672 
673 
674     // -- RC profile parameter defining if a source handles the media context sensitive menu.
675     /**
676      * Handles the media context sensitive menu.
677      *
678      * @hide
679      */
680     public static final int RC_PROFILE_SOURCE_MEDIA_CONTEXT_SENSITIVE_MENU_HANDLED = 1;
681     /**
682      * Doesn't handle the media context sensitive menu.
683      *
684      * @hide
685      */
686     public static final int RC_PROFILE_SOURCE_MEDIA_CONTEXT_SENSITIVE_MENU_NOT_HANDLED = 0;
687     /**
688      * @hide
689      */
690     @IntDef(prefix = { "RC_PROFILE_SOURCE_MEDIA_CONTEXT_SENSITIVE_" }, value = {
691             RC_PROFILE_SOURCE_MEDIA_CONTEXT_SENSITIVE_MENU_HANDLED,
692             RC_PROFILE_SOURCE_MEDIA_CONTEXT_SENSITIVE_MENU_NOT_HANDLED
693     })
694     @Retention(RetentionPolicy.SOURCE)
695     public @interface RcProfileSourceHandlesMediaContextSensitiveMenu {}
696 
697     // -- Settings available in the CEC Configuration.
698     /**
699      * Name of a setting deciding whether the CEC is enabled.
700      *
701      * @hide
702      */
703     @SystemApi
704     public static final String CEC_SETTING_NAME_HDMI_CEC_ENABLED = "hdmi_cec_enabled";
705     /**
706      * Name of a setting controlling the version of HDMI-CEC used.
707      *
708      * @hide
709      */
710     @SystemApi
711     public static final String CEC_SETTING_NAME_HDMI_CEC_VERSION = "hdmi_cec_version";
712     /**
713      * Name of a setting deciding on the power control mode.
714      *
715      * @hide
716      */
717     @SystemApi
718     public static final String CEC_SETTING_NAME_POWER_CONTROL_MODE = "power_control_mode";
719     /**
720      * Name of a setting deciding on power state action when losing Active Source.
721      *
722      * @hide
723      */
724     @SystemApi
725     public static final String CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST =
726             "power_state_change_on_active_source_lost";
727     /**
728      * Name of a setting deciding whether System Audio Muting is allowed.
729      *
730      * @hide
731      */
732     @SystemApi
733     public static final String CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING =
734             "system_audio_mode_muting";
735     /**
736      * Controls whether volume control commands via HDMI CEC are enabled.
737      *
738      * <p>Effects on different device types:
739      * <table>
740      *     <tr><th>HDMI CEC device type</th><th>0: disabled</th><th>1: enabled</th></tr>
741      *     <tr>
742      *         <td>TV (type: 0)</td>
743      *         <td>Per CEC specification.</td>
744      *         <td>TV changes system volume. TV no longer reacts to incoming volume changes
745      *         via {@code <User Control Pressed>}. TV no longer handles {@code <Report Audio
746      *         Status>}.</td>
747      *     </tr>
748      *     <tr>
749      *         <td>Playback device (type: 4)</td>
750      *         <td>Device sends volume commands to TV/Audio system via {@code <User Control
751      *         Pressed>}</td>
752      *         <td>Device does not send volume commands via {@code <User Control Pressed>}.</td>
753      *     </tr>
754      *     <tr>
755      *         <td>Audio device (type: 5)</td>
756      *         <td>Full "System Audio Control" capabilities.</td>
757      *         <td>Audio device no longer reacts to incoming {@code <User Control Pressed>}
758      *         volume commands. Audio device no longer reports volume changes via {@code
759      *         <Report Audio Status>}.</td>
760      *     </tr>
761      * </table>
762      *
763      * <p> Due to the resulting behavior, usage on TV and Audio devices is discouraged.
764      *
765      * @hide
766      * @see android.hardware.hdmi.HdmiControlManager#setHdmiCecVolumeControlEnabled(int)
767      */
768     @SystemApi
769     public static final String CEC_SETTING_NAME_VOLUME_CONTROL_MODE =
770             "volume_control_enabled";
771     /**
772      * Name of a setting deciding whether the TV will automatically turn on upon reception
773      * of the CEC command &lt;Text View On&gt; or &lt;Image View On&gt;.
774      *
775      * @hide
776      */
777     @SystemApi
778     public static final String CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY =
779             "tv_wake_on_one_touch_play";
780     /**
781      * Name of a setting deciding whether the device will also turn off other CEC devices
782      * when it goes to standby mode.
783      *
784      * @hide
785      */
786     @SystemApi
787     public static final String CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP =
788             "tv_send_standby_on_sleep";
789     /**
790      * Name of a setting representing the RC profile of a TV panel.
791      *
792      * @hide
793      */
794     public static final String CEC_SETTING_NAME_RC_PROFILE_TV =
795             "rc_profile_tv";
796     /**
797      * Name of a setting representing the RC profile parameter defining if a source handles the root
798      * menu.
799      *
800      * @hide
801      */
802     public static final String CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU =
803             "rc_profile_source_handles_root_menu";
804     /**
805      * Name of a setting representing the RC profile parameter defining if a source handles the
806      * setup menu.
807      *
808      * @hide
809      */
810     public static final String CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU =
811             "rc_profile_source_handles_setup_menu";
812     /**
813      * Name of a setting representing the RC profile parameter defining if a source handles the
814      * contents menu.
815      *
816      * @hide
817      */
818     public static final String CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_CONTENTS_MENU =
819             "rc_profile_source_handles_contents_menu";
820     /**
821      * Name of a setting representing the RC profile parameter defining if a source handles the top
822      * menu.
823      *
824      * @hide
825      */
826     public static final String CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_TOP_MENU =
827             "rc_profile_source_handles_top_menu";
828     /**
829      * Name of a setting representing the RC profile parameter defining if a source handles the
830      * media context sensitive menu.
831      *
832      * @hide
833      */
834     public static final String
835             CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU =
836             "rc_profile_source_handles_media_context_sensitive_menu";
837     /**
838      * @hide
839      */
840     @StringDef(prefix = { "CEC_SETTING_NAME_" }, value = {
841         CEC_SETTING_NAME_HDMI_CEC_ENABLED,
842         CEC_SETTING_NAME_HDMI_CEC_VERSION,
843         CEC_SETTING_NAME_POWER_CONTROL_MODE,
844         CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST,
845         CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING,
846         CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
847         CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY,
848         CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP,
849         CEC_SETTING_NAME_RC_PROFILE_TV,
850         CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_ROOT_MENU,
851         CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_SETUP_MENU,
852         CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_CONTENTS_MENU,
853         CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_TOP_MENU,
854         CEC_SETTING_NAME_RC_PROFILE_SOURCE_HANDLES_MEDIA_CONTEXT_SENSITIVE_MENU,
855     })
856     public @interface CecSettingName {}
857 
858     // True if we have a logical device of type playback hosted in the system.
859     private final boolean mHasPlaybackDevice;
860     // True if we have a logical device of type TV hosted in the system.
861     private final boolean mHasTvDevice;
862     // True if we have a logical device of type audio system hosted in the system.
863     private final boolean mHasAudioSystemDevice;
864     // True if we have a logical device of type audio system hosted in the system.
865     private final boolean mHasSwitchDevice;
866     // True if it's a switch device.
867     private final boolean mIsSwitchDevice;
868 
869     /**
870      * {@hide} - hide this constructor because it has a parameter of type IHdmiControlService,
871      * which is a system private class. The right way to create an instance of this class is
872      * using the factory Context.getSystemService.
873      */
HdmiControlManager(IHdmiControlService service)874     public HdmiControlManager(IHdmiControlService service) {
875         mService = service;
876         int[] types = null;
877         if (mService != null) {
878             try {
879                 types = mService.getSupportedTypes();
880             } catch (RemoteException e) {
881                 throw e.rethrowFromSystemServer();
882             }
883         }
884         mHasTvDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_TV);
885         mHasPlaybackDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_PLAYBACK);
886         mHasAudioSystemDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
887         mHasSwitchDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH);
888         mIsSwitchDevice = HdmiProperties.is_switch().orElse(false);
889         addHotplugEventListener(new ClientHotplugEventListener());
890     }
891 
892     private final class ClientHotplugEventListener implements HotplugEventListener {
893 
894         @Override
onReceived(HdmiHotplugEvent event)895         public void onReceived(HdmiHotplugEvent event) {
896             List<HdmiPortInfo> ports = new ArrayList<>();
897             try {
898                 ports = mService.getPortInfo();
899             } catch (RemoteException e) {
900                 throw e.rethrowFromSystemServer();
901             }
902             if (ports.isEmpty()) {
903                 Log.e(TAG, "Can't find port info, not updating connected status. "
904                         + "Hotplug event:" + event);
905                 return;
906             }
907             // If the HDMI OUT port is plugged or unplugged, update the mLocalPhysicalAddress
908             for (HdmiPortInfo port : ports) {
909                 if (port.getId() == event.getPort()) {
910                     if (port.getType() == HdmiPortInfo.PORT_OUTPUT) {
911                         setLocalPhysicalAddress(
912                                 event.isConnected()
913                                         ? port.getAddress()
914                                         : INVALID_PHYSICAL_ADDRESS);
915                     }
916                     break;
917                 }
918             }
919         }
920     }
921 
hasDeviceType(int[] types, int type)922     private static boolean hasDeviceType(int[] types, int type) {
923         if (types == null) {
924             return false;
925         }
926         for (int t : types) {
927             if (t == type) {
928                 return true;
929             }
930         }
931         return false;
932     }
933 
934     /**
935      * Gets an object that represents an HDMI-CEC logical device of a specified type.
936      *
937      * @param type CEC device type
938      * @return {@link HdmiClient} instance. {@code null} on failure.
939      * See {@link HdmiDeviceInfo#DEVICE_PLAYBACK}
940      * See {@link HdmiDeviceInfo#DEVICE_TV}
941      * See {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM}
942      *
943      * @hide
944      */
945     @Nullable
946     @SystemApi
947     @SuppressLint("RequiresPermission")
getClient(int type)948     public HdmiClient getClient(int type) {
949         if (mService == null) {
950             return null;
951         }
952         switch (type) {
953             case HdmiDeviceInfo.DEVICE_TV:
954                 return mHasTvDevice ? new HdmiTvClient(mService) : null;
955             case HdmiDeviceInfo.DEVICE_PLAYBACK:
956                 return mHasPlaybackDevice ? new HdmiPlaybackClient(mService) : null;
957             case HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM:
958                 return mHasAudioSystemDevice ? new HdmiAudioSystemClient(mService) : null;
959             case HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH:
960                 return (mHasSwitchDevice || mIsSwitchDevice)
961                     ? new HdmiSwitchClient(mService) : null;
962             default:
963                 return null;
964         }
965     }
966 
967     /**
968      * Gets an object that represents an HDMI-CEC logical device of type playback on the system.
969      *
970      * <p>Used to send HDMI control messages to other devices like TV or audio amplifier through
971      * HDMI bus. It is also possible to communicate with other logical devices hosted in the same
972      * system if the system is configured to host more than one type of HDMI-CEC logical devices.
973      *
974      * @return {@link HdmiPlaybackClient} instance. {@code null} on failure.
975      *
976      * @hide
977      */
978     @Nullable
979     @SystemApi
980     @SuppressLint("RequiresPermission")
getPlaybackClient()981     public HdmiPlaybackClient getPlaybackClient() {
982         return (HdmiPlaybackClient) getClient(HdmiDeviceInfo.DEVICE_PLAYBACK);
983     }
984 
985     /**
986      * Gets an object that represents an HDMI-CEC logical device of type TV on the system.
987      *
988      * <p>Used to send HDMI control messages to other devices and manage them through
989      * HDMI bus. It is also possible to communicate with other logical devices hosted in the same
990      * system if the system is configured to host more than one type of HDMI-CEC logical devices.
991      *
992      * @return {@link HdmiTvClient} instance. {@code null} on failure.
993      *
994      * @hide
995      */
996     @Nullable
997     @SystemApi
998     @SuppressLint("RequiresPermission")
getTvClient()999     public HdmiTvClient getTvClient() {
1000         return (HdmiTvClient) getClient(HdmiDeviceInfo.DEVICE_TV);
1001     }
1002 
1003     /**
1004      * Gets an object that represents an HDMI-CEC logical device of type audio system on the system.
1005      *
1006      * <p>Used to send HDMI control messages to other devices like TV through HDMI bus. It is also
1007      * possible to communicate with other logical devices hosted in the same system if the system is
1008      * configured to host more than one type of HDMI-CEC logical devices.
1009      *
1010      * @return {@link HdmiAudioSystemClient} instance. {@code null} on failure.
1011      *
1012      * @hide
1013      */
1014     @Nullable
1015     @SuppressLint("RequiresPermission")
getAudioSystemClient()1016     public HdmiAudioSystemClient getAudioSystemClient() {
1017         return (HdmiAudioSystemClient) getClient(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
1018     }
1019 
1020     /**
1021      * Gets an object that represents an HDMI-CEC logical device of type switch on the system.
1022      *
1023      * <p>Used to send HDMI control messages to other devices (e.g. TVs) through HDMI bus.
1024      * It is also possible to communicate with other logical devices hosted in the same
1025      * system if the system is configured to host more than one type of HDMI-CEC logical device.
1026      *
1027      * @return {@link HdmiSwitchClient} instance. {@code null} on failure.
1028      */
1029     @Nullable
1030     @SuppressLint("RequiresPermission")
getSwitchClient()1031     public HdmiSwitchClient getSwitchClient() {
1032         return (HdmiSwitchClient) getClient(HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH);
1033     }
1034 
1035     /**
1036      * Get a snapshot of the real-time status of the devices on the CEC bus.
1037      *
1038      * <p>This only applies to devices with switch functionality, which are devices with one
1039      * or more than one HDMI inputs.
1040      *
1041      * @return a list of {@link HdmiDeviceInfo} of the connected CEC devices on the CEC bus. An
1042      * empty list will be returned if there is none.
1043      *
1044      * @hide
1045      */
1046     @NonNull
1047     @SystemApi
getConnectedDevices()1048     public List<HdmiDeviceInfo> getConnectedDevices() {
1049         try {
1050             return mService.getDeviceList();
1051         } catch (RemoteException e) {
1052             throw e.rethrowFromSystemServer();
1053         }
1054     }
1055 
1056     /**
1057      * @removed
1058      * @hide
1059      * @deprecated Please use {@link #getConnectedDevices()} instead.
1060      */
1061     @Deprecated
1062     @SystemApi
getConnectedDevicesList()1063     public List<HdmiDeviceInfo> getConnectedDevicesList() {
1064         try {
1065             return mService.getDeviceList();
1066         } catch (RemoteException e) {
1067             throw e.rethrowFromSystemServer();
1068         }
1069     }
1070 
1071     /**
1072      * Power off the target device by sending CEC commands. Note that this device can't be the
1073      * current device itself.
1074      *
1075      * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}.
1076      *
1077      * @param deviceInfo {@link HdmiDeviceInfo} of the device to be powered off.
1078      *
1079      * @hide
1080      */
1081     @SystemApi
powerOffDevice(@onNull HdmiDeviceInfo deviceInfo)1082     public void powerOffDevice(@NonNull HdmiDeviceInfo deviceInfo) {
1083         Objects.requireNonNull(deviceInfo);
1084         try {
1085             mService.powerOffRemoteDevice(
1086                     deviceInfo.getLogicalAddress(), deviceInfo.getDevicePowerStatus());
1087         } catch (RemoteException e) {
1088             throw e.rethrowFromSystemServer();
1089         }
1090     }
1091 
1092     /**
1093      * @removed
1094      * @hide
1095      * @deprecated Please use {@link #powerOffDevice(deviceInfo)} instead.
1096      */
1097     @Deprecated
1098     @SystemApi
powerOffRemoteDevice(@onNull HdmiDeviceInfo deviceInfo)1099     public void powerOffRemoteDevice(@NonNull HdmiDeviceInfo deviceInfo) {
1100         Objects.requireNonNull(deviceInfo);
1101         try {
1102             mService.powerOffRemoteDevice(
1103                     deviceInfo.getLogicalAddress(), deviceInfo.getDevicePowerStatus());
1104         } catch (RemoteException e) {
1105             throw e.rethrowFromSystemServer();
1106         }
1107     }
1108 
1109     /**
1110      * Power on the target device by sending CEC commands. Note that this device can't be the
1111      * current device itself.
1112      *
1113      * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}.
1114      *
1115      * @param deviceInfo {@link HdmiDeviceInfo} of the device to be powered on.
1116      *
1117      * @hide
1118      */
powerOnDevice(HdmiDeviceInfo deviceInfo)1119     public void powerOnDevice(HdmiDeviceInfo deviceInfo) {
1120         Objects.requireNonNull(deviceInfo);
1121         try {
1122             mService.powerOnRemoteDevice(
1123                     deviceInfo.getLogicalAddress(), deviceInfo.getDevicePowerStatus());
1124         } catch (RemoteException e) {
1125             throw e.rethrowFromSystemServer();
1126         }
1127     }
1128 
1129     /**
1130      * @removed
1131      * @hide
1132      * @deprecated Please use {@link #powerOnDevice(deviceInfo)} instead.
1133      */
1134     @Deprecated
1135     @SystemApi
powerOnRemoteDevice(HdmiDeviceInfo deviceInfo)1136     public void powerOnRemoteDevice(HdmiDeviceInfo deviceInfo) {
1137         Objects.requireNonNull(deviceInfo);
1138         try {
1139             mService.powerOnRemoteDevice(
1140                     deviceInfo.getLogicalAddress(), deviceInfo.getDevicePowerStatus());
1141         } catch (RemoteException e) {
1142             throw e.rethrowFromSystemServer();
1143         }
1144     }
1145 
1146     /**
1147      * Request the target device to be the new Active Source by sending CEC commands. Note that
1148      * this device can't be the current device itself.
1149      *
1150      * <p>The target device info can be obtained by calling {@link #getConnectedDevicesList()}.
1151      *
1152      * <p>If the target device responds to the command, the users should see the target device
1153      * streaming on their TVs.
1154      *
1155      * @param deviceInfo HdmiDeviceInfo of the target device
1156      *
1157      * @hide
1158      */
1159     @SystemApi
setActiveSource(@onNull HdmiDeviceInfo deviceInfo)1160     public void setActiveSource(@NonNull HdmiDeviceInfo deviceInfo) {
1161         Objects.requireNonNull(deviceInfo);
1162         try {
1163             mService.askRemoteDeviceToBecomeActiveSource(deviceInfo.getPhysicalAddress());
1164         } catch (RemoteException e) {
1165             throw e.rethrowFromSystemServer();
1166         }
1167     }
1168 
1169     /**
1170      * @removed
1171      * @hide
1172      * @deprecated Please use {@link #setActiveSource(deviceInfo)} instead.
1173      */
1174     @Deprecated
1175     @SystemApi
requestRemoteDeviceToBecomeActiveSource(@onNull HdmiDeviceInfo deviceInfo)1176     public void requestRemoteDeviceToBecomeActiveSource(@NonNull HdmiDeviceInfo deviceInfo) {
1177         Objects.requireNonNull(deviceInfo);
1178         try {
1179             mService.askRemoteDeviceToBecomeActiveSource(deviceInfo.getPhysicalAddress());
1180         } catch (RemoteException e) {
1181             throw e.rethrowFromSystemServer();
1182         }
1183     }
1184 
1185     /**
1186      * Controls standby mode of the system. It will also try to turn on/off the connected devices if
1187      * necessary.
1188      *
1189      * @param isStandbyModeOn target status of the system's standby mode
1190      */
1191     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
setStandbyMode(boolean isStandbyModeOn)1192     public void setStandbyMode(boolean isStandbyModeOn) {
1193         try {
1194             mService.setStandbyMode(isStandbyModeOn);
1195         } catch (RemoteException e) {
1196             throw e.rethrowFromSystemServer();
1197         }
1198     }
1199 
1200     /**
1201      * For CEC source devices (OTT/STB/Audio system): toggle the power status of the HDMI-connected
1202      * display and follow the display's new power status.
1203      * For all other devices: no functionality.
1204      *
1205      * @hide
1206      */
1207     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
toggleAndFollowTvPower()1208     public void toggleAndFollowTvPower() {
1209         try {
1210             mService.toggleAndFollowTvPower();
1211         } catch (RemoteException e) {
1212             throw e.rethrowFromSystemServer();
1213         }
1214     }
1215 
1216     /**
1217      * Determines whether the HDMI CEC stack should handle KEYCODE_TV_POWER.
1218      *
1219      * @hide
1220      */
1221     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
shouldHandleTvPowerKey()1222     public boolean shouldHandleTvPowerKey() {
1223         try {
1224             return mService.shouldHandleTvPowerKey();
1225         } catch (RemoteException e) {
1226             throw e.rethrowFromSystemServer();
1227         }
1228     }
1229 
1230     /**
1231      * Controls whether volume control commands via HDMI CEC are enabled.
1232      *
1233      * <p>When disabled:
1234      * <ul>
1235      *     <li>the device will not send any HDMI CEC audio messages
1236      *     <li>received HDMI CEC audio messages are responded to with {@code <Feature Abort>}
1237      * </ul>
1238      *
1239      * <p>Effects on different device types:
1240      * <table>
1241      *     <tr><th>HDMI CEC device type</th><th>enabled</th><th>disabled</th></tr>
1242      *     <tr>
1243      *         <td>TV (type: 0)</td>
1244      *         <td>Per CEC specification.</td>
1245      *         <td>TV changes system volume. TV no longer reacts to incoming volume changes via
1246      *         {@code <User Control Pressed>}. TV no longer handles {@code <Report Audio Status>}
1247      *         .</td>
1248      *     </tr>
1249      *     <tr>
1250      *         <td>Playback device (type: 4)</td>
1251      *         <td>Device sends volume commands to TV/Audio system via {@code <User Control
1252      *         Pressed>}</td><td>Device does not send volume commands via {@code <User Control
1253      *         Pressed>}.</td>
1254      *     </tr>
1255      *     <tr>
1256      *         <td>Audio device (type: 5)</td>
1257      *         <td>Full "System Audio Control" capabilities.</td>
1258      *         <td>Audio device no longer reacts to incoming {@code <User Control Pressed>}
1259      *         volume commands. Audio device no longer reports volume changes via {@code <Report
1260      *         Audio Status>}.</td>
1261      *     </tr>
1262      * </table>
1263      *
1264      * <p> Due to the resulting behavior, usage on TV and Audio devices is discouraged.
1265      *
1266      * @param hdmiCecVolumeControlEnabled target state of HDMI CEC volume control.
1267      * @see HdmiControlManager#CEC_SETTING_NAME_VOLUME_CONTROL_MODE
1268      * @hide
1269      */
1270     @SystemApi
1271     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
setHdmiCecVolumeControlEnabled( @olumeControl int hdmiCecVolumeControlEnabled)1272     public void setHdmiCecVolumeControlEnabled(
1273             @VolumeControl int hdmiCecVolumeControlEnabled) {
1274         try {
1275             mService.setCecSettingIntValue(CEC_SETTING_NAME_VOLUME_CONTROL_MODE,
1276                     hdmiCecVolumeControlEnabled);
1277         } catch (RemoteException e) {
1278             throw e.rethrowFromSystemServer();
1279         }
1280     }
1281 
1282     /**
1283      * Returns whether volume changes via HDMI CEC are enabled.
1284      * @hide
1285      */
1286     @SystemApi
1287     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
1288     @VolumeControl
getHdmiCecVolumeControlEnabled()1289     public int getHdmiCecVolumeControlEnabled() {
1290         try {
1291             return mService.getCecSettingIntValue(CEC_SETTING_NAME_VOLUME_CONTROL_MODE);
1292         } catch (RemoteException e) {
1293             throw e.rethrowFromSystemServer();
1294         }
1295     }
1296 
1297     /**
1298      * Gets whether the system is in system audio mode.
1299      *
1300      * @hide
1301      */
getSystemAudioMode()1302     public boolean getSystemAudioMode() {
1303         try {
1304             return mService.getSystemAudioMode();
1305         } catch (RemoteException e) {
1306             throw e.rethrowFromSystemServer();
1307         }
1308     }
1309 
1310     /**
1311      * Get the physical address of the device.
1312      *
1313      * <p>Physical address needs to be automatically adjusted when devices are phyiscally or
1314      * electrically added or removed from the device tree. Please see HDMI Specification Version
1315      * 1.4b 8.7 Physical Address for more details on the address discovery proccess.
1316      *
1317      * @hide
1318      */
1319     @SystemApi
getPhysicalAddress()1320     public int getPhysicalAddress() {
1321         return getLocalPhysicalAddress();
1322     }
1323 
1324     /**
1325      * Check if the target device is connected to the current device.
1326      *
1327      * <p>The API also returns true if the current device is the target.
1328      *
1329      * @param targetDevice {@link HdmiDeviceInfo} of the target device.
1330      * @return true if {@code targetDevice} is directly or indirectly
1331      * connected to the current device.
1332      *
1333      * @hide
1334      */
1335     @SystemApi
isDeviceConnected(@onNull HdmiDeviceInfo targetDevice)1336     public boolean isDeviceConnected(@NonNull HdmiDeviceInfo targetDevice) {
1337         Objects.requireNonNull(targetDevice);
1338         int physicalAddress = getLocalPhysicalAddress();
1339         if (physicalAddress == INVALID_PHYSICAL_ADDRESS) {
1340             return false;
1341         }
1342         int targetPhysicalAddress = targetDevice.getPhysicalAddress();
1343         if (targetPhysicalAddress == INVALID_PHYSICAL_ADDRESS) {
1344             return false;
1345         }
1346         return HdmiUtils.getLocalPortFromPhysicalAddress(targetPhysicalAddress, physicalAddress)
1347             != HdmiUtils.TARGET_NOT_UNDER_LOCAL_DEVICE;
1348     }
1349 
1350     /**
1351      * @removed
1352      * @hide
1353      * @deprecated Please use {@link #isDeviceConnected(targetDevice)} instead.
1354      */
1355     @Deprecated
1356     @SystemApi
isRemoteDeviceConnected(@onNull HdmiDeviceInfo targetDevice)1357     public boolean isRemoteDeviceConnected(@NonNull HdmiDeviceInfo targetDevice) {
1358         Objects.requireNonNull(targetDevice);
1359         int physicalAddress = getLocalPhysicalAddress();
1360         if (physicalAddress == INVALID_PHYSICAL_ADDRESS) {
1361             return false;
1362         }
1363         int targetPhysicalAddress = targetDevice.getPhysicalAddress();
1364         if (targetPhysicalAddress == INVALID_PHYSICAL_ADDRESS) {
1365             return false;
1366         }
1367         return HdmiUtils.getLocalPortFromPhysicalAddress(targetPhysicalAddress, physicalAddress)
1368             != HdmiUtils.TARGET_NOT_UNDER_LOCAL_DEVICE;
1369     }
1370 
1371     /**
1372      * Listener used to get hotplug event from HDMI port.
1373      *
1374      * @hide
1375      */
1376     @SystemApi
1377     public interface HotplugEventListener {
onReceived(HdmiHotplugEvent event)1378         void onReceived(HdmiHotplugEvent event);
1379     }
1380 
1381     private final ArrayMap<HotplugEventListener, IHdmiHotplugEventListener>
1382             mHotplugEventListeners = new ArrayMap<>();
1383 
1384     /**
1385      * Listener used to get HDMI Control (CEC) status (enabled/disabled) and the connected display
1386      * status.
1387      * @hide
1388      */
1389     public interface HdmiControlStatusChangeListener {
1390         /**
1391          * Called when HDMI Control (CEC) is enabled/disabled.
1392          *
1393          * @param isCecEnabled status of HDMI Control
1394          * {@link android.provider.Settings.Global#HDMI_CONTROL_ENABLED}: {@code true} if enabled.
1395          * @param isCecAvailable status of CEC support of the connected display (the TV).
1396          * {@code true} if supported.
1397          *
1398          * Note: Value of isCecAvailable is only valid when isCecEnabled is true.
1399          **/
onStatusChange(@dmiControlManager.HdmiCecControl int isCecEnabled, boolean isCecAvailable)1400         void onStatusChange(@HdmiControlManager.HdmiCecControl int isCecEnabled,
1401                 boolean isCecAvailable);
1402     }
1403 
1404     private final ArrayMap<HdmiControlStatusChangeListener, IHdmiControlStatusChangeListener>
1405             mHdmiControlStatusChangeListeners = new ArrayMap<>();
1406 
1407     /**
1408      * Listener used to get the status of the HDMI CEC volume control feature (enabled/disabled).
1409      * @hide
1410      */
1411     public interface HdmiCecVolumeControlFeatureListener {
1412         /**
1413          * Called when the HDMI Control (CEC) volume control feature is enabled/disabled.
1414          *
1415          * @param hdmiCecVolumeControl status of HDMI CEC volume control feature
1416          * @see {@link HdmiControlManager#setHdmiCecVolumeControlEnabled(int)} ()}
1417          **/
onHdmiCecVolumeControlFeature(@olumeControl int hdmiCecVolumeControl)1418         void onHdmiCecVolumeControlFeature(@VolumeControl int hdmiCecVolumeControl);
1419     }
1420 
1421     private final ArrayMap<HdmiCecVolumeControlFeatureListener,
1422             IHdmiCecVolumeControlFeatureListener>
1423             mHdmiCecVolumeControlFeatureListeners = new ArrayMap<>();
1424 
1425     /**
1426      * Listener used to get vendor-specific commands.
1427      *
1428      * @hide
1429      */
1430     @SystemApi
1431     public interface VendorCommandListener {
1432         /**
1433          * Called when a vendor command is received.
1434          *
1435          * @param srcAddress source logical address
1436          * @param destAddress destination logical address
1437          * @param params vendor-specific parameters
1438          * @param hasVendorId {@code true} if the command is &lt;Vendor Command
1439          *        With ID&gt;. The first 3 bytes of params is vendor id.
1440          */
onReceived(int srcAddress, int destAddress, byte[] params, boolean hasVendorId)1441         void onReceived(int srcAddress, int destAddress, byte[] params, boolean hasVendorId);
1442 
1443         /**
1444          * The callback is called:
1445          * <ul>
1446          *     <li> before HdmiControlService is disabled.
1447          *     <li> after HdmiControlService is enabled and the local address is assigned.
1448          * </ul>
1449          * The client shouldn't hold the thread too long since this is a blocking call.
1450          *
1451          * @param enabled {@code true} if HdmiControlService is enabled.
1452          * @param reason the reason code why the state of HdmiControlService is changed.
1453          * @see #CONTROL_STATE_CHANGED_REASON_START
1454          * @see #CONTROL_STATE_CHANGED_REASON_SETTING
1455          * @see #CONTROL_STATE_CHANGED_REASON_WAKEUP
1456          * @see #CONTROL_STATE_CHANGED_REASON_STANDBY
1457          */
onControlStateChanged(boolean enabled, int reason)1458         void onControlStateChanged(boolean enabled, int reason);
1459     }
1460 
1461     /**
1462      * Adds a listener to get informed of {@link HdmiHotplugEvent}.
1463      *
1464      * <p>To stop getting the notification,
1465      * use {@link #removeHotplugEventListener(HotplugEventListener)}.
1466      *
1467      * Note that each invocation of the callback will be executed on an arbitrary
1468      * Binder thread. This means that all callback implementations must be
1469      * thread safe. To specify the execution thread, use
1470      * {@link addHotplugEventListener(Executor, HotplugEventListener)}.
1471      *
1472      * @param listener {@link HotplugEventListener} instance
1473      * @see HdmiControlManager#removeHotplugEventListener(HotplugEventListener)
1474      *
1475      * @hide
1476      */
1477     @SystemApi
1478     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
addHotplugEventListener(HotplugEventListener listener)1479     public void addHotplugEventListener(HotplugEventListener listener) {
1480         addHotplugEventListener(ConcurrentUtils.DIRECT_EXECUTOR, listener);
1481     }
1482 
1483     /**
1484      * Adds a listener to get informed of {@link HdmiHotplugEvent}.
1485      *
1486      * <p>To stop getting the notification,
1487      * use {@link #removeHotplugEventListener(HotplugEventListener)}.
1488      *
1489      * @param listener {@link HotplugEventListener} instance
1490      * @see HdmiControlManager#removeHotplugEventListener(HotplugEventListener)
1491      *
1492      * @hide
1493      */
1494     @SystemApi
1495     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
addHotplugEventListener(@onNull @allbackExecutor Executor executor, @NonNull HotplugEventListener listener)1496     public void addHotplugEventListener(@NonNull @CallbackExecutor Executor executor,
1497             @NonNull HotplugEventListener listener) {
1498         if (mService == null) {
1499             Log.e(TAG, "HdmiControlService is not available");
1500             return;
1501         }
1502         if (mHotplugEventListeners.containsKey(listener)) {
1503             Log.e(TAG, "listener is already registered");
1504             return;
1505         }
1506         IHdmiHotplugEventListener wrappedListener =
1507                 getHotplugEventListenerWrapper(executor, listener);
1508         mHotplugEventListeners.put(listener, wrappedListener);
1509         try {
1510             mService.addHotplugEventListener(wrappedListener);
1511         } catch (RemoteException e) {
1512             throw e.rethrowFromSystemServer();
1513         }
1514     }
1515 
1516     /**
1517      * Removes a listener to stop getting informed of {@link HdmiHotplugEvent}.
1518      *
1519      * @param listener {@link HotplugEventListener} instance to be removed
1520      *
1521      * @hide
1522      */
1523     @SystemApi
1524     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
removeHotplugEventListener(HotplugEventListener listener)1525     public void removeHotplugEventListener(HotplugEventListener listener) {
1526         if (mService == null) {
1527             Log.e(TAG, "HdmiControlService is not available");
1528             return;
1529         }
1530         IHdmiHotplugEventListener wrappedListener = mHotplugEventListeners.remove(listener);
1531         if (wrappedListener == null) {
1532             Log.e(TAG, "tried to remove not-registered listener");
1533             return;
1534         }
1535         try {
1536             mService.removeHotplugEventListener(wrappedListener);
1537         } catch (RemoteException e) {
1538             throw e.rethrowFromSystemServer();
1539         }
1540     }
1541 
getHotplugEventListenerWrapper( Executor executor, final HotplugEventListener listener)1542     private IHdmiHotplugEventListener getHotplugEventListenerWrapper(
1543             Executor executor, final HotplugEventListener listener) {
1544         return new IHdmiHotplugEventListener.Stub() {
1545             @Override
1546             public void onReceived(HdmiHotplugEvent event) {
1547                 final long token = Binder.clearCallingIdentity();
1548                 try {
1549                     executor.execute(() -> listener.onReceived(event));
1550                 } finally {
1551                     Binder.restoreCallingIdentity(token);
1552                 }
1553             }
1554         };
1555     }
1556 
1557     /**
1558      * Adds a listener to get informed of {@link HdmiControlStatusChange}.
1559      *
1560      * <p>To stop getting the notification,
1561      * use {@link #removeHdmiControlStatusChangeListener(HdmiControlStatusChangeListener)}.
1562      *
1563      * Note that each invocation of the callback will be executed on an arbitrary
1564      * Binder thread. This means that all callback implementations must be
1565      * thread safe. To specify the execution thread, use
1566      * {@link addHdmiControlStatusChangeListener(Executor, HdmiControlStatusChangeListener)}.
1567      *
1568      * @param listener {@link HdmiControlStatusChangeListener} instance
1569      * @see HdmiControlManager#removeHdmiControlStatusChangeListener(
1570      * HdmiControlStatusChangeListener)
1571      *
1572      * @hide
1573      */
1574     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
1575     public void addHdmiControlStatusChangeListener(HdmiControlStatusChangeListener listener) {
1576         addHdmiControlStatusChangeListener(ConcurrentUtils.DIRECT_EXECUTOR, listener);
1577     }
1578 
1579     /**
1580      * Adds a listener to get informed of {@link HdmiControlStatusChange}.
1581      *
1582      * <p>To stop getting the notification,
1583      * use {@link #removeHdmiControlStatusChangeListener(HdmiControlStatusChangeListener)}.
1584      *
1585      * @param listener {@link HdmiControlStatusChangeListener} instance
1586      * @see HdmiControlManager#removeHdmiControlStatusChangeListener(
1587      * HdmiControlStatusChangeListener)
1588      *
1589      * @hide
1590      */
1591     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
1592     public void addHdmiControlStatusChangeListener(@NonNull @CallbackExecutor Executor executor,
1593             @NonNull HdmiControlStatusChangeListener listener) {
1594         if (mService == null) {
1595             Log.e(TAG, "HdmiControlService is not available");
1596             return;
1597         }
1598         if (mHdmiControlStatusChangeListeners.containsKey(listener)) {
1599             Log.e(TAG, "listener is already registered");
1600             return;
1601         }
1602         IHdmiControlStatusChangeListener wrappedListener =
1603                 getHdmiControlStatusChangeListenerWrapper(executor, listener);
1604         mHdmiControlStatusChangeListeners.put(listener, wrappedListener);
1605         try {
1606             mService.addHdmiControlStatusChangeListener(wrappedListener);
1607         } catch (RemoteException e) {
1608             throw e.rethrowFromSystemServer();
1609         }
1610     }
1611 
1612     /**
1613      * Removes a listener to stop getting informed of {@link HdmiControlStatusChange}.
1614      *
1615      * @param listener {@link HdmiControlStatusChangeListener} instance to be removed
1616      *
1617      * @hide
1618      */
1619     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
1620     public void removeHdmiControlStatusChangeListener(HdmiControlStatusChangeListener listener) {
1621         if (mService == null) {
1622             Log.e(TAG, "HdmiControlService is not available");
1623             return;
1624         }
1625         IHdmiControlStatusChangeListener wrappedListener =
1626                 mHdmiControlStatusChangeListeners.remove(listener);
1627         if (wrappedListener == null) {
1628             Log.e(TAG, "tried to remove not-registered listener");
1629             return;
1630         }
1631         try {
1632             mService.removeHdmiControlStatusChangeListener(wrappedListener);
1633         } catch (RemoteException e) {
1634             throw e.rethrowFromSystemServer();
1635         }
1636     }
1637 
1638     private IHdmiControlStatusChangeListener getHdmiControlStatusChangeListenerWrapper(
1639             Executor executor, final HdmiControlStatusChangeListener listener) {
1640         return new IHdmiControlStatusChangeListener.Stub() {
1641             @Override
1642             public void onStatusChange(@HdmiCecControl int isCecEnabled, boolean isCecAvailable) {
1643                 final long token = Binder.clearCallingIdentity();
1644                 try {
1645                     executor.execute(() -> listener.onStatusChange(isCecEnabled, isCecAvailable));
1646                 } finally {
1647                     Binder.restoreCallingIdentity(token);
1648                 }
1649             }
1650         };
1651     }
1652 
1653     /**
1654      * Adds a listener to get informed of changes to the state of the HDMI CEC volume control
1655      * feature.
1656      *
1657      * Upon adding a listener, the current state of the HDMI CEC volume control feature will be
1658      * sent immediately.
1659      *
1660      * <p>To stop getting the notification,
1661      * use {@link #removeHdmiCecVolumeControlFeatureListener(HdmiCecVolumeControlFeatureListener)}.
1662      *
1663      * @param listener {@link HdmiCecVolumeControlFeatureListener} instance
1664      * @hide
1665      * @see #removeHdmiCecVolumeControlFeatureListener(HdmiCecVolumeControlFeatureListener)
1666      */
1667     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
1668     public void addHdmiCecVolumeControlFeatureListener(@NonNull @CallbackExecutor Executor executor,
1669             @NonNull HdmiCecVolumeControlFeatureListener listener) {
1670         if (mService == null) {
1671             Log.e(TAG, "HdmiControlService is not available");
1672             return;
1673         }
1674         if (mHdmiCecVolumeControlFeatureListeners.containsKey(listener)) {
1675             Log.e(TAG, "listener is already registered");
1676             return;
1677         }
1678         IHdmiCecVolumeControlFeatureListener wrappedListener =
1679                 createHdmiCecVolumeControlFeatureListenerWrapper(executor, listener);
1680         mHdmiCecVolumeControlFeatureListeners.put(listener, wrappedListener);
1681         try {
1682             mService.addHdmiCecVolumeControlFeatureListener(wrappedListener);
1683         } catch (RemoteException e) {
1684             throw e.rethrowFromSystemServer();
1685         }
1686     }
1687 
1688     /**
1689      * Removes a listener to stop getting informed of changes to the state of the HDMI CEC volume
1690      * control feature.
1691      *
1692      * @param listener {@link HdmiCecVolumeControlFeatureListener} instance to be removed
1693      * @hide
1694      */
1695     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
1696     public void removeHdmiCecVolumeControlFeatureListener(
1697             HdmiCecVolumeControlFeatureListener listener) {
1698         if (mService == null) {
1699             Log.e(TAG, "HdmiControlService is not available");
1700             return;
1701         }
1702         IHdmiCecVolumeControlFeatureListener wrappedListener =
1703                 mHdmiCecVolumeControlFeatureListeners.remove(listener);
1704         if (wrappedListener == null) {
1705             Log.e(TAG, "tried to remove not-registered listener");
1706             return;
1707         }
1708         try {
1709             mService.removeHdmiCecVolumeControlFeatureListener(wrappedListener);
1710         } catch (RemoteException e) {
1711             throw e.rethrowFromSystemServer();
1712         }
1713     }
1714 
1715     private IHdmiCecVolumeControlFeatureListener createHdmiCecVolumeControlFeatureListenerWrapper(
1716             Executor executor, final HdmiCecVolumeControlFeatureListener listener) {
1717         return new android.hardware.hdmi.IHdmiCecVolumeControlFeatureListener.Stub() {
1718             @Override
1719             public void onHdmiCecVolumeControlFeature(int enabled) {
1720                 final long token = Binder.clearCallingIdentity();
1721                 try {
1722                     executor.execute(() -> listener.onHdmiCecVolumeControlFeature(enabled));
1723                 } finally {
1724                     Binder.restoreCallingIdentity(token);
1725                 }
1726             }
1727         };
1728     }
1729 
1730     /**
1731      * Listener used to get setting change notification.
1732      *
1733      * @hide
1734      */
1735     @SystemApi
1736     public interface CecSettingChangeListener {
1737         /**
1738          * Called when value of a setting changes.
1739          *
1740          * @param setting name of a CEC setting that changed
1741          */
1742         void onChange(@NonNull @CecSettingName String setting);
1743     }
1744 
1745     private final ArrayMap<String,
1746             ArrayMap<CecSettingChangeListener, IHdmiCecSettingChangeListener>>
1747                     mCecSettingChangeListeners = new ArrayMap<>();
1748 
1749     private void addCecSettingChangeListener(
1750             @NonNull @CecSettingName String setting,
1751             @NonNull @CallbackExecutor Executor executor,
1752             @NonNull CecSettingChangeListener listener) {
1753         if (mService == null) {
1754             Log.e(TAG, "HdmiControlService is not available");
1755             return;
1756         }
1757         if (mCecSettingChangeListeners.containsKey(setting)
1758                 && mCecSettingChangeListeners.get(setting).containsKey(listener)) {
1759             Log.e(TAG, "listener is already registered");
1760             return;
1761         }
1762         IHdmiCecSettingChangeListener wrappedListener =
1763                 getCecSettingChangeListenerWrapper(executor, listener);
1764         if (!mCecSettingChangeListeners.containsKey(setting)) {
1765             mCecSettingChangeListeners.put(setting, new ArrayMap<>());
1766         }
1767         mCecSettingChangeListeners.get(setting).put(listener, wrappedListener);
1768         try {
1769             mService.addCecSettingChangeListener(setting, wrappedListener);
1770         } catch (RemoteException e) {
1771             throw e.rethrowFromSystemServer();
1772         }
1773     }
1774 
1775     private void removeCecSettingChangeListener(
1776             @NonNull @CecSettingName String setting,
1777             @NonNull CecSettingChangeListener listener) {
1778         if (mService == null) {
1779             Log.e(TAG, "HdmiControlService is not available");
1780             return;
1781         }
1782         IHdmiCecSettingChangeListener wrappedListener =
1783                 !mCecSettingChangeListeners.containsKey(setting) ? null :
1784                     mCecSettingChangeListeners.get(setting).remove(listener);
1785         if (wrappedListener == null) {
1786             Log.e(TAG, "tried to remove not-registered listener");
1787             return;
1788         }
1789         try {
1790             mService.removeCecSettingChangeListener(setting, wrappedListener);
1791         } catch (RemoteException e) {
1792             throw e.rethrowFromSystemServer();
1793         }
1794     }
1795 
1796     private IHdmiCecSettingChangeListener getCecSettingChangeListenerWrapper(
1797             Executor executor, final CecSettingChangeListener listener) {
1798         return new IHdmiCecSettingChangeListener.Stub() {
1799             @Override
1800             public void onChange(String setting) {
1801                 final long token = Binder.clearCallingIdentity();
1802                 try {
1803                     executor.execute(() -> listener.onChange(setting));
1804                 } finally {
1805                     Binder.restoreCallingIdentity(token);
1806                 }
1807             }
1808         };
1809     }
1810 
1811     /**
1812      * Get a set of user-modifiable settings.
1813      *
1814      * @return a set of user-modifiable settings.
1815      * @throws RuntimeException when the HdmiControlService is not available.
1816      *
1817      * @hide
1818      */
1819     @SystemApi
1820     @NonNull
1821     @CecSettingName
1822     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
1823     public List<String> getUserCecSettings() {
1824         if (mService == null) {
1825             Log.e(TAG, "HdmiControlService is not available");
1826             throw new RuntimeException("HdmiControlService is not available");
1827         }
1828         try {
1829             return mService.getUserCecSettings();
1830         } catch (RemoteException e) {
1831             throw e.rethrowFromSystemServer();
1832         }
1833     }
1834 
1835     /**
1836      * Get a set of allowed values for a setting (string value-type).
1837      *
1838      * @param name name of the setting
1839      * @return a set of allowed values for a settings. {@code null} on failure.
1840      * @throws IllegalArgumentException when setting {@code name} does not exist.
1841      * @throws IllegalArgumentException when setting {@code name} value type is invalid.
1842      * @throws RuntimeException when the HdmiControlService is not available.
1843      *
1844      * @hide
1845      */
1846     @SystemApi
1847     @NonNull
1848     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
1849     public List<String> getAllowedCecSettingStringValues(@NonNull @CecSettingName String name) {
1850         if (mService == null) {
1851             Log.e(TAG, "HdmiControlService is not available");
1852             throw new RuntimeException("HdmiControlService is not available");
1853         }
1854         try {
1855             return mService.getAllowedCecSettingStringValues(name);
1856         } catch (RemoteException e) {
1857             throw e.rethrowFromSystemServer();
1858         }
1859     }
1860 
1861     /**
1862      * Get a set of allowed values for a setting (int value-type).
1863      *
1864      * @param name name of the setting
1865      * @return a set of allowed values for a settings. {@code null} on failure.
1866      * @throws IllegalArgumentException when setting {@code name} does not exist.
1867      * @throws IllegalArgumentException when setting {@code name} value type is invalid.
1868      * @throws RuntimeException when the HdmiControlService is not available.
1869      *
1870      * @hide
1871      */
1872     @SystemApi
1873     @NonNull
1874     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
1875     public List<Integer> getAllowedCecSettingIntValues(@NonNull @CecSettingName String name) {
1876         if (mService == null) {
1877             Log.e(TAG, "HdmiControlService is not available");
1878             throw new RuntimeException("HdmiControlService is not available");
1879         }
1880         try {
1881             int[] allowedValues = mService.getAllowedCecSettingIntValues(name);
1882             return Arrays.stream(allowedValues).boxed().collect(Collectors.toList());
1883         } catch (RemoteException e) {
1884             throw e.rethrowFromSystemServer();
1885         }
1886     }
1887 
1888     /**
1889      * Set the global status of HDMI CEC.
1890      *
1891      * <p>This allows to enable/disable HDMI CEC on the device.
1892      *
1893      * @hide
1894      */
1895     @SystemApi
1896     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
1897     public void setHdmiCecEnabled(@NonNull @HdmiCecControl int value) {
1898         if (mService == null) {
1899             Log.e(TAG, "HdmiControlService is not available");
1900             throw new RuntimeException("HdmiControlService is not available");
1901         }
1902         try {
1903             mService.setCecSettingIntValue(CEC_SETTING_NAME_HDMI_CEC_ENABLED, value);
1904         } catch (RemoteException e) {
1905             throw e.rethrowFromSystemServer();
1906         }
1907     }
1908 
1909     /**
1910      * Get the current global status of HDMI CEC.
1911      *
1912      * <p>Reflects whether HDMI CEC is currently enabled on the device.
1913      *
1914      * @hide
1915      */
1916     @SystemApi
1917     @NonNull
1918     @HdmiCecControl
1919     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
1920     public int getHdmiCecEnabled() {
1921         if (mService == null) {
1922             Log.e(TAG, "HdmiControlService is not available");
1923             throw new RuntimeException("HdmiControlService is not available");
1924         }
1925         try {
1926             return mService.getCecSettingIntValue(CEC_SETTING_NAME_HDMI_CEC_ENABLED);
1927         } catch (RemoteException e) {
1928             throw e.rethrowFromSystemServer();
1929         }
1930     }
1931 
1932     /**
1933      * Add change listener for global status of HDMI CEC.
1934      *
1935      * <p>To stop getting the notification,
1936      * use {@link #removeHdmiCecEnabledChangeListener(CecSettingChangeListener)}.
1937      *
1938      * Note that each invocation of the callback will be executed on an arbitrary
1939      * Binder thread. This means that all callback implementations must be
1940      * thread safe. To specify the execution thread, use
1941      * {@link addHdmiCecEnabledChangeListener(Executor, CecSettingChangeListener)}.
1942      *
1943      * @hide
1944      */
1945     @SystemApi
1946     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
1947     public void addHdmiCecEnabledChangeListener(@NonNull CecSettingChangeListener listener) {
1948         addHdmiCecEnabledChangeListener(ConcurrentUtils.DIRECT_EXECUTOR, listener);
1949     }
1950 
1951     /**
1952      * Add change listener for global status of HDMI CEC.
1953      *
1954      * <p>To stop getting the notification,
1955      * use {@link #removeHdmiCecEnabledChangeListener(CecSettingChangeListener)}.
1956      *
1957      * @hide
1958      */
1959     @SystemApi
1960     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
1961     public void addHdmiCecEnabledChangeListener(
1962             @NonNull @CallbackExecutor Executor executor,
1963             @NonNull CecSettingChangeListener listener) {
1964         addCecSettingChangeListener(CEC_SETTING_NAME_HDMI_CEC_ENABLED, executor, listener);
1965     }
1966 
1967     /**
1968      * Remove change listener for global status of HDMI CEC.
1969      *
1970      * @hide
1971      */
1972     @SystemApi
1973     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
1974     public void removeHdmiCecEnabledChangeListener(
1975             @NonNull CecSettingChangeListener listener) {
1976         removeCecSettingChangeListener(CEC_SETTING_NAME_HDMI_CEC_ENABLED, listener);
1977     }
1978 
1979     /**
1980      * Set the version of the HDMI CEC specification currently used.
1981      *
1982      * <p>Allows to select either CEC 1.4b or 2.0 to be used by the device.
1983      *
1984      * @hide
1985      */
1986     @SystemApi
1987     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
1988     public void setHdmiCecVersion(@NonNull @HdmiCecVersion int value) {
1989         if (mService == null) {
1990             Log.e(TAG, "HdmiControlService is not available");
1991             throw new RuntimeException("HdmiControlService is not available");
1992         }
1993         try {
1994             mService.setCecSettingIntValue(CEC_SETTING_NAME_HDMI_CEC_VERSION, value);
1995         } catch (RemoteException e) {
1996             throw e.rethrowFromSystemServer();
1997         }
1998     }
1999 
2000     /**
2001      * Get the version of the HDMI CEC specification currently used.
2002      *
2003      * <p>Reflects which CEC version 1.4b or 2.0 is currently used by the device.
2004      *
2005      * @hide
2006      */
2007     @SystemApi
2008     @NonNull
2009     @HdmiCecVersion
2010     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
2011     public int getHdmiCecVersion() {
2012         if (mService == null) {
2013             Log.e(TAG, "HdmiControlService is not available");
2014             throw new RuntimeException("HdmiControlService is not available");
2015         }
2016         try {
2017             return mService.getCecSettingIntValue(CEC_SETTING_NAME_HDMI_CEC_VERSION);
2018         } catch (RemoteException e) {
2019             throw e.rethrowFromSystemServer();
2020         }
2021     }
2022 
2023     /**
2024      * Set the status of Power Control.
2025      *
2026      * <p>Specifies to which devices Power Control messages should be sent:
2027      * only to the TV, broadcast to all devices, no power control messages.
2028      *
2029      * @hide
2030      */
2031     @SystemApi
2032     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
2033     public void setPowerControlMode(@NonNull @PowerControlMode String value) {
2034         if (mService == null) {
2035             Log.e(TAG, "HdmiControlService is not available");
2036             throw new RuntimeException("HdmiControlService is not available");
2037         }
2038         try {
2039             mService.setCecSettingStringValue(CEC_SETTING_NAME_POWER_CONTROL_MODE, value);
2040         } catch (RemoteException e) {
2041             throw e.rethrowFromSystemServer();
2042         }
2043     }
2044 
2045     /**
2046      * Get the status of Power Control.
2047      *
2048      * <p>Reflects to which devices Power Control messages should be sent:
2049      * only to the TV, broadcast to all devices, no power control messages.
2050      *
2051      * @hide
2052      */
2053     @SystemApi
2054     @NonNull
2055     @PowerControlMode
2056     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
2057     public String getPowerControlMode() {
2058         if (mService == null) {
2059             Log.e(TAG, "HdmiControlService is not available");
2060             throw new RuntimeException("HdmiControlService is not available");
2061         }
2062         try {
2063             return mService.getCecSettingStringValue(CEC_SETTING_NAME_POWER_CONTROL_MODE);
2064         } catch (RemoteException e) {
2065             throw e.rethrowFromSystemServer();
2066         }
2067     }
2068 
2069     /**
2070      * Set the current power state behaviour when Active Source is lost.
2071      *
2072      * <p>Sets the action taken: do nothing or go to sleep immediately.
2073      *
2074      * @hide
2075      */
2076     @SystemApi
2077     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
2078     public void setPowerStateChangeOnActiveSourceLost(
2079             @NonNull @ActiveSourceLostBehavior String value) {
2080         if (mService == null) {
2081             Log.e(TAG, "HdmiControlService is not available");
2082             throw new RuntimeException("HdmiControlService is not available");
2083         }
2084         try {
2085             mService.setCecSettingStringValue(
2086                     CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST, value);
2087         } catch (RemoteException e) {
2088             throw e.rethrowFromSystemServer();
2089         }
2090     }
2091 
2092     /**
2093      * Get the current power state behaviour when Active Source is lost.
2094      *
2095      * <p>Reflects the action taken: do nothing or go to sleep immediately.
2096      *
2097      * @hide
2098      */
2099     @SystemApi
2100     @NonNull
2101     @ActiveSourceLostBehavior
2102     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
2103     public String getPowerStateChangeOnActiveSourceLost() {
2104         if (mService == null) {
2105             Log.e(TAG, "HdmiControlService is not available");
2106             throw new RuntimeException("HdmiControlService is not available");
2107         }
2108         try {
2109             return mService.getCecSettingStringValue(
2110                     CEC_SETTING_NAME_POWER_STATE_CHANGE_ON_ACTIVE_SOURCE_LOST);
2111         } catch (RemoteException e) {
2112             throw e.rethrowFromSystemServer();
2113         }
2114     }
2115 
2116     /**
2117      * Set the current status of System Audio Mode muting.
2118      *
2119      * <p>Sets whether the device should be muted when System Audio Mode is turned off.
2120      *
2121      * @hide
2122      */
2123     @SystemApi
2124     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
2125     public void setSystemAudioModeMuting(@NonNull @SystemAudioModeMuting int value) {
2126         if (mService == null) {
2127             Log.e(TAG, "HdmiControlService is not available");
2128             throw new RuntimeException("HdmiControlService is not available");
2129         }
2130         try {
2131             mService.setCecSettingIntValue(CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING, value);
2132         } catch (RemoteException e) {
2133             throw e.rethrowFromSystemServer();
2134         }
2135     }
2136 
2137     /**
2138      * Get the current status of System Audio Mode muting.
2139      *
2140      * <p>Reflects whether the device should be muted when System Audio Mode is turned off.
2141      *
2142      * @hide
2143      */
2144     @SystemApi
2145     @NonNull
2146     @SystemAudioModeMuting
2147     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
2148     public int getSystemAudioModeMuting() {
2149         if (mService == null) {
2150             Log.e(TAG, "HdmiControlService is not available");
2151             throw new RuntimeException("HdmiControlService is not available");
2152         }
2153         try {
2154             return mService.getCecSettingIntValue(CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING);
2155         } catch (RemoteException e) {
2156             throw e.rethrowFromSystemServer();
2157         }
2158     }
2159 
2160     /**
2161      * Set the current status of TV Wake on One Touch Play.
2162      *
2163      * <p>Sets whether the TV should wake up upon reception of &lt;Text View On&gt;
2164      * or &lt;Image View On&gt;.
2165      *
2166      * @hide
2167      */
2168     @SystemApi
2169     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
2170     public void setTvWakeOnOneTouchPlay(@NonNull @TvWakeOnOneTouchPlay int value) {
2171         if (mService == null) {
2172             Log.e(TAG, "HdmiControlService is not available");
2173             throw new RuntimeException("HdmiControlService is not available");
2174         }
2175         try {
2176             mService.setCecSettingIntValue(CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY, value);
2177         } catch (RemoteException e) {
2178             throw e.rethrowFromSystemServer();
2179         }
2180     }
2181 
2182     /**
2183      * Get the current status of TV Wake on One Touch Play.
2184      *
2185      * <p>Reflects whether the TV should wake up upon reception of &lt;Text View On&gt;
2186      * or &lt;Image View On&gt;.
2187      *
2188      * @hide
2189      */
2190     @SystemApi
2191     @NonNull
2192     @TvWakeOnOneTouchPlay
2193     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
2194     public int getTvWakeOnOneTouchPlay() {
2195         if (mService == null) {
2196             Log.e(TAG, "HdmiControlService is not available");
2197             throw new RuntimeException("HdmiControlService is not available");
2198         }
2199         try {
2200             return mService.getCecSettingIntValue(CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY);
2201         } catch (RemoteException e) {
2202             throw e.rethrowFromSystemServer();
2203         }
2204     }
2205 
2206     /**
2207      * Set the current status of TV send &lt;Standby&gt; on Sleep.
2208      *
2209      * <p>Sets whether the device will also turn off other CEC devices
2210      * when it goes to standby mode.
2211      *
2212      * @hide
2213      */
2214     @SystemApi
2215     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
2216     public void setTvSendStandbyOnSleep(@NonNull @TvSendStandbyOnSleep int value) {
2217         if (mService == null) {
2218             Log.e(TAG, "HdmiControlService is not available");
2219             throw new RuntimeException("HdmiControlService is not available");
2220         }
2221         try {
2222             mService.setCecSettingIntValue(CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP, value);
2223         } catch (RemoteException e) {
2224             throw e.rethrowFromSystemServer();
2225         }
2226     }
2227 
2228     /**
2229      * Get the current status of TV send &lt;Standby&gt; on Sleep.
2230      *
2231      * <p>Reflects whether the device will also turn off other CEC devices
2232      * when it goes to standby mode.
2233      *
2234      * @hide
2235      */
2236     @SystemApi
2237     @NonNull
2238     @TvSendStandbyOnSleep
2239     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
2240     public int getTvSendStandbyOnSleep() {
2241         if (mService == null) {
2242             Log.e(TAG, "HdmiControlService is not available");
2243             throw new RuntimeException("HdmiControlService is not available");
2244         }
2245         try {
2246             return mService.getCecSettingIntValue(CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP);
2247         } catch (RemoteException e) {
2248             throw e.rethrowFromSystemServer();
2249         }
2250     }
2251 }
2252