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