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