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