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 package android.hardware.hdmi;
17 
18 import android.annotation.NonNull;
19 import android.annotation.SystemApi;
20 import android.hardware.hdmi.HdmiRecordSources.RecordSource;
21 import android.hardware.hdmi.HdmiTimerRecordSources.TimerRecordSource;
22 import android.os.RemoteException;
23 import android.util.Log;
24 
25 import libcore.util.EmptyArray;
26 
27 import java.util.Collections;
28 import java.util.List;
29 
30 /**
31  * HdmiTvClient represents HDMI-CEC logical device of type TV in the Android system
32  * which acts as TV/Display.
33  *
34  * <p>HdmiTvClient provides methods that manage, interact with other devices on the CEC bus.
35  *
36  * @hide
37  */
38 @SystemApi
39 public final class HdmiTvClient extends HdmiClient {
40     private static final String TAG = "HdmiTvClient";
41 
42     /**
43      * Size of MHL register for vendor command
44      */
45     public static final int VENDOR_DATA_SIZE = 16;
46 
HdmiTvClient(IHdmiControlService service)47     /* package */ HdmiTvClient(IHdmiControlService service) {
48         super(service);
49     }
50 
51     // Factory method for HdmiTvClient.
52     // Declared package-private. Accessed by HdmiControlManager only.
create(IHdmiControlService service)53     /* package */ static HdmiTvClient create(IHdmiControlService service) {
54         return new HdmiTvClient(service);
55     }
56 
57     @Override
getDeviceType()58     public int getDeviceType() {
59         return HdmiDeviceInfo.DEVICE_TV;
60     }
61 
62     /**
63      * Callback interface used to get the result of {@link #portSelect} and
64      * {@link #setSystemAudioMode}.
65      */
66     public interface SelectCallback {
67         /**
68          * Called when the operation is finished.
69          *
70          * @param result the result value of {@link #deviceSelect}
71          */
onComplete(int result)72         void onComplete(int result);
73     }
74 
75     /**
76      * Selects a CEC logical device to be a new active source.
77      *
78      * @param logicalAddress logical address of the device to select
79      * @param callback callback to get the result with
80      * @throws {@link IllegalArgumentException} if the {@code callback} is null
81      * @deprecated Please use {@link HdmiClient#selectDevice} instead.
82      */
83     @Deprecated
deviceSelect(int logicalAddress, @NonNull SelectCallback callback)84     public void deviceSelect(int logicalAddress, @NonNull SelectCallback callback) {
85         if (callback == null) {
86             throw new IllegalArgumentException("callback must not be null.");
87         }
88         try {
89             mService.deviceSelect(logicalAddress, getCallbackWrapper(callback));
90         } catch (RemoteException e) {
91             Log.e(TAG, "failed to select device: ", e);
92         }
93     }
94 
getCallbackWrapper(final SelectCallback callback)95     private static IHdmiControlCallback getCallbackWrapper(final SelectCallback callback) {
96         return new IHdmiControlCallback.Stub() {
97             @Override
98             public void onComplete(int result) {
99                 callback.onComplete(result);
100             }
101         };
102     }
103 
104     /**
105      * Selects a HDMI port to be a new route path.
106      *
107      * @param portId HDMI port to select
108      * @param callback callback to get the result with
109      * @throws {@link IllegalArgumentException} if the {@code callback} is null
110      */
111     public void portSelect(int portId, @NonNull SelectCallback callback) {
112         if (callback == null) {
113             throw new IllegalArgumentException("Callback must not be null");
114         }
115         try {
116             mService.portSelect(portId, getCallbackWrapper(callback));
117         } catch (RemoteException e) {
118             Log.e(TAG, "failed to select port: ", e);
119         }
120     }
121 
122     /**
123      * Callback interface used to get the input change event.
124      */
125     public interface InputChangeListener {
126         /**
127          * Called when the input was changed.
128          *
129          * @param info newly selected HDMI input
130          */
131         void onChanged(HdmiDeviceInfo info);
132     }
133 
134     /**
135      * Sets the listener used to get informed of the input change event.
136      *
137      * @param listener listener object
138      */
139     public void setInputChangeListener(InputChangeListener listener) {
140         if (listener == null) {
141             throw new IllegalArgumentException("listener must not be null.");
142         }
143         try {
144             mService.setInputChangeListener(getListenerWrapper(listener));
145         } catch (RemoteException e) {
146             Log.e("TAG", "Failed to set InputChangeListener:", e);
147         }
148     }
149 
150     private static IHdmiInputChangeListener getListenerWrapper(final InputChangeListener listener) {
151         return new IHdmiInputChangeListener.Stub() {
152             @Override
153             public void onChanged(HdmiDeviceInfo info) {
154                 listener.onChanged(info);
155             }
156         };
157     }
158 
159     /**
160      * Returns all the CEC devices connected to TV.
161      *
162      * @return list of {@link HdmiDeviceInfo} for connected CEC devices.
163      *         Empty list is returned if there is none.
164      * @deprecated Please use {@link HdmiControlManager#getConnectedDevices()} instead.
165      */
166     @Deprecated
167     public List<HdmiDeviceInfo> getDeviceList() {
168         try {
169             return mService.getDeviceList();
170         } catch (RemoteException e) {
171             Log.e("TAG", "Failed to call getDeviceList():", e);
172             return Collections.<HdmiDeviceInfo>emptyList();
173         }
174     }
175 
176     /**
177      * Sets system audio mode.
178      *
179      * @param enabled set to {@code true} to enable the mode; otherwise {@code false}
180      * @param callback callback to get the result with
181      * @throws {@link IllegalArgumentException} if the {@code callback} is null
182      */
183     public void setSystemAudioMode(boolean enabled, SelectCallback callback) {
184         try {
185             mService.setSystemAudioMode(enabled, getCallbackWrapper(callback));
186         } catch (RemoteException e) {
187             Log.e(TAG, "failed to set system audio mode:", e);
188         }
189     }
190 
191     /**
192      * Sets system audio volume.
193      *
194      * @param oldIndex current volume index
195      * @param newIndex volume index to be set
196      * @param maxIndex maximum volume index
197      */
198     public void setSystemAudioVolume(int oldIndex, int newIndex, int maxIndex) {
199         try {
200             mService.setSystemAudioVolume(oldIndex, newIndex, maxIndex);
201         } catch (RemoteException e) {
202             Log.e(TAG, "failed to set volume: ", e);
203         }
204     }
205 
206     /**
207      * Sets system audio mute status.
208      *
209      * @param mute {@code true} if muted; otherwise, {@code false}
210      */
211     public void setSystemAudioMute(boolean mute) {
212         try {
213             mService.setSystemAudioMute(mute);
214         } catch (RemoteException e) {
215             Log.e(TAG, "failed to set mute: ", e);
216         }
217     }
218 
219     /**
220      * Sets record listener.
221      *
222      * @param listener
223      */
224     public void setRecordListener(@NonNull HdmiRecordListener listener) {
225         if (listener == null) {
226             throw new IllegalArgumentException("listener must not be null.");
227         }
228         try {
229             mService.setHdmiRecordListener(getListenerWrapper(listener));
230         } catch (RemoteException e) {
231             Log.e(TAG, "failed to set record listener.", e);
232         }
233     }
234 
235     /**
236      * Sends a &lt;Standby&gt; command to other device.
237      *
238      * @param deviceId device id to send the command to
239      */
240     public void sendStandby(int deviceId) {
241         try {
242             mService.sendStandby(getDeviceType(), deviceId);
243         } catch (RemoteException e) {
244             Log.e(TAG, "sendStandby threw exception ", e);
245         }
246     }
247 
248     private static IHdmiRecordListener getListenerWrapper(final HdmiRecordListener callback) {
249         return new IHdmiRecordListener.Stub() {
250             @Override
251             public byte[] getOneTouchRecordSource(int recorderAddress) {
252                 HdmiRecordSources.RecordSource source =
253                         callback.onOneTouchRecordSourceRequested(recorderAddress);
254                 if (source == null) {
255                     return EmptyArray.BYTE;
256                 }
257                 byte[] data = new byte[source.getDataSize(true)];
258                 source.toByteArray(true, data, 0);
259                 return data;
260             }
261 
262             @Override
263             public void onOneTouchRecordResult(int recorderAddress, int result) {
264                 callback.onOneTouchRecordResult(recorderAddress, result);
265             }
266 
267             @Override
268             public void onTimerRecordingResult(int recorderAddress, int result) {
269                 callback.onTimerRecordingResult(recorderAddress,
270                         HdmiRecordListener.TimerStatusData.parseFrom(result));
271             }
272 
273             @Override
274             public void onClearTimerRecordingResult(int recorderAddress, int result) {
275                 callback.onClearTimerRecordingResult(recorderAddress, result);
276             }
277         };
278     }
279 
280     /**
281      * Starts one touch recording with the given recorder address and recorder source.
282      * <p>
283      * Usage
284      * <pre>
285      * HdmiTvClient tvClient = ....;
286      * // for own source.
287      * OwnSource ownSource = HdmiRecordSources.ofOwnSource();
288      * tvClient.startOneTouchRecord(recorderAddress, ownSource);
289      * </pre>
290      */
291     public void startOneTouchRecord(int recorderAddress, @NonNull RecordSource source) {
292         if (source == null) {
293             throw new IllegalArgumentException("source must not be null.");
294         }
295 
296         try {
297             byte[] data = new byte[source.getDataSize(true)];
298             source.toByteArray(true, data, 0);
299             mService.startOneTouchRecord(recorderAddress, data);
300         } catch (RemoteException e) {
301             Log.e(TAG, "failed to start record: ", e);
302         }
303     }
304 
305     /**
306      * Stops one touch record.
307      *
308      * @param recorderAddress recorder address where recoding will be stopped
309      */
310     public void stopOneTouchRecord(int recorderAddress) {
311         try {
312             mService.stopOneTouchRecord(recorderAddress);
313         } catch (RemoteException e) {
314             Log.e(TAG, "failed to stop record: ", e);
315         }
316     }
317 
318     /**
319      * Starts timer recording with the given recoder address and recorder source.
320      * <p>
321      * Usage
322      * <pre>
323      * HdmiTvClient tvClient = ....;
324      * // create timer info
325      * TimerInfo timerInfo = HdmiTimerRecourdSources.timerInfoOf(...);
326      * // for digital source.
327      * DigitalServiceSource recordSource = HdmiRecordSources.ofDigitalService(...);
328      * // create timer recording source.
329      * TimerRecordSource source = HdmiTimerRecourdSources.ofDigitalSource(timerInfo, recordSource);
330      * tvClient.startTimerRecording(recorderAddress, source);
331      * </pre>
332      *
333      * @param recorderAddress target recorder address
334      * @param sourceType type of record source. It should be one of
335      *          {@link HdmiControlManager#TIMER_RECORDING_TYPE_DIGITAL},
336      *          {@link HdmiControlManager#TIMER_RECORDING_TYPE_ANALOGUE},
337      *          {@link HdmiControlManager#TIMER_RECORDING_TYPE_EXTERNAL}.
338      * @param source record source to be used
339      */
340     public void startTimerRecording(int recorderAddress, int sourceType, TimerRecordSource source) {
341         if (source == null) {
342             throw new IllegalArgumentException("source must not be null.");
343         }
344 
345         checkTimerRecordingSourceType(sourceType);
346 
347         try {
348             byte[] data = new byte[source.getDataSize()];
349             source.toByteArray(data, 0);
350             mService.startTimerRecording(recorderAddress, sourceType, data);
351         } catch (RemoteException e) {
352             Log.e(TAG, "failed to start record: ", e);
353         }
354     }
355 
356     private void checkTimerRecordingSourceType(int sourceType) {
357         switch (sourceType) {
358             case HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL:
359             case HdmiControlManager.TIMER_RECORDING_TYPE_ANALOGUE:
360             case HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL:
361                 break;
362             default:
363                 throw new IllegalArgumentException("Invalid source type:" + sourceType);
364         }
365     }
366 
367     /**
368      * Clears timer recording with the given recorder address and recording source.
369      * For more details, please refer {@link #startTimerRecording(int, int, TimerRecordSource)}.
370      */
371     public void clearTimerRecording(int recorderAddress, int sourceType, TimerRecordSource source) {
372         if (source == null) {
373             throw new IllegalArgumentException("source must not be null.");
374         }
375 
376         checkTimerRecordingSourceType(sourceType);
377         try {
378             byte[] data = new byte[source.getDataSize()];
379             source.toByteArray(data, 0);
380             mService.clearTimerRecording(recorderAddress, sourceType, data);
381         } catch (RemoteException e) {
382             Log.e(TAG, "failed to start record: ", e);
383         }
384     }
385 
386     /**
387      * Interface used to get incoming MHL vendor command.
388      */
389     public interface HdmiMhlVendorCommandListener {
390         void onReceived(int portId, int offset, int length, byte[] data);
391     }
392 
393     /**
394      * Sets {@link HdmiMhlVendorCommandListener} to get incoming MHL vendor command.
395      *
396      * @param listener to receive incoming MHL vendor command
397      */
398     public void setHdmiMhlVendorCommandListener(HdmiMhlVendorCommandListener listener) {
399         if (listener == null) {
400             throw new IllegalArgumentException("listener must not be null.");
401         }
402         try {
403             mService.addHdmiMhlVendorCommandListener(getListenerWrapper(listener));
404         } catch (RemoteException e) {
405             Log.e(TAG, "failed to set hdmi mhl vendor command listener: ", e);
406         }
407     }
408 
409     private IHdmiMhlVendorCommandListener getListenerWrapper(
410             final HdmiMhlVendorCommandListener listener) {
411         return new IHdmiMhlVendorCommandListener.Stub() {
412             @Override
413             public void onReceived(int portId, int offset, int length, byte[] data) {
414                 listener.onReceived(portId, offset, length, data);
415             }
416         };
417     }
418 
419     /**
420      * Sends MHL vendor command to the device connected to a port of the given portId.
421      *
422      * @param portId id of port to send MHL vendor command
423      * @param offset offset in the in given data
424      * @param length length of data. offset + length should be bound to length of data.
425      * @param data container for vendor command data. It should be 16 bytes.
426      * @throws IllegalArgumentException if the given parameters are invalid
427      */
428     public void sendMhlVendorCommand(int portId, int offset, int length, byte[] data) {
429         if (data == null || data.length != VENDOR_DATA_SIZE) {
430             throw new IllegalArgumentException("Invalid vendor command data.");
431         }
432         if (offset < 0 || offset >= VENDOR_DATA_SIZE) {
433             throw new IllegalArgumentException("Invalid offset:" + offset);
434         }
435         if (length < 0 || offset + length > VENDOR_DATA_SIZE) {
436             throw new IllegalArgumentException("Invalid length:" + length);
437         }
438 
439         try {
440             mService.sendMhlVendorCommand(portId, offset, length, data);
441         } catch (RemoteException e) {
442             Log.e(TAG, "failed to send vendor command: ", e);
443         }
444     }
445 }
446