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