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 */
16package android.hardware.hdmi;
17
18import android.annotation.NonNull;
19import android.annotation.SystemApi;
20import android.hardware.hdmi.HdmiRecordSources.RecordSource;
21import android.hardware.hdmi.HdmiTimerRecordSources.TimerRecordSource;
22import android.os.RemoteException;
23import android.util.Log;
24
25import libcore.util.EmptyArray;
26
27import java.util.Collections;
28import 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
38public 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
46    /* package */ HdmiTvClient(IHdmiControlService service) {
47        super(service);
48    }
49
50    // Factory method for HdmiTvClient.
51    // Declared package-private. Accessed by HdmiControlManager only.
52    /* package */ static HdmiTvClient create(IHdmiControlService service) {
53        return new HdmiTvClient(service);
54    }
55
56    @Override
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         */
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     */
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
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 mode.
172     *
173     * @param enabled set to {@code true} to enable the mode; otherwise {@code false}
174     * @param callback callback to get the result with
175     * @throws {@link IllegalArgumentException} if the {@code callback} is null
176     */
177    public void setSystemAudioMode(boolean enabled, SelectCallback callback) {
178        try {
179            mService.setSystemAudioMode(enabled, getCallbackWrapper(callback));
180        } catch (RemoteException e) {
181            Log.e(TAG, "failed to set system audio mode:", e);
182        }
183    }
184
185    /**
186     * Sets system audio volume.
187     *
188     * @param oldIndex current volume index
189     * @param newIndex volume index to be set
190     * @param maxIndex maximum volume index
191     */
192    public void setSystemAudioVolume(int oldIndex, int newIndex, int maxIndex) {
193        try {
194            mService.setSystemAudioVolume(oldIndex, newIndex, maxIndex);
195        } catch (RemoteException e) {
196            Log.e(TAG, "failed to set volume: ", e);
197        }
198    }
199
200    /**
201     * Sets system audio mute status.
202     *
203     * @param mute {@code true} if muted; otherwise, {@code false}
204     */
205    public void setSystemAudioMute(boolean mute) {
206        try {
207            mService.setSystemAudioMute(mute);
208        } catch (RemoteException e) {
209            Log.e(TAG, "failed to set mute: ", e);
210        }
211    }
212
213    /**
214     * Sets record listener.
215     *
216     * @param listener
217     */
218    public void setRecordListener(@NonNull HdmiRecordListener listener) {
219        if (listener == null) {
220            throw new IllegalArgumentException("listener must not be null.");
221        }
222        try {
223            mService.setHdmiRecordListener(getListenerWrapper(listener));
224        } catch (RemoteException e) {
225            Log.e(TAG, "failed to set record listener.", e);
226        }
227    }
228
229    /**
230     * Sends a &lt;Standby&gt; command to other device.
231     *
232     * @param deviceId device id to send the command to
233     */
234    public void sendStandby(int deviceId) {
235        try {
236            mService.sendStandby(getDeviceType(), deviceId);
237        } catch (RemoteException e) {
238            Log.e(TAG, "sendStandby threw exception ", e);
239        }
240    }
241
242    private static IHdmiRecordListener getListenerWrapper(final HdmiRecordListener callback) {
243        return new IHdmiRecordListener.Stub() {
244            @Override
245            public byte[] getOneTouchRecordSource(int recorderAddress) {
246                HdmiRecordSources.RecordSource source =
247                        callback.onOneTouchRecordSourceRequested(recorderAddress);
248                if (source == null) {
249                    return EmptyArray.BYTE;
250                }
251                byte[] data = new byte[source.getDataSize(true)];
252                source.toByteArray(true, data, 0);
253                return data;
254            }
255
256            @Override
257            public void onOneTouchRecordResult(int recorderAddress, int result) {
258                callback.onOneTouchRecordResult(recorderAddress, result);
259            }
260
261            @Override
262            public void onTimerRecordingResult(int recorderAddress, int result) {
263                callback.onTimerRecordingResult(recorderAddress,
264                        HdmiRecordListener.TimerStatusData.parseFrom(result));
265            }
266
267            @Override
268            public void onClearTimerRecordingResult(int recorderAddress, int result) {
269                callback.onClearTimerRecordingResult(recorderAddress, result);
270            }
271        };
272    }
273
274    /**
275     * Starts one touch recording with the given recorder address and recorder source.
276     * <p>
277     * Usage
278     * <pre>
279     * HdmiTvClient tvClient = ....;
280     * // for own source.
281     * OwnSource ownSource = HdmiRecordSources.ofOwnSource();
282     * tvClient.startOneTouchRecord(recorderAddress, ownSource);
283     * </pre>
284     */
285    public void startOneTouchRecord(int recorderAddress, @NonNull RecordSource source) {
286        if (source == null) {
287            throw new IllegalArgumentException("source must not be null.");
288        }
289
290        try {
291            byte[] data = new byte[source.getDataSize(true)];
292            source.toByteArray(true, data, 0);
293            mService.startOneTouchRecord(recorderAddress, data);
294        } catch (RemoteException e) {
295            Log.e(TAG, "failed to start record: ", e);
296        }
297    }
298
299    /**
300     * Stops one touch record.
301     *
302     * @param recorderAddress recorder address where recoding will be stopped
303     */
304    public void stopOneTouchRecord(int recorderAddress) {
305        try {
306            mService.stopOneTouchRecord(recorderAddress);
307        } catch (RemoteException e) {
308            Log.e(TAG, "failed to stop record: ", e);
309        }
310    }
311
312    /**
313     * Starts timer recording with the given recoder address and recorder source.
314     * <p>
315     * Usage
316     * <pre>
317     * HdmiTvClient tvClient = ....;
318     * // create timer info
319     * TimerInfo timerInfo = HdmiTimerRecourdSources.timerInfoOf(...);
320     * // for digital source.
321     * DigitalServiceSource recordSource = HdmiRecordSources.ofDigitalService(...);
322     * // create timer recording source.
323     * TimerRecordSource source = HdmiTimerRecourdSources.ofDigitalSource(timerInfo, recordSource);
324     * tvClient.startTimerRecording(recorderAddress, source);
325     * </pre>
326     *
327     * @param recorderAddress target recorder address
328     * @param sourceType type of record source. It should be one of
329     *          {@link HdmiControlManager#TIMER_RECORDING_TYPE_DIGITAL},
330     *          {@link HdmiControlManager#TIMER_RECORDING_TYPE_ANALOGUE},
331     *          {@link HdmiControlManager#TIMER_RECORDING_TYPE_EXTERNAL}.
332     * @param source record source to be used
333     */
334    public void startTimerRecording(int recorderAddress, int sourceType, TimerRecordSource source) {
335        if (source == null) {
336            throw new IllegalArgumentException("source must not be null.");
337        }
338
339        checkTimerRecordingSourceType(sourceType);
340
341        try {
342            byte[] data = new byte[source.getDataSize()];
343            source.toByteArray(data, 0);
344            mService.startTimerRecording(recorderAddress, sourceType, data);
345        } catch (RemoteException e) {
346            Log.e(TAG, "failed to start record: ", e);
347        }
348    }
349
350    private void checkTimerRecordingSourceType(int sourceType) {
351        switch (sourceType) {
352            case HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL:
353            case HdmiControlManager.TIMER_RECORDING_TYPE_ANALOGUE:
354            case HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL:
355                break;
356            default:
357                throw new IllegalArgumentException("Invalid source type:" + sourceType);
358        }
359    }
360
361    /**
362     * Clears timer recording with the given recorder address and recording source.
363     * For more details, please refer {@link #startTimerRecording(int, int, TimerRecordSource)}.
364     */
365    public void clearTimerRecording(int recorderAddress, int sourceType, TimerRecordSource source) {
366        if (source == null) {
367            throw new IllegalArgumentException("source must not be null.");
368        }
369
370        checkTimerRecordingSourceType(sourceType);
371        try {
372            byte[] data = new byte[source.getDataSize()];
373            source.toByteArray(data, 0);
374            mService.clearTimerRecording(recorderAddress, sourceType, data);
375        } catch (RemoteException e) {
376            Log.e(TAG, "failed to start record: ", e);
377        }
378    }
379
380    /**
381     * Interface used to get incoming MHL vendor command.
382     */
383    public interface HdmiMhlVendorCommandListener {
384        void onReceived(int portId, int offset, int length, byte[] data);
385    }
386
387    /**
388     * Sets {@link HdmiMhlVendorCommandListener} to get incoming MHL vendor command.
389     *
390     * @param listener to receive incoming MHL vendor command
391     */
392    public void setHdmiMhlVendorCommandListener(HdmiMhlVendorCommandListener listener) {
393        if (listener == null) {
394            throw new IllegalArgumentException("listener must not be null.");
395        }
396        try {
397            mService.addHdmiMhlVendorCommandListener(getListenerWrapper(listener));
398        } catch (RemoteException e) {
399            Log.e(TAG, "failed to set hdmi mhl vendor command listener: ", e);
400        }
401    }
402
403    private IHdmiMhlVendorCommandListener getListenerWrapper(
404            final HdmiMhlVendorCommandListener listener) {
405        return new IHdmiMhlVendorCommandListener.Stub() {
406            @Override
407            public void onReceived(int portId, int offset, int length, byte[] data) {
408                listener.onReceived(portId, offset, length, data);
409            }
410        };
411    }
412
413    /**
414     * Sends MHL vendor command to the device connected to a port of the given portId.
415     *
416     * @param portId id of port to send MHL vendor command
417     * @param offset offset in the in given data
418     * @param length length of data. offset + length should be bound to length of data.
419     * @param data container for vendor command data. It should be 16 bytes.
420     * @throws IllegalArgumentException if the given parameters are invalid
421     */
422    public void sendMhlVendorCommand(int portId, int offset, int length, byte[] data) {
423        if (data == null || data.length != VENDOR_DATA_SIZE) {
424            throw new IllegalArgumentException("Invalid vendor command data.");
425        }
426        if (offset < 0 || offset >= VENDOR_DATA_SIZE) {
427            throw new IllegalArgumentException("Invalid offset:" + offset);
428        }
429        if (length < 0 || offset + length > VENDOR_DATA_SIZE) {
430            throw new IllegalArgumentException("Invalid length:" + length);
431        }
432
433        try {
434            mService.sendMhlVendorCommand(portId, offset, length, data);
435        } catch (RemoteException e) {
436            Log.e(TAG, "failed to send vendor command: ", e);
437        }
438    }
439}
440