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