HdmiTvClient.java revision bdf27fbf746bee11430c4db2ea6dfd026bae77fe
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 java.util.Collections;
26import java.util.List;
27
28import libcore.util.EmptyArray;
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    HdmiTvClient(IHdmiControlService service) {
47        super(service);
48    }
49
50    // Factory method for HdmiTvClient.
51    // Declared package-private. Accessed by HdmiControlManager only.
52    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     * Select 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     * Select 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     * Set 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     * Set 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     * Set 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     * Set 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.getOneTouchRecordSource(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 result) {
230                callback.onOneTouchRecordResult(result);
231            }
232
233            @Override
234            public void onTimerRecordingResult(int result) {
235                callback.onTimerRecordingResult(
236                        HdmiRecordListener.TimerStatusData.parseFrom(result));
237            }
238
239            @Override
240            public void onClearTimerRecordingResult(int result) {
241                callback.onClearTimerRecordingResult(result);
242            }
243        };
244    }
245
246    /**
247     * Start 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 = ownHdmiRecordSources.ownSource();
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     * Stop 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     * Start 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     * Clear 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     * Set {@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     * Send 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