TvInputHardwareManager.java revision c086a3df3b28996cd10ebe42c5f59035d054aa0d
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
17package com.android.server.tv;
18
19import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
20import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
21
22import android.content.Context;
23import android.hardware.hdmi.HdmiCecDeviceInfo;
24import android.hardware.hdmi.HdmiHotplugEvent;
25import android.hardware.hdmi.IHdmiControlService;
26import android.hardware.hdmi.IHdmiDeviceEventListener;
27import android.hardware.hdmi.IHdmiHotplugEventListener;
28import android.hardware.hdmi.IHdmiInputChangeListener;
29import android.media.AudioDevicePort;
30import android.media.AudioManager;
31import android.media.AudioPatch;
32import android.media.AudioPort;
33import android.media.AudioPortConfig;
34import android.media.tv.ITvInputHardware;
35import android.media.tv.ITvInputHardwareCallback;
36import android.media.tv.TvInputHardwareInfo;
37import android.media.tv.TvInputInfo;
38import android.media.tv.TvStreamConfig;
39import android.os.Handler;
40import android.os.IBinder;
41import android.os.Looper;
42import android.os.Message;
43import android.os.RemoteException;
44import android.os.ServiceManager;
45import android.util.ArrayMap;
46import android.util.Slog;
47import android.util.SparseArray;
48import android.util.SparseBooleanArray;
49import android.view.KeyEvent;
50import android.view.Surface;
51
52import com.android.server.SystemService;
53
54import java.util.ArrayList;
55import java.util.Collections;
56import java.util.HashSet;
57import java.util.List;
58import java.util.Map;
59import java.util.Set;
60
61/**
62 * A helper class for TvInputManagerService to handle TV input hardware.
63 *
64 * This class does a basic connection management and forwarding calls to TvInputHal which eventually
65 * calls to tv_input HAL module.
66 *
67 * @hide
68 */
69class TvInputHardwareManager implements TvInputHal.Callback {
70    private static final String TAG = TvInputHardwareManager.class.getSimpleName();
71
72    private final Context mContext;
73    private final Listener mListener;
74    private final TvInputHal mHal = new TvInputHal(this);
75    private final SparseArray<Connection> mConnections = new SparseArray<Connection>();
76    private final List<TvInputHardwareInfo> mInfoList = new ArrayList<TvInputHardwareInfo>();
77    /* A map from a device ID to the matching TV input ID. */
78    private final SparseArray<String> mHardwareInputIdMap = new SparseArray<String>();
79    /* A map from a HDMI logical address to the matching TV input ID. */
80    private final SparseArray<String> mHdmiCecInputIdMap = new SparseArray<String>();
81    private final Map<String, TvInputInfo> mInputMap = new ArrayMap<String, TvInputInfo>();
82
83    private final AudioManager mAudioManager;
84    private IHdmiControlService mHdmiControlService;
85    private final IHdmiHotplugEventListener mHdmiHotplugEventListener =
86            new HdmiHotplugEventListener();
87    private final IHdmiDeviceEventListener mHdmiDeviceEventListener = new HdmiDeviceEventListener();
88    private final IHdmiInputChangeListener mHdmiInputChangeListener = new HdmiInputChangeListener();
89    private final Set<Integer> mActiveHdmiSources = new HashSet<Integer>();
90    // TODO: Should handle INACTIVE case.
91    private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray();
92
93    // Calls to mListener should happen here.
94    private final Handler mHandler = new ListenerHandler();
95
96    private final Object mLock = new Object();
97
98    public TvInputHardwareManager(Context context, Listener listener) {
99        mContext = context;
100        mListener = listener;
101        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
102        mHal.init();
103    }
104
105    public void onBootPhase(int phase) {
106        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
107            mHdmiControlService = IHdmiControlService.Stub.asInterface(ServiceManager.getService(
108                    Context.HDMI_CONTROL_SERVICE));
109            if (mHdmiControlService != null) {
110                try {
111                    mHdmiControlService.addHotplugEventListener(mHdmiHotplugEventListener);
112                    mHdmiControlService.addDeviceEventListener(mHdmiDeviceEventListener);
113                    mHdmiControlService.setInputChangeListener(mHdmiInputChangeListener);
114                } catch (RemoteException e) {
115                    Slog.w(TAG, "Error registering listeners to HdmiControlService:", e);
116                }
117            }
118        }
119    }
120
121    @Override
122    public void onDeviceAvailable(TvInputHardwareInfo info, TvStreamConfig[] configs) {
123        synchronized (mLock) {
124            Connection connection = new Connection(info);
125            connection.updateConfigsLocked(configs);
126            mConnections.put(info.getDeviceId(), connection);
127            buildInfoListLocked();
128            mHandler.obtainMessage(
129                    ListenerHandler.HARDWARE_DEVICE_ADDED, 0, 0, info).sendToTarget();
130        }
131    }
132
133    private void buildInfoListLocked() {
134        mInfoList.clear();
135        for (int i = 0; i < mConnections.size(); ++i) {
136            mInfoList.add(mConnections.valueAt(i).getHardwareInfoLocked());
137        }
138    }
139
140    @Override
141    public void onDeviceUnavailable(int deviceId) {
142        synchronized (mLock) {
143            Connection connection = mConnections.get(deviceId);
144            if (connection == null) {
145                Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId);
146                return;
147            }
148            connection.resetLocked(null, null, null, null, null);
149            mConnections.remove(deviceId);
150            buildInfoListLocked();
151            TvInputHardwareInfo info = connection.getHardwareInfoLocked();
152            mHandler.obtainMessage(
153                    ListenerHandler.HARDWARE_DEVICE_REMOVED, 0, 0, info).sendToTarget();
154        }
155    }
156
157    @Override
158    public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs) {
159        synchronized (mLock) {
160            Connection connection = mConnections.get(deviceId);
161            if (connection == null) {
162                Slog.e(TAG, "StreamConfigurationChanged: Cannot find a connection with "
163                        + deviceId);
164                return;
165            }
166            connection.updateConfigsLocked(configs);
167            try {
168                connection.getCallbackLocked().onStreamConfigChanged(configs);
169            } catch (RemoteException e) {
170                Slog.e(TAG, "error in onStreamConfigurationChanged", e);
171            }
172        }
173    }
174
175    @Override
176    public void onFirstFrameCaptured(int deviceId, int streamId) {
177        synchronized (mLock) {
178            Connection connection = mConnections.get(deviceId);
179            if (connection == null) {
180                Slog.e(TAG, "FirstFrameCaptured: Cannot find a connection with "
181                        + deviceId);
182                return;
183            }
184            Runnable runnable = connection.getOnFirstFrameCapturedLocked();
185            if (runnable != null) {
186                runnable.run();
187                connection.setOnFirstFrameCapturedLocked(null);
188            }
189        }
190    }
191
192    public List<TvInputHardwareInfo> getHardwareList() {
193        synchronized (mLock) {
194            return mInfoList;
195        }
196    }
197
198    public List<HdmiCecDeviceInfo> getHdmiCecInputDeviceList() {
199        if (mHdmiControlService != null) {
200            try {
201                return mHdmiControlService.getInputDevices();
202            } catch (RemoteException e) {
203                Slog.e(TAG, "error in getHdmiCecInputDeviceList", e);
204            }
205        }
206        return Collections.emptyList();
207    }
208
209    private boolean checkUidChangedLocked(
210            Connection connection, int callingUid, int resolvedUserId) {
211        Integer connectionCallingUid = connection.getCallingUidLocked();
212        Integer connectionResolvedUserId = connection.getResolvedUserIdLocked();
213        if (connectionCallingUid == null || connectionResolvedUserId == null) {
214            return true;
215        }
216        if (connectionCallingUid != callingUid || connectionResolvedUserId != resolvedUserId) {
217            return true;
218        }
219        return false;
220    }
221
222    private int convertConnectedToState(boolean connected) {
223        if (connected) {
224            return INPUT_STATE_CONNECTED;
225        } else {
226            return INPUT_STATE_DISCONNECTED;
227        }
228    }
229
230    public void addHardwareTvInput(int deviceId, TvInputInfo info) {
231        synchronized (mLock) {
232            String oldInputId = mHardwareInputIdMap.get(deviceId);
233            if (oldInputId != null) {
234                Slog.w(TAG, "Trying to override previous registration: old = "
235                        + mInputMap.get(oldInputId) + ":" + deviceId + ", new = "
236                        + info + ":" + deviceId);
237            }
238            mHardwareInputIdMap.put(deviceId, info.getId());
239            mInputMap.put(info.getId(), info);
240
241            for (int i = 0; i < mHdmiStateMap.size(); ++i) {
242                String inputId = findInputIdForHdmiPortLocked(mHdmiStateMap.keyAt(i));
243                if (inputId != null && inputId.equals(info.getId())) {
244                    mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
245                            convertConnectedToState(mHdmiStateMap.valueAt(i)), 0,
246                            inputId).sendToTarget();
247                }
248            }
249        }
250    }
251
252    private static <T> int indexOfEqualValue(SparseArray<T> map, T value) {
253        for (int i = 0; i < map.size(); ++i) {
254            if (map.valueAt(i).equals(value)) {
255                return i;
256            }
257        }
258        return -1;
259    }
260
261    public void addHdmiCecTvInput(int logicalAddress, TvInputInfo info) {
262        if (info.getType() != TvInputInfo.TYPE_HDMI) {
263            throw new IllegalArgumentException("info (" + info + ") has non-HDMI type.");
264        }
265        synchronized (mLock) {
266            String parentId = info.getParentId();
267            int parentIndex = indexOfEqualValue(mHardwareInputIdMap, parentId);
268            if (parentIndex < 0) {
269                throw new IllegalArgumentException("info (" + info + ") has invalid parentId.");
270            }
271            String oldInputId = mHdmiCecInputIdMap.get(logicalAddress);
272            if (oldInputId != null) {
273                Slog.w(TAG, "Trying to override previous registration: old = "
274                        + mInputMap.get(oldInputId) + ":" + logicalAddress + ", new = "
275                        + info + ":" + logicalAddress);
276            }
277            mHdmiCecInputIdMap.put(logicalAddress, info.getId());
278            mInputMap.put(info.getId(), info);
279        }
280    }
281
282    public void removeTvInput(String inputId) {
283        synchronized (mLock) {
284            mInputMap.remove(inputId);
285            int hardwareIndex = indexOfEqualValue(mHardwareInputIdMap, inputId);
286            if (hardwareIndex >= 0) {
287                mHardwareInputIdMap.removeAt(hardwareIndex);
288            }
289            int cecIndex = indexOfEqualValue(mHdmiCecInputIdMap, inputId);
290            if (cecIndex >= 0) {
291                mHdmiCecInputIdMap.removeAt(cecIndex);
292            }
293        }
294    }
295
296    /**
297     * Create a TvInputHardware object with a specific deviceId. One service at a time can access
298     * the object, and if more than one process attempts to create hardware with the same deviceId,
299     * the latest service will get the object and all the other hardware are released. The
300     * release is notified via ITvInputHardwareCallback.onReleased().
301     */
302    public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback,
303            TvInputInfo info, int callingUid, int resolvedUserId) {
304        if (callback == null) {
305            throw new NullPointerException();
306        }
307        synchronized (mLock) {
308            Connection connection = mConnections.get(deviceId);
309            if (connection == null) {
310                Slog.e(TAG, "Invalid deviceId : " + deviceId);
311                return null;
312            }
313            if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
314                TvInputHardwareImpl hardware =
315                        new TvInputHardwareImpl(connection.getHardwareInfoLocked());
316                try {
317                    callback.asBinder().linkToDeath(connection, 0);
318                } catch (RemoteException e) {
319                    hardware.release();
320                    return null;
321                }
322                connection.resetLocked(hardware, callback, info, callingUid, resolvedUserId);
323            }
324            return connection.getHardwareLocked();
325        }
326    }
327
328    /**
329     * Release the specified hardware.
330     */
331    public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid,
332            int resolvedUserId) {
333        synchronized (mLock) {
334            Connection connection = mConnections.get(deviceId);
335            if (connection == null) {
336                Slog.e(TAG, "Invalid deviceId : " + deviceId);
337                return;
338            }
339            if (connection.getHardwareLocked() != hardware
340                    || checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
341                return;
342            }
343            connection.resetLocked(null, null, null, null, null);
344        }
345    }
346
347    private String findInputIdForHdmiPortLocked(int port) {
348        for (TvInputHardwareInfo hardwareInfo : mInfoList) {
349            if (hardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI
350                    && hardwareInfo.getHdmiPortId() == port) {
351                return mHardwareInputIdMap.get(hardwareInfo.getDeviceId());
352            }
353        }
354        return null;
355    }
356
357    private int findDeviceIdForInputIdLocked(String inputId) {
358        for (int i = 0; i < mConnections.size(); ++i) {
359            Connection connection = mConnections.get(i);
360            if (connection.getInfoLocked().getId().equals(inputId)) {
361                return i;
362            }
363        }
364        return -1;
365    }
366
367    /**
368     * Get the list of TvStreamConfig which is buffered mode.
369     */
370    public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int callingUid,
371            int resolvedUserId) {
372        List<TvStreamConfig> configsList = new ArrayList<TvStreamConfig>();
373        synchronized (mLock) {
374            int deviceId = findDeviceIdForInputIdLocked(inputId);
375            if (deviceId < 0) {
376                Slog.e(TAG, "Invalid inputId : " + inputId);
377                return configsList;
378            }
379            Connection connection = mConnections.get(deviceId);
380            for (TvStreamConfig config : connection.getConfigsLocked()) {
381                if (config.getType() == TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) {
382                    configsList.add(config);
383                }
384            }
385        }
386        return configsList;
387    }
388
389    /**
390     * Take a snapshot of the given TV input into the provided Surface.
391     */
392    public boolean captureFrame(String inputId, Surface surface, final TvStreamConfig config,
393            int callingUid, int resolvedUserId) {
394        synchronized (mLock) {
395            int deviceId = findDeviceIdForInputIdLocked(inputId);
396            if (deviceId < 0) {
397                Slog.e(TAG, "Invalid inputId : " + inputId);
398                return false;
399            }
400            Connection connection = mConnections.get(deviceId);
401            final TvInputHardwareImpl hardwareImpl = connection.getHardwareImplLocked();
402            if (hardwareImpl != null) {
403                // Stop previous capture.
404                Runnable runnable = connection.getOnFirstFrameCapturedLocked();
405                if (runnable != null) {
406                    runnable.run();
407                    connection.setOnFirstFrameCapturedLocked(null);
408                }
409
410                boolean result = hardwareImpl.startCapture(surface, config);
411                if (result) {
412                    connection.setOnFirstFrameCapturedLocked(new Runnable() {
413                        @Override
414                        public void run() {
415                            hardwareImpl.stopCapture(config);
416                        }
417                    });
418                }
419                return result;
420            }
421        }
422        return false;
423    }
424
425    private class Connection implements IBinder.DeathRecipient {
426        private final TvInputHardwareInfo mHardwareInfo;
427        private TvInputInfo mInfo;
428        private TvInputHardwareImpl mHardware = null;
429        private ITvInputHardwareCallback mCallback;
430        private TvStreamConfig[] mConfigs = null;
431        private Integer mCallingUid = null;
432        private Integer mResolvedUserId = null;
433        private Runnable mOnFirstFrameCaptured;
434
435        public Connection(TvInputHardwareInfo hardwareInfo) {
436            mHardwareInfo = hardwareInfo;
437        }
438
439        // *Locked methods assume TvInputHardwareManager.mLock is held.
440
441        public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback,
442                TvInputInfo info, Integer callingUid, Integer resolvedUserId) {
443            if (mHardware != null) {
444                try {
445                    mCallback.onReleased();
446                } catch (RemoteException e) {
447                    Slog.e(TAG, "error in Connection::resetLocked", e);
448                }
449                mHardware.release();
450            }
451            mHardware = hardware;
452            mCallback = callback;
453            mInfo = info;
454            mCallingUid = callingUid;
455            mResolvedUserId = resolvedUserId;
456            mOnFirstFrameCaptured = null;
457
458            if (mHardware != null && mCallback != null) {
459                try {
460                    mCallback.onStreamConfigChanged(getConfigsLocked());
461                } catch (RemoteException e) {
462                    Slog.e(TAG, "error in Connection::resetLocked", e);
463                }
464            }
465        }
466
467        public void updateConfigsLocked(TvStreamConfig[] configs) {
468            mConfigs = configs;
469        }
470
471        public TvInputHardwareInfo getHardwareInfoLocked() {
472            return mHardwareInfo;
473        }
474
475        public TvInputInfo getInfoLocked() {
476            return mInfo;
477        }
478
479        public ITvInputHardware getHardwareLocked() {
480            return mHardware;
481        }
482
483        public TvInputHardwareImpl getHardwareImplLocked() {
484            return mHardware;
485        }
486
487        public ITvInputHardwareCallback getCallbackLocked() {
488            return mCallback;
489        }
490
491        public TvStreamConfig[] getConfigsLocked() {
492            return mConfigs;
493        }
494
495        public Integer getCallingUidLocked() {
496            return mCallingUid;
497        }
498
499        public Integer getResolvedUserIdLocked() {
500            return mResolvedUserId;
501        }
502
503        public void setOnFirstFrameCapturedLocked(Runnable runnable) {
504            mOnFirstFrameCaptured = runnable;
505        }
506
507        public Runnable getOnFirstFrameCapturedLocked() {
508            return mOnFirstFrameCaptured;
509        }
510
511        @Override
512        public void binderDied() {
513            synchronized (mLock) {
514                resetLocked(null, null, null, null, null);
515            }
516        }
517    }
518
519    private class TvInputHardwareImpl extends ITvInputHardware.Stub {
520        private final TvInputHardwareInfo mInfo;
521        private boolean mReleased = false;
522        private final Object mImplLock = new Object();
523
524        private final AudioDevicePort mAudioSource;
525        private final AudioDevicePort mAudioSink;
526        private AudioPatch mAudioPatch = null;
527
528        private TvStreamConfig mActiveConfig = null;
529
530        public TvInputHardwareImpl(TvInputHardwareInfo info) {
531            mInfo = info;
532            AudioDevicePort audioSource = null;
533            AudioDevicePort audioSink = null;
534            if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) {
535                ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>();
536                if (mAudioManager.listAudioDevicePorts(devicePorts) == AudioManager.SUCCESS) {
537                    // Find source
538                    for (AudioPort port : devicePorts) {
539                        AudioDevicePort devicePort = (AudioDevicePort) port;
540                        if (devicePort.type() == mInfo.getAudioType() &&
541                                devicePort.address().equals(mInfo.getAudioAddress())) {
542                            audioSource = devicePort;
543                            break;
544                        }
545                    }
546                    // Find sink
547                    // TODO: App may want to specify sink device?
548                    int sinkDevices = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC);
549                    for (AudioPort port : devicePorts) {
550                        AudioDevicePort devicePort = (AudioDevicePort) port;
551                        if (devicePort.type() == sinkDevices) {
552                            audioSink = devicePort;
553                            break;
554                        }
555                    }
556                }
557            }
558            mAudioSource = audioSource;
559            mAudioSink = audioSink;
560        }
561
562        public void release() {
563            synchronized (mImplLock) {
564                if (mAudioPatch != null) {
565                    mAudioManager.releaseAudioPatch(mAudioPatch);
566                    mAudioPatch = null;
567                }
568                mReleased = true;
569            }
570        }
571
572        // A TvInputHardwareImpl object holds only one active session. Therefore, if a client
573        // attempts to call setSurface with different TvStreamConfig objects, the last call will
574        // prevail.
575        @Override
576        public boolean setSurface(Surface surface, TvStreamConfig config)
577                throws RemoteException {
578            synchronized (mImplLock) {
579                if (mReleased) {
580                    throw new IllegalStateException("Device already released.");
581                }
582                if (surface != null && config == null) {
583                    return false;
584                }
585                if (surface == null && mActiveConfig == null) {
586                    return false;
587                }
588                if (mInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
589                    if (surface != null) {
590                        // Set "Active Source" for HDMI.
591                        // TODO(hdmi): mHdmiClient.deviceSelect(...);
592                        mActiveHdmiSources.add(mInfo.getDeviceId());
593                    } else {
594                        mActiveHdmiSources.remove(mInfo.getDeviceId());
595                        if (mActiveHdmiSources.size() == 0) {
596                            // Tell HDMI that no HDMI source is active
597                            // TODO(hdmi): mHdmiClient.portSelect(null);
598                        }
599                    }
600                }
601                if (mAudioSource != null && mAudioSink != null) {
602                    if (surface != null) {
603                        AudioPortConfig sourceConfig = mAudioSource.activeConfig();
604                        AudioPortConfig sinkConfig = mAudioSink.activeConfig();
605                        AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch };
606                        // TODO: build config if activeConfig() == null
607                        mAudioManager.createAudioPatch(
608                                audioPatchArray,
609                                new AudioPortConfig[] { sourceConfig },
610                                new AudioPortConfig[] { sinkConfig });
611                        mAudioPatch = audioPatchArray[0];
612                    } else {
613                        mAudioManager.releaseAudioPatch(mAudioPatch);
614                        mAudioPatch = null;
615                    }
616                }
617                int result = TvInputHal.ERROR_UNKNOWN;
618                if (surface == null) {
619                    result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
620                    mActiveConfig = null;
621                } else {
622                    if (config != mActiveConfig && mActiveConfig != null) {
623                        result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
624                        if (result != TvInputHal.SUCCESS) {
625                            mActiveConfig = null;
626                            return false;
627                        }
628                    }
629                    result = mHal.addStream(mInfo.getDeviceId(), surface, config);
630                    if (result == TvInputHal.SUCCESS) {
631                        mActiveConfig = config;
632                    }
633                }
634                return result == TvInputHal.SUCCESS;
635            }
636        }
637
638        @Override
639        public void setVolume(float volume) throws RemoteException {
640            synchronized (mImplLock) {
641                if (mReleased) {
642                    throw new IllegalStateException("Device already released.");
643                }
644            }
645            // TODO: Use AudioGain?
646        }
647
648        @Override
649        public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException {
650            synchronized (mImplLock) {
651                if (mReleased) {
652                    throw new IllegalStateException("Device already released.");
653                }
654            }
655            if (mInfo.getType() != TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
656                return false;
657            }
658            // TODO(hdmi): mHdmiClient.sendKeyEvent(event);
659            return false;
660        }
661
662        private boolean startCapture(Surface surface, TvStreamConfig config) {
663            synchronized (mImplLock) {
664                if (mReleased) {
665                    return false;
666                }
667                if (surface == null || config == null) {
668                    return false;
669                }
670                if (config.getType() != TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) {
671                    return false;
672                }
673
674                int result = mHal.addStream(mInfo.getDeviceId(), surface, config);
675                return result == TvInputHal.SUCCESS;
676            }
677        }
678
679        private boolean stopCapture(TvStreamConfig config) {
680            synchronized (mImplLock) {
681                if (mReleased) {
682                    return false;
683                }
684                if (config == null) {
685                    return false;
686                }
687
688                int result = mHal.removeStream(mInfo.getDeviceId(), config);
689                return result == TvInputHal.SUCCESS;
690            }
691        }
692    }
693
694    interface Listener {
695        public void onStateChanged(String inputId, int state);
696        public void onHardwareDeviceAdded(TvInputHardwareInfo info);
697        public void onHardwareDeviceRemoved(TvInputHardwareInfo info);
698        public void onHdmiCecDeviceAdded(HdmiCecDeviceInfo cecDevice);
699        public void onHdmiCecDeviceRemoved(HdmiCecDeviceInfo cecDevice);
700    }
701
702    private class ListenerHandler extends Handler {
703        private static final int STATE_CHANGED = 1;
704        private static final int HARDWARE_DEVICE_ADDED = 2;
705        private static final int HARDWARE_DEVICE_REMOVED = 3;
706        private static final int HDMI_CEC_DEVICE_ADDED = 4;
707        private static final int HDMI_CEC_DEVICE_REMOVED = 5;
708
709        @Override
710        public final void handleMessage(Message msg) {
711            switch (msg.what) {
712                case STATE_CHANGED: {
713                    String inputId = (String) msg.obj;
714                    int state = msg.arg1;
715                    mListener.onStateChanged(inputId, state);
716                    break;
717                }
718                case HARDWARE_DEVICE_ADDED: {
719                    TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
720                    mListener.onHardwareDeviceAdded(info);
721                    break;
722                }
723                case HARDWARE_DEVICE_REMOVED: {
724                    TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
725                    mListener.onHardwareDeviceRemoved(info);
726                    break;
727                }
728                case HDMI_CEC_DEVICE_ADDED: {
729                    HdmiCecDeviceInfo info = (HdmiCecDeviceInfo) msg.obj;
730                    mListener.onHdmiCecDeviceAdded(info);
731                    break;
732                }
733                case HDMI_CEC_DEVICE_REMOVED: {
734                    HdmiCecDeviceInfo info = (HdmiCecDeviceInfo) msg.obj;
735                    mListener.onHdmiCecDeviceRemoved(info);
736                    break;
737                }
738                default: {
739                    Slog.w(TAG, "Unhandled message: " + msg);
740                    break;
741                }
742            }
743        }
744    }
745
746    // Listener implementations for HdmiControlService
747
748    private final class HdmiHotplugEventListener extends IHdmiHotplugEventListener.Stub {
749        @Override
750        public void onReceived(HdmiHotplugEvent event) {
751            synchronized (mLock) {
752                mHdmiStateMap.put(event.getPort(), event.isConnected());
753                String inputId = findInputIdForHdmiPortLocked(event.getPort());
754                if (inputId == null) {
755                    return;
756                }
757                mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
758                        convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget();
759            }
760        }
761    }
762
763    private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub {
764        @Override
765        public void onStatusChanged(HdmiCecDeviceInfo deviceInfo, boolean activated) {
766            mHandler.obtainMessage(
767                    activated ? ListenerHandler.HDMI_CEC_DEVICE_ADDED
768                    : ListenerHandler.HDMI_CEC_DEVICE_REMOVED,
769                    0, 0, deviceInfo).sendToTarget();
770        }
771    }
772
773    private final class HdmiInputChangeListener extends IHdmiInputChangeListener.Stub {
774        @Override
775        public void onChanged(HdmiCecDeviceInfo device) throws RemoteException {
776            // TODO: Build a channel Uri for the TvInputInfo associated with the logical device
777            //       and send an intent to TV app
778        }
779    }
780}
781