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