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.Nullable;
20 import android.annotation.RequiresFeature;
21 import android.annotation.RequiresPermission;
22 import android.annotation.SdkConstant;
23 import android.annotation.SdkConstant.SdkConstantType;
24 import android.annotation.SuppressLint;
25 import android.content.Context;
26 import android.content.pm.PackageManager;
27 import android.annotation.SystemApi;
28 import android.annotation.SystemService;
29 import android.os.RemoteException;
30 import android.util.ArrayMap;
31 import android.util.Log;
32 
33 /**
34  * The {@link HdmiControlManager} class is used to send HDMI control messages
35  * to attached CEC devices.
36  *
37  * <p>Provides various HDMI client instances that represent HDMI-CEC logical devices
38  * hosted in the system. {@link #getTvClient()}, for instance will return an
39  * {@link HdmiTvClient} object if the system is configured to host one. Android system
40  * can host more than one logical CEC devices. If multiple types are configured they
41  * all work as if they were independent logical devices running in the system.
42  *
43  * @hide
44  */
45 @SystemApi
46 @SystemService(Context.HDMI_CONTROL_SERVICE)
47 @RequiresFeature(PackageManager.FEATURE_HDMI_CEC)
48 public final class HdmiControlManager {
49     private static final String TAG = "HdmiControlManager";
50 
51     @Nullable private final IHdmiControlService mService;
52 
53     /**
54      * Broadcast Action: Display OSD message.
55      * <p>Send when the service has a message to display on screen for events
56      * that need user's attention such as ARC status change.
57      * <p>Always contains the extra fields {@link #EXTRA_MESSAGE_ID}.
58      * <p>Requires {@link android.Manifest.permission#HDMI_CEC} to receive.
59      */
60     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
61     public static final String ACTION_OSD_MESSAGE = "android.hardware.hdmi.action.OSD_MESSAGE";
62 
63     // --- Messages for ACTION_OSD_MESSAGE ---
64     /**
65      * Message that ARC enabled device is connected to invalid port (non-ARC port).
66      */
67     public static final int OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT = 1;
68 
69     /**
70      * Message used by TV to receive volume status from Audio Receiver. It should check volume value
71      * that is retrieved from extra value with the key {@link #EXTRA_MESSAGE_EXTRA_PARAM1}. If the
72      * value is in range of [0,100], it is current volume of Audio Receiver. And there is another
73      * value, {@link #AVR_VOLUME_MUTED}, which is used to inform volume mute.
74      */
75     public static final int OSD_MESSAGE_AVR_VOLUME_CHANGED = 2;
76 
77     /**
78      * Used as an extra field in the intent {@link #ACTION_OSD_MESSAGE}. Contains the ID of
79      * the message to display on screen.
80      */
81     public static final String EXTRA_MESSAGE_ID = "android.hardware.hdmi.extra.MESSAGE_ID";
82     /**
83      * Used as an extra field in the intent {@link #ACTION_OSD_MESSAGE}. Contains the extra value
84      * of the message.
85      */
86     public static final String EXTRA_MESSAGE_EXTRA_PARAM1 =
87             "android.hardware.hdmi.extra.MESSAGE_EXTRA_PARAM1";
88 
89     /**
90      * Volume value for mute state.
91      */
92     public static final int AVR_VOLUME_MUTED = 101;
93 
94     public static final int POWER_STATUS_UNKNOWN = -1;
95     public static final int POWER_STATUS_ON = 0;
96     public static final int POWER_STATUS_STANDBY = 1;
97     public static final int POWER_STATUS_TRANSIENT_TO_ON = 2;
98     public static final int POWER_STATUS_TRANSIENT_TO_STANDBY = 3;
99 
100     public static final int RESULT_SUCCESS = 0;
101     public static final int RESULT_TIMEOUT = 1;
102     public static final int RESULT_SOURCE_NOT_AVAILABLE = 2;
103     public static final int RESULT_TARGET_NOT_AVAILABLE = 3;
104 
105     @Deprecated public static final int RESULT_ALREADY_IN_PROGRESS = 4;
106     public static final int RESULT_EXCEPTION = 5;
107     public static final int RESULT_INCORRECT_MODE = 6;
108     public static final int RESULT_COMMUNICATION_FAILED = 7;
109 
110     public static final int DEVICE_EVENT_ADD_DEVICE = 1;
111     public static final int DEVICE_EVENT_REMOVE_DEVICE = 2;
112     public static final int DEVICE_EVENT_UPDATE_DEVICE = 3;
113 
114     // --- One Touch Recording success result
115     /** Recording currently selected source. Indicates the status of a recording. */
116     public static final int ONE_TOUCH_RECORD_RECORDING_CURRENTLY_SELECTED_SOURCE = 0x01;
117     /** Recording Digital Service. Indicates the status of a recording. */
118     public static final int ONE_TOUCH_RECORD_RECORDING_DIGITAL_SERVICE = 0x02;
119     /** Recording Analogue Service. Indicates the status of a recording. */
120     public static final int ONE_TOUCH_RECORD_RECORDING_ANALOGUE_SERVICE = 0x03;
121     /** Recording External input. Indicates the status of a recording. */
122     public static final int ONE_TOUCH_RECORD_RECORDING_EXTERNAL_INPUT = 0x04;
123 
124     // --- One Touch Record failure result
125     /** No recording – unable to record Digital Service. No suitable tuner. */
126     public static final int ONE_TOUCH_RECORD_UNABLE_DIGITAL_SERVICE = 0x05;
127     /** No recording – unable to record Analogue Service. No suitable tuner. */
128     public static final int ONE_TOUCH_RECORD_UNABLE_ANALOGUE_SERVICE = 0x06;
129     /**
130      * No recording – unable to select required service. as suitable tuner, but the requested
131      * parameters are invalid or out of range for that tuner.
132      */
133     public static final int ONE_TOUCH_RECORD_UNABLE_SELECTED_SERVICE = 0x07;
134     /** No recording – invalid External plug number */
135     public static final int ONE_TOUCH_RECORD_INVALID_EXTERNAL_PLUG_NUMBER = 0x09;
136     /** No recording – invalid External Physical Address */
137     public static final int ONE_TOUCH_RECORD_INVALID_EXTERNAL_PHYSICAL_ADDRESS = 0x0A;
138     /** No recording – CA system not supported */
139     public static final int ONE_TOUCH_RECORD_UNSUPPORTED_CA = 0x0B;
140     /** No Recording – No or Insufficient CA Entitlements” */
141     public static final int ONE_TOUCH_RECORD_NO_OR_INSUFFICIENT_CA_ENTITLEMENTS = 0x0C;
142     /** No recording – Not allowed to copy source. Source is “copy never”. */
143     public static final int ONE_TOUCH_RECORD_DISALLOW_TO_COPY = 0x0D;
144     /** No recording – No further copies allowed */
145     public static final int ONE_TOUCH_RECORD_DISALLOW_TO_FUTHER_COPIES = 0x0E;
146     /** No recording – No media */
147     public static final int ONE_TOUCH_RECORD_NO_MEDIA = 0x10;
148     /** No recording – playing */
149     public static final int ONE_TOUCH_RECORD_PLAYING = 0x11;
150     /** No recording – already recording */
151     public static final int ONE_TOUCH_RECORD_ALREADY_RECORDING = 0x12;
152     /** No recording – media protected */
153     public static final int ONE_TOUCH_RECORD_MEDIA_PROTECTED = 0x13;
154     /** No recording – no source signal */
155     public static final int ONE_TOUCH_RECORD_NO_SOURCE_SIGNAL = 0x14;
156     /** No recording – media problem */
157     public static final int ONE_TOUCH_RECORD_MEDIA_PROBLEM = 0x15;
158     /** No recording – not enough space available */
159     public static final int ONE_TOUCH_RECORD_NOT_ENOUGH_SPACE = 0x16;
160     /** No recording – Parental Lock On */
161     public static final int ONE_TOUCH_RECORD_PARENT_LOCK_ON = 0x17;
162     /** Recording terminated normally */
163     public static final int ONE_TOUCH_RECORD_RECORDING_TERMINATED_NORMALLY = 0x1A;
164     /** Recording has already terminated */
165     public static final int ONE_TOUCH_RECORD_RECORDING_ALREADY_TERMINATED = 0x1B;
166     /** No recording – other reason */
167     public static final int ONE_TOUCH_RECORD_OTHER_REASON = 0x1F;
168     // From here extra message for recording that is not mentioned in CEC spec
169     /** No recording. Previous recording request in progress. */
170     public static final int ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS = 0x30;
171     /** No recording. Please check recorder and connection. */
172     public static final int ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION = 0x31;
173     /** Cannot record currently displayed source. */
174     public static final int ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN = 0x32;
175     /** CEC is disabled. */
176     public static final int ONE_TOUCH_RECORD_CEC_DISABLED = 0x33;
177 
178     // --- Types for timer recording
179     /** Timer recording type for digital service source. */
180     public static final int TIMER_RECORDING_TYPE_DIGITAL = 1;
181     /** Timer recording type for analogue service source. */
182     public static final int TIMER_RECORDING_TYPE_ANALOGUE = 2;
183     /** Timer recording type for external source. */
184     public static final int TIMER_RECORDING_TYPE_EXTERNAL = 3;
185 
186     // --- Timer Status Data
187     /** [Timer Status Data/Media Info] - Media present and not protected. */
188     public static final int TIMER_STATUS_MEDIA_INFO_PRESENT_NOT_PROTECTED = 0x0;
189     /** [Timer Status Data/Media Info] - Media present, but protected. */
190     public static final int TIMER_STATUS_MEDIA_INFO_PRESENT_PROTECTED = 0x1;
191     /** [Timer Status Data/Media Info] - Media not present. */
192     public static final int TIMER_STATUS_MEDIA_INFO_NOT_PRESENT = 0x2;
193 
194     /** [Timer Status Data/Programmed Info] - Enough space available for recording. */
195     public static final int TIMER_STATUS_PROGRAMMED_INFO_ENOUGH_SPACE = 0x8;
196     /** [Timer Status Data/Programmed Info] - Not enough space available for recording. */
197     public static final int TIMER_STATUS_PROGRAMMED_INFO_NOT_ENOUGH_SPACE = 0x9;
198     /** [Timer Status Data/Programmed Info] - Might not enough space available for recording. */
199     public static final int TIMER_STATUS_PROGRAMMED_INFO_MIGHT_NOT_ENOUGH_SPACE = 0xB;
200     /** [Timer Status Data/Programmed Info] - No media info available. */
201     public static final int TIMER_STATUS_PROGRAMMED_INFO_NO_MEDIA_INFO = 0xA;
202 
203     /** [Timer Status Data/Not Programmed Error Info] - No free timer available. */
204     public static final int TIMER_STATUS_NOT_PROGRAMMED_NO_FREE_TIME = 0x1;
205     /** [Timer Status Data/Not Programmed Error Info] - Date out of range. */
206     public static final int TIMER_STATUS_NOT_PROGRAMMED_DATE_OUT_OF_RANGE = 0x2;
207     /** [Timer Status Data/Not Programmed Error Info] - Recording Sequence error. */
208     public static final int TIMER_STATUS_NOT_PROGRAMMED_INVALID_SEQUENCE = 0x3;
209     /** [Timer Status Data/Not Programmed Error Info] - Invalid External Plug Number. */
210     public static final int TIMER_STATUS_NOT_PROGRAMMED_INVALID_EXTERNAL_PLUG_NUMBER = 0x4;
211     /** [Timer Status Data/Not Programmed Error Info] - Invalid External Physical Address. */
212     public static final int TIMER_STATUS_NOT_PROGRAMMED_INVALID_EXTERNAL_PHYSICAL_NUMBER = 0x5;
213     /** [Timer Status Data/Not Programmed Error Info] - CA system not supported. */
214     public static final int TIMER_STATUS_NOT_PROGRAMMED_CA_NOT_SUPPORTED = 0x6;
215     /** [Timer Status Data/Not Programmed Error Info] - No or insufficient CA Entitlements. */
216     public static final int TIMER_STATUS_NOT_PROGRAMMED_NO_CA_ENTITLEMENTS = 0x7;
217     /** [Timer Status Data/Not Programmed Error Info] - Does not support resolution. */
218     public static final int TIMER_STATUS_NOT_PROGRAMMED_UNSUPPORTED_RESOLUTION = 0x8;
219     /** [Timer Status Data/Not Programmed Error Info] - Parental Lock On. */
220     public static final int TIMER_STATUS_NOT_PROGRAMMED_PARENTAL_LOCK_ON= 0x9;
221     /** [Timer Status Data/Not Programmed Error Info] - Clock Failure. */
222     public static final int TIMER_STATUS_NOT_PROGRAMMED_CLOCK_FAILURE = 0xA;
223     /** [Timer Status Data/Not Programmed Error Info] - Duplicate: already programmed. */
224     public static final int TIMER_STATUS_NOT_PROGRAMMED_DUPLICATED = 0xE;
225 
226     // --- Extra result value for timer recording.
227     /** No extra error. */
228     public static final int TIMER_RECORDING_RESULT_EXTRA_NO_ERROR = 0x00;
229     /** No timer recording - check recorder and connection. */
230     public static final int TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION = 0x01;
231     /** No timer recording - cannot record selected source. */
232     public static final int TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE = 0x02;
233     /** CEC is disabled. */
234     public static final int TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED = 0x03;
235 
236     // -- Timer cleared status data code used for result of onClearTimerRecordingResult.
237     /** Timer not cleared – recording. */
238     public static final int CLEAR_TIMER_STATUS_TIMER_NOT_CLEARED_RECORDING = 0x00;
239     /** Timer not cleared – no matching. */
240     public static final int CLEAR_TIMER_STATUS_TIMER_NOT_CLEARED_NO_MATCHING = 0x01;
241     /** Timer not cleared – no info available. */
242     public static final int CLEAR_TIMER_STATUS_TIMER_NOT_CLEARED_NO_INFO_AVAILABLE = 0x02;
243     /** Timer cleared. */
244     public static final int CLEAR_TIMER_STATUS_TIMER_CLEARED = 0x80;
245     /** Clear timer error - check recorder and connection. */
246     public static final int CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION = 0xA0;
247     /** Clear timer error - cannot clear timer for selected source. */
248     public static final int CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE = 0xA1;
249     /** Clear timer error - CEC is disabled. */
250     public static final int CLEAR_TIMER_STATUS_CEC_DISABLE = 0xA2;
251 
252     /** The HdmiControlService is started. */
253     public static final int CONTROL_STATE_CHANGED_REASON_START = 0;
254     /** The state of HdmiControlService is changed by changing of settings. */
255     public static final int CONTROL_STATE_CHANGED_REASON_SETTING = 1;
256     /** The HdmiControlService is enabled to wake up. */
257     public static final int CONTROL_STATE_CHANGED_REASON_WAKEUP = 2;
258     /** The HdmiControlService will be disabled to standby. */
259     public static final int CONTROL_STATE_CHANGED_REASON_STANDBY = 3;
260 
261     // True if we have a logical device of type playback hosted in the system.
262     private final boolean mHasPlaybackDevice;
263     // True if we have a logical device of type TV hosted in the system.
264     private final boolean mHasTvDevice;
265 
266     /**
267      * {@hide} - hide this constructor because it has a parameter of type IHdmiControlService,
268      * which is a system private class. The right way to create an instance of this class is
269      * using the factory Context.getSystemService.
270      */
HdmiControlManager(IHdmiControlService service)271     public HdmiControlManager(IHdmiControlService service) {
272         mService = service;
273         int[] types = null;
274         if (mService != null) {
275             try {
276                 types = mService.getSupportedTypes();
277             } catch (RemoteException e) {
278                 throw e.rethrowFromSystemServer();
279             }
280         }
281         mHasTvDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_TV);
282         mHasPlaybackDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_PLAYBACK);
283     }
284 
hasDeviceType(int[] types, int type)285     private static boolean hasDeviceType(int[] types, int type) {
286         if (types == null) {
287             return false;
288         }
289         for (int t : types) {
290             if (t == type) {
291                 return true;
292             }
293         }
294         return false;
295     }
296 
297     /**
298      * Gets an object that represents an HDMI-CEC logical device of a specified type.
299      *
300      * @param type CEC device type
301      * @return {@link HdmiClient} instance. {@code null} on failure.
302      * See {@link HdmiDeviceInfo#DEVICE_PLAYBACK}
303      * See {@link HdmiDeviceInfo#DEVICE_TV}
304      */
305     @Nullable
306     @SuppressLint("Doclava125")
getClient(int type)307     public HdmiClient getClient(int type) {
308         if (mService == null) {
309             return null;
310         }
311         switch (type) {
312             case HdmiDeviceInfo.DEVICE_TV:
313                 return mHasTvDevice ? new HdmiTvClient(mService) : null;
314             case HdmiDeviceInfo.DEVICE_PLAYBACK:
315                 return mHasPlaybackDevice ? new HdmiPlaybackClient(mService) : null;
316             default:
317                 return null;
318         }
319     }
320 
321     /**
322      * Gets an object that represents an HDMI-CEC logical device of type playback on the system.
323      *
324      * <p>Used to send HDMI control messages to other devices like TV or audio amplifier through
325      * HDMI bus. It is also possible to communicate with other logical devices hosted in the same
326      * system if the system is configured to host more than one type of HDMI-CEC logical devices.
327      *
328      * @return {@link HdmiPlaybackClient} instance. {@code null} on failure.
329      */
330     @Nullable
331     @SuppressLint("Doclava125")
getPlaybackClient()332     public HdmiPlaybackClient getPlaybackClient() {
333         return (HdmiPlaybackClient) getClient(HdmiDeviceInfo.DEVICE_PLAYBACK);
334     }
335 
336     /**
337      * Gets an object that represents an HDMI-CEC logical device of type TV on the system.
338      *
339      * <p>Used to send HDMI control messages to other devices and manage them through
340      * HDMI bus. It is also possible to communicate with other logical devices hosted in the same
341      * system if the system is configured to host more than one type of HDMI-CEC logical devices.
342      *
343      * @return {@link HdmiTvClient} instance. {@code null} on failure.
344      */
345     @Nullable
346     @SuppressLint("Doclava125")
getTvClient()347     public HdmiTvClient getTvClient() {
348         return (HdmiTvClient) getClient(HdmiDeviceInfo.DEVICE_TV);
349     }
350 
351     /**
352      * Controls standby mode of the system. It will also try to turn on/off the connected devices if
353      * necessary.
354      *
355      * @param isStandbyModeOn target status of the system's standby mode
356      */
357     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
setStandbyMode(boolean isStandbyModeOn)358     public void setStandbyMode(boolean isStandbyModeOn) {
359         try {
360             mService.setStandbyMode(isStandbyModeOn);
361         } catch (RemoteException e) {
362             throw e.rethrowFromSystemServer();
363         }
364     }
365 
366     /**
367      * Listener used to get hotplug event from HDMI port.
368      */
369     public interface HotplugEventListener {
onReceived(HdmiHotplugEvent event)370         void onReceived(HdmiHotplugEvent event);
371     }
372 
373     private final ArrayMap<HotplugEventListener, IHdmiHotplugEventListener>
374             mHotplugEventListeners = new ArrayMap<>();
375 
376     /**
377      * Listener used to get vendor-specific commands.
378      */
379     public interface VendorCommandListener {
380         /**
381          * Called when a vendor command is received.
382          *
383          * @param srcAddress source logical address
384          * @param destAddress destination logical address
385          * @param params vendor-specific parameters
386          * @param hasVendorId {@code true} if the command is &lt;Vendor Command
387          *        With ID&gt;. The first 3 bytes of params is vendor id.
388          */
onReceived(int srcAddress, int destAddress, byte[] params, boolean hasVendorId)389         void onReceived(int srcAddress, int destAddress, byte[] params, boolean hasVendorId);
390 
391         /**
392          * The callback is called:
393          * <ul>
394          *     <li> before HdmiControlService is disabled.
395          *     <li> after HdmiControlService is enabled and the local address is assigned.
396          * </ul>
397          * The client shouldn't hold the thread too long since this is a blocking call.
398          *
399          * @param enabled {@code true} if HdmiControlService is enabled.
400          * @param reason the reason code why the state of HdmiControlService is changed.
401          * @see #CONTROL_STATE_CHANGED_REASON_START
402          * @see #CONTROL_STATE_CHANGED_REASON_SETTING
403          * @see #CONTROL_STATE_CHANGED_REASON_WAKEUP
404          * @see #CONTROL_STATE_CHANGED_REASON_STANDBY
405          */
onControlStateChanged(boolean enabled, int reason)406         void onControlStateChanged(boolean enabled, int reason);
407     }
408 
409     /**
410      * Adds a listener to get informed of {@link HdmiHotplugEvent}.
411      *
412      * <p>To stop getting the notification,
413      * use {@link #removeHotplugEventListener(HotplugEventListener)}.
414      *
415      * @param listener {@link HotplugEventListener} instance
416      * @see HdmiControlManager#removeHotplugEventListener(HotplugEventListener)
417      */
418     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
addHotplugEventListener(HotplugEventListener listener)419     public void addHotplugEventListener(HotplugEventListener listener) {
420         if (mService == null) {
421             Log.e(TAG, "HdmiControlService is not available");
422             return;
423         }
424         if (mHotplugEventListeners.containsKey(listener)) {
425             Log.e(TAG, "listener is already registered");
426             return;
427         }
428         IHdmiHotplugEventListener wrappedListener = getHotplugEventListenerWrapper(listener);
429         mHotplugEventListeners.put(listener, wrappedListener);
430         try {
431             mService.addHotplugEventListener(wrappedListener);
432         } catch (RemoteException e) {
433             throw e.rethrowFromSystemServer();
434         }
435     }
436 
437     /**
438      * Removes a listener to stop getting informed of {@link HdmiHotplugEvent}.
439      *
440      * @param listener {@link HotplugEventListener} instance to be removed
441      */
442     @RequiresPermission(android.Manifest.permission.HDMI_CEC)
removeHotplugEventListener(HotplugEventListener listener)443     public void removeHotplugEventListener(HotplugEventListener listener) {
444         if (mService == null) {
445             Log.e(TAG, "HdmiControlService is not available");
446             return;
447         }
448         IHdmiHotplugEventListener wrappedListener = mHotplugEventListeners.remove(listener);
449         if (wrappedListener == null) {
450             Log.e(TAG, "tried to remove not-registered listener");
451             return;
452         }
453         try {
454             mService.removeHotplugEventListener(wrappedListener);
455         } catch (RemoteException e) {
456             throw e.rethrowFromSystemServer();
457         }
458     }
459 
getHotplugEventListenerWrapper( final HotplugEventListener listener)460     private IHdmiHotplugEventListener getHotplugEventListenerWrapper(
461             final HotplugEventListener listener) {
462         return new IHdmiHotplugEventListener.Stub() {
463             @Override
464             public void onReceived(HdmiHotplugEvent event) {
465                 listener.onReceived(event);;
466             }
467         };
468     }
469 }
470