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