TvInputHardwareManager.java revision 71dfa96f536e2ee6fbd4c5b0e517afcc1086d1fb
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.tv;
18
19import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
20import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
21
22import android.content.Context;
23import android.hardware.hdmi.HdmiControlManager;
24import android.hardware.hdmi.HdmiDeviceInfo;
25import android.hardware.hdmi.HdmiHotplugEvent;
26import android.hardware.hdmi.IHdmiControlService;
27import android.hardware.hdmi.IHdmiDeviceEventListener;
28import android.hardware.hdmi.IHdmiHotplugEventListener;
29import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
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.TvInputInfo;
42import android.media.tv.TvStreamConfig;
43import android.os.Handler;
44import android.os.IBinder;
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.internal.os.SomeArgs;
56import com.android.server.SystemService;
57
58import java.util.ArrayList;
59import java.util.Arrays;
60import java.util.Collections;
61import java.util.Iterator;
62import java.util.LinkedList;
63import java.util.List;
64import java.util.Map;
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 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 final List<HdmiDeviceInfo> mHdmiDeviceList = 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> mHdmiInputIdMap = 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 IHdmiSystemAudioModeChangeListener mHdmiSystemAudioModeChangeListener =
94            new HdmiSystemAudioModeChangeListener();
95
96    // TODO: Should handle STANDBY case.
97    private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray();
98    private final List<Message> mPendingHdmiDeviceEvents = new LinkedList<>();
99
100    // Calls to mListener should happen here.
101    private final Handler mHandler = new ListenerHandler();
102
103    private final Object mLock = new Object();
104
105    public TvInputHardwareManager(Context context, Listener listener) {
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                    mHdmiControlService.addSystemAudioModeChangeListener(
120                            mHdmiSystemAudioModeChangeListener);
121                    mHdmiDeviceList.addAll(mHdmiControlService.getInputDevices());
122                } catch (RemoteException e) {
123                    Slog.w(TAG, "Error registering listeners to HdmiControlService:", e);
124                }
125            } else {
126                Slog.w(TAG, "HdmiControlService is not available");
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 devices linked with this hardware.
167                for (Iterator<HdmiDeviceInfo> it = mHdmiDeviceList.iterator(); it.hasNext();) {
168                    HdmiDeviceInfo deviceInfo = it.next();
169                    if (deviceInfo.getPortId() == info.getHdmiPortId()) {
170                        mHandler.obtainMessage(ListenerHandler.HDMI_DEVICE_REMOVED, 0, 0,
171                                deviceInfo).sendToTarget();
172                        it.remove();
173                    }
174                }
175            }
176            mHandler.obtainMessage(
177                    ListenerHandler.HARDWARE_DEVICE_REMOVED, 0, 0, info).sendToTarget();
178        }
179    }
180
181    @Override
182    public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs) {
183        synchronized (mLock) {
184            Connection connection = mConnections.get(deviceId);
185            if (connection == null) {
186                Slog.e(TAG, "StreamConfigurationChanged: Cannot find a connection with "
187                        + deviceId);
188                return;
189            }
190            connection.updateConfigsLocked(configs);
191            String inputId = mHardwareInputIdMap.get(deviceId);
192            if (inputId != null) {
193                mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
194                        convertConnectedToState(configs.length > 0), 0, inputId).sendToTarget();
195            }
196            try {
197                connection.getCallbackLocked().onStreamConfigChanged(configs);
198            } catch (RemoteException e) {
199                Slog.e(TAG, "error in onStreamConfigurationChanged", e);
200            }
201        }
202    }
203
204    @Override
205    public void onFirstFrameCaptured(int deviceId, int streamId) {
206        synchronized (mLock) {
207            Connection connection = mConnections.get(deviceId);
208            if (connection == null) {
209                Slog.e(TAG, "FirstFrameCaptured: Cannot find a connection with "
210                        + deviceId);
211                return;
212            }
213            Runnable runnable = connection.getOnFirstFrameCapturedLocked();
214            if (runnable != null) {
215                runnable.run();
216                connection.setOnFirstFrameCapturedLocked(null);
217            }
218        }
219    }
220
221    public List<TvInputHardwareInfo> getHardwareList() {
222        synchronized (mLock) {
223            return Collections.unmodifiableList(mHardwareList);
224        }
225    }
226
227    public List<HdmiDeviceInfo> getHdmiDeviceList() {
228        synchronized (mLock) {
229            return Collections.unmodifiableList(mHdmiDeviceList);
230        }
231    }
232
233    private boolean checkUidChangedLocked(
234            Connection connection, int callingUid, int resolvedUserId) {
235        Integer connectionCallingUid = connection.getCallingUidLocked();
236        Integer connectionResolvedUserId = connection.getResolvedUserIdLocked();
237        if (connectionCallingUid == null || connectionResolvedUserId == null) {
238            return true;
239        }
240        if (connectionCallingUid != callingUid || connectionResolvedUserId != resolvedUserId) {
241            return true;
242        }
243        return false;
244    }
245
246    private int convertConnectedToState(boolean connected) {
247        if (connected) {
248            return INPUT_STATE_CONNECTED;
249        } else {
250            return INPUT_STATE_DISCONNECTED;
251        }
252    }
253
254    public void addHardwareTvInput(int deviceId, TvInputInfo info) {
255        synchronized (mLock) {
256            String oldInputId = mHardwareInputIdMap.get(deviceId);
257            if (oldInputId != null) {
258                Slog.w(TAG, "Trying to override previous registration: old = "
259                        + mInputMap.get(oldInputId) + ":" + deviceId + ", new = "
260                        + info + ":" + deviceId);
261            }
262            mHardwareInputIdMap.put(deviceId, info.getId());
263            mInputMap.put(info.getId(), info);
264
265            // Process pending state changes
266
267            // For logical HDMI devices, they have information from HDMI CEC signals.
268            for (int i = 0; i < mHdmiStateMap.size(); ++i) {
269                TvInputHardwareInfo hardwareInfo =
270                        findHardwareInfoForHdmiPortLocked(mHdmiStateMap.keyAt(i));
271                if (hardwareInfo == null) {
272                    continue;
273                }
274                String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId());
275                if (inputId != null && inputId.equals(info.getId())) {
276                    mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
277                            convertConnectedToState(mHdmiStateMap.valueAt(i)), 0,
278                            inputId).sendToTarget();
279                    return;
280                }
281            }
282            // For the rest of the devices, we can tell by the number of available streams.
283            Connection connection = mConnections.get(deviceId);
284            if (connection != null) {
285                mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
286                        convertConnectedToState(connection.getConfigsLocked().length > 0), 0,
287                        info.getId()).sendToTarget();
288                return;
289            }
290        }
291    }
292
293    private static <T> int indexOfEqualValue(SparseArray<T> map, T value) {
294        for (int i = 0; i < map.size(); ++i) {
295            if (map.valueAt(i).equals(value)) {
296                return i;
297            }
298        }
299        return -1;
300    }
301
302    private static boolean intArrayContains(int[] array, int value) {
303        for (int element : array) {
304            if (element == value) return true;
305        }
306        return false;
307    }
308
309    public void addHdmiTvInput(int id, TvInputInfo info) {
310        if (info.getType() != TvInputInfo.TYPE_HDMI) {
311            throw new IllegalArgumentException("info (" + info + ") has non-HDMI type.");
312        }
313        synchronized (mLock) {
314            String parentId = info.getParentId();
315            int parentIndex = indexOfEqualValue(mHardwareInputIdMap, parentId);
316            if (parentIndex < 0) {
317                throw new IllegalArgumentException("info (" + info + ") has invalid parentId.");
318            }
319            String oldInputId = mHdmiInputIdMap.get(id);
320            if (oldInputId != null) {
321                Slog.w(TAG, "Trying to override previous registration: old = "
322                        + mInputMap.get(oldInputId) + ":" + id + ", new = "
323                        + info + ":" + id);
324            }
325            mHdmiInputIdMap.put(id, info.getId());
326            mInputMap.put(info.getId(), info);
327        }
328    }
329
330    public void removeTvInput(String inputId) {
331        synchronized (mLock) {
332            mInputMap.remove(inputId);
333            int hardwareIndex = indexOfEqualValue(mHardwareInputIdMap, inputId);
334            if (hardwareIndex >= 0) {
335                mHardwareInputIdMap.removeAt(hardwareIndex);
336            }
337            int deviceIndex = indexOfEqualValue(mHdmiInputIdMap, inputId);
338            if (deviceIndex >= 0) {
339                mHdmiInputIdMap.removeAt(deviceIndex);
340            }
341        }
342    }
343
344    /**
345     * Create a TvInputHardware object with a specific deviceId. One service at a time can access
346     * the object, and if more than one process attempts to create hardware with the same deviceId,
347     * the latest service will get the object and all the other hardware are released. The
348     * release is notified via ITvInputHardwareCallback.onReleased().
349     */
350    public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback,
351            TvInputInfo info, int callingUid, int resolvedUserId) {
352        if (callback == null) {
353            throw new NullPointerException();
354        }
355        synchronized (mLock) {
356            Connection connection = mConnections.get(deviceId);
357            if (connection == null) {
358                Slog.e(TAG, "Invalid deviceId : " + deviceId);
359                return null;
360            }
361            if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
362                TvInputHardwareImpl hardware =
363                        new TvInputHardwareImpl(connection.getHardwareInfoLocked());
364                try {
365                    callback.asBinder().linkToDeath(connection, 0);
366                } catch (RemoteException e) {
367                    hardware.release();
368                    return null;
369                }
370                connection.resetLocked(hardware, callback, info, callingUid, resolvedUserId);
371            }
372            return connection.getHardwareLocked();
373        }
374    }
375
376    /**
377     * Release the specified hardware.
378     */
379    public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid,
380            int resolvedUserId) {
381        synchronized (mLock) {
382            Connection connection = mConnections.get(deviceId);
383            if (connection == null) {
384                Slog.e(TAG, "Invalid deviceId : " + deviceId);
385                return;
386            }
387            if (connection.getHardwareLocked() != hardware
388                    || checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
389                return;
390            }
391            connection.resetLocked(null, null, null, null, null);
392        }
393    }
394
395    private TvInputHardwareInfo findHardwareInfoForHdmiPortLocked(int port) {
396        for (TvInputHardwareInfo hardwareInfo : mHardwareList) {
397            if (hardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI
398                    && hardwareInfo.getHdmiPortId() == port) {
399                return hardwareInfo;
400            }
401        }
402        return null;
403    }
404
405    private int findDeviceIdForInputIdLocked(String inputId) {
406        for (int i = 0; i < mConnections.size(); ++i) {
407            Connection connection = mConnections.get(i);
408            if (connection.getInfoLocked().getId().equals(inputId)) {
409                return i;
410            }
411        }
412        return -1;
413    }
414
415    /**
416     * Get the list of TvStreamConfig which is buffered mode.
417     */
418    public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int callingUid,
419            int resolvedUserId) {
420        List<TvStreamConfig> configsList = new ArrayList<TvStreamConfig>();
421        synchronized (mLock) {
422            int deviceId = findDeviceIdForInputIdLocked(inputId);
423            if (deviceId < 0) {
424                Slog.e(TAG, "Invalid inputId : " + inputId);
425                return configsList;
426            }
427            Connection connection = mConnections.get(deviceId);
428            for (TvStreamConfig config : connection.getConfigsLocked()) {
429                if (config.getType() == TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) {
430                    configsList.add(config);
431                }
432            }
433        }
434        return configsList;
435    }
436
437    /**
438     * Take a snapshot of the given TV input into the provided Surface.
439     */
440    public boolean captureFrame(String inputId, Surface surface, final TvStreamConfig config,
441            int callingUid, int resolvedUserId) {
442        synchronized (mLock) {
443            int deviceId = findDeviceIdForInputIdLocked(inputId);
444            if (deviceId < 0) {
445                Slog.e(TAG, "Invalid inputId : " + inputId);
446                return false;
447            }
448            Connection connection = mConnections.get(deviceId);
449            final TvInputHardwareImpl hardwareImpl = connection.getHardwareImplLocked();
450            if (hardwareImpl != null) {
451                // Stop previous capture.
452                Runnable runnable = connection.getOnFirstFrameCapturedLocked();
453                if (runnable != null) {
454                    runnable.run();
455                    connection.setOnFirstFrameCapturedLocked(null);
456                }
457
458                boolean result = hardwareImpl.startCapture(surface, config);
459                if (result) {
460                    connection.setOnFirstFrameCapturedLocked(new Runnable() {
461                        @Override
462                        public void run() {
463                            hardwareImpl.stopCapture(config);
464                        }
465                    });
466                }
467                return result;
468            }
469        }
470        return false;
471    }
472
473    private void processPendingHdmiDeviceEventsLocked() {
474        for (Iterator<Message> it = mPendingHdmiDeviceEvents.iterator(); it.hasNext(); ) {
475            Message msg = it.next();
476            HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj;
477            TvInputHardwareInfo hardwareInfo =
478                    findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId());
479            if (hardwareInfo != null) {
480                msg.sendToTarget();
481                it.remove();
482            }
483        }
484    }
485
486    private class Connection implements IBinder.DeathRecipient {
487        private final TvInputHardwareInfo mHardwareInfo;
488        private TvInputInfo mInfo;
489        private TvInputHardwareImpl mHardware = null;
490        private ITvInputHardwareCallback mCallback;
491        private TvStreamConfig[] mConfigs = null;
492        private Integer mCallingUid = null;
493        private Integer mResolvedUserId = null;
494        private Runnable mOnFirstFrameCaptured;
495
496        public Connection(TvInputHardwareInfo hardwareInfo) {
497            mHardwareInfo = hardwareInfo;
498        }
499
500        // *Locked methods assume TvInputHardwareManager.mLock is held.
501
502        public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback,
503                TvInputInfo info, Integer callingUid, Integer resolvedUserId) {
504            if (mHardware != null) {
505                try {
506                    mCallback.onReleased();
507                } catch (RemoteException e) {
508                    Slog.e(TAG, "error in Connection::resetLocked", e);
509                }
510                mHardware.release();
511            }
512            mHardware = hardware;
513            mCallback = callback;
514            mInfo = info;
515            mCallingUid = callingUid;
516            mResolvedUserId = resolvedUserId;
517            mOnFirstFrameCaptured = null;
518
519            if (mHardware != null && mCallback != null) {
520                try {
521                    mCallback.onStreamConfigChanged(getConfigsLocked());
522                } catch (RemoteException e) {
523                    Slog.e(TAG, "error in Connection::resetLocked", e);
524                }
525            }
526        }
527
528        public void updateConfigsLocked(TvStreamConfig[] configs) {
529            mConfigs = configs;
530        }
531
532        public TvInputHardwareInfo getHardwareInfoLocked() {
533            return mHardwareInfo;
534        }
535
536        public TvInputInfo getInfoLocked() {
537            return mInfo;
538        }
539
540        public ITvInputHardware getHardwareLocked() {
541            return mHardware;
542        }
543
544        public TvInputHardwareImpl getHardwareImplLocked() {
545            return mHardware;
546        }
547
548        public ITvInputHardwareCallback getCallbackLocked() {
549            return mCallback;
550        }
551
552        public TvStreamConfig[] getConfigsLocked() {
553            return mConfigs;
554        }
555
556        public Integer getCallingUidLocked() {
557            return mCallingUid;
558        }
559
560        public Integer getResolvedUserIdLocked() {
561            return mResolvedUserId;
562        }
563
564        public void setOnFirstFrameCapturedLocked(Runnable runnable) {
565            mOnFirstFrameCaptured = runnable;
566        }
567
568        public Runnable getOnFirstFrameCapturedLocked() {
569            return mOnFirstFrameCaptured;
570        }
571
572        @Override
573        public void binderDied() {
574            synchronized (mLock) {
575                resetLocked(null, null, null, null, null);
576            }
577        }
578    }
579
580    private class TvInputHardwareImpl extends ITvInputHardware.Stub {
581        private final TvInputHardwareInfo mInfo;
582        private boolean mReleased = false;
583        private final Object mImplLock = new Object();
584
585        private final AudioManager.OnAudioPortUpdateListener mAudioListener =
586                new AudioManager.OnAudioPortUpdateListener() {
587            @Override
588            public void onAudioPortListUpdate(AudioPort[] portList) {
589                synchronized (mImplLock) {
590                    updateAudioConfigLocked();
591                }
592            }
593
594            @Override
595            public void onAudioPatchListUpdate(AudioPatch[] patchList) {
596                // No-op
597            }
598
599            @Override
600            public void onServiceDied() {
601                synchronized (mImplLock) {
602                    mAudioSource = null;
603                    mAudioSink = null;
604                    mAudioPatch = null;
605                }
606            }
607        };
608        private int mOverrideAudioType = AudioManager.DEVICE_NONE;
609        private String mOverrideAudioAddress = "";
610        private AudioDevicePort mAudioSource;
611        private AudioDevicePort mAudioSink;
612        private AudioPatch mAudioPatch = null;
613        private float mCommittedVolume = 0.0f;
614        private float mVolume = 0.0f;
615
616        private TvStreamConfig mActiveConfig = null;
617
618        private int mDesiredSamplingRate = 0;
619        private int mDesiredChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT;
620        private int mDesiredFormat = AudioFormat.ENCODING_DEFAULT;
621
622        public TvInputHardwareImpl(TvInputHardwareInfo info) {
623            mInfo = info;
624            mAudioManager.registerAudioPortUpdateListener(mAudioListener);
625            if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) {
626                mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress());
627                mAudioSink = findAudioSinkFromAudioPolicy();
628            }
629        }
630
631        private AudioDevicePort findAudioSinkFromAudioPolicy() {
632            ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>();
633            if (mAudioManager.listAudioDevicePorts(devicePorts) == AudioManager.SUCCESS) {
634                int sinkDevice = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC);
635                for (AudioPort port : devicePorts) {
636                    AudioDevicePort devicePort = (AudioDevicePort) port;
637                    if ((devicePort.type() & sinkDevice) != 0) {
638                        return devicePort;
639                    }
640                }
641            }
642            return null;
643        }
644
645        private AudioDevicePort findAudioDevicePort(int type, String address) {
646            if (type == AudioManager.DEVICE_NONE) {
647                return null;
648            }
649            ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>();
650            if (mAudioManager.listAudioDevicePorts(devicePorts) != AudioManager.SUCCESS) {
651                return null;
652            }
653            for (AudioPort port : devicePorts) {
654                AudioDevicePort devicePort = (AudioDevicePort) port;
655                if (devicePort.type() == type && devicePort.address().equals(address)) {
656                    return devicePort;
657                }
658            }
659            return null;
660        }
661
662        public void release() {
663            synchronized (mImplLock) {
664                mAudioManager.unregisterAudioPortUpdateListener(mAudioListener);
665                if (mAudioPatch != null) {
666                    mAudioManager.releaseAudioPatch(mAudioPatch);
667                    mAudioPatch = null;
668                }
669                mReleased = true;
670            }
671        }
672
673        // A TvInputHardwareImpl object holds only one active session. Therefore, if a client
674        // attempts to call setSurface with different TvStreamConfig objects, the last call will
675        // prevail.
676        @Override
677        public boolean setSurface(Surface surface, TvStreamConfig config)
678                throws RemoteException {
679            synchronized (mImplLock) {
680                if (mReleased) {
681                    throw new IllegalStateException("Device already released.");
682                }
683                if (surface != null && config == null) {
684                    return false;
685                }
686                if (surface == null && mActiveConfig == null) {
687                    return false;
688                }
689
690                int result = TvInputHal.ERROR_UNKNOWN;
691                if (surface == null) {
692                    result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
693                    mActiveConfig = null;
694                } else {
695                    if (!config.equals(mActiveConfig)) {
696                        result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
697                        if (result != TvInputHal.SUCCESS) {
698                            mActiveConfig = null;
699                            return false;
700                        }
701                    }
702                    result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config);
703                    if (result == TvInputHal.SUCCESS) {
704                        mActiveConfig = config;
705                    }
706                }
707                updateAudioConfigLocked();
708                return result == TvInputHal.SUCCESS;
709            }
710        }
711
712        /**
713         * Update audio configuration (source, sink, patch) all up to current state.
714         */
715        private void updateAudioConfigLocked() {
716            boolean sinkUpdated = updateAudioSinkLocked();
717            boolean sourceUpdated = updateAudioSourceLocked();
718            // We can't do updated = updateAudioSinkLocked() || updateAudioSourceLocked() here
719            // because Java won't evaluate the latter if the former is true.
720
721            if (mAudioSource == null || mAudioSink == null || mActiveConfig == null) {
722                if (mAudioPatch != null) {
723                    mAudioManager.releaseAudioPatch(mAudioPatch);
724                    mAudioPatch = null;
725                }
726                return;
727            }
728
729            AudioGainConfig sourceGainConfig = null;
730            if (mAudioSource.gains().length > 0 && mVolume != mCommittedVolume) {
731                AudioGain sourceGain = null;
732                for (AudioGain gain : mAudioSource.gains()) {
733                    if ((gain.mode() & AudioGain.MODE_JOINT) != 0) {
734                        sourceGain = gain;
735                        break;
736                    }
737                }
738                // NOTE: we only change the source gain in MODE_JOINT here.
739                if (sourceGain != null) {
740                    int steps = (sourceGain.maxValue() - sourceGain.minValue())
741                            / sourceGain.stepValue();
742                    int gainValue = sourceGain.minValue();
743                    if (mVolume < 1.0f) {
744                        gainValue += sourceGain.stepValue() * (int) (mVolume * steps + 0.5);
745                    } else {
746                        gainValue = sourceGain.maxValue();
747                    }
748                    int numChannels = 0;
749                    for (int mask = sourceGain.channelMask(); mask > 0; mask >>= 1) {
750                        numChannels += (mask & 1);
751                    }
752                    int[] gainValues = new int[numChannels];
753                    Arrays.fill(gainValues, gainValue);
754                    sourceGainConfig = sourceGain.buildConfig(AudioGain.MODE_JOINT,
755                            sourceGain.channelMask(), gainValues, 0);
756                } else {
757                    Slog.w(TAG, "No audio source gain with MODE_JOINT support exists.");
758                }
759            }
760
761            AudioPortConfig sourceConfig = mAudioSource.activeConfig();
762            AudioPortConfig sinkConfig = mAudioSink.activeConfig();
763            AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch };
764            boolean shouldRecreateAudioPatch = sourceUpdated || sinkUpdated;
765
766            int sinkSamplingRate = mDesiredSamplingRate;
767            int sinkChannelMask = mDesiredChannelMask;
768            int sinkFormat = mDesiredFormat;
769            // If sinkConfig != null and values are set to default, fill in the sinkConfig values.
770            if (sinkConfig != null) {
771                if (sinkSamplingRate == 0) {
772                    sinkSamplingRate = sinkConfig.samplingRate();
773                }
774                if (sinkChannelMask == AudioFormat.CHANNEL_OUT_DEFAULT) {
775                    sinkChannelMask = sinkConfig.channelMask();
776                }
777                if (sinkFormat == AudioFormat.ENCODING_DEFAULT) {
778                    sinkChannelMask = sinkConfig.format();
779                }
780            }
781
782            if (sinkConfig == null
783                    || sinkConfig.samplingRate() != sinkSamplingRate
784                    || sinkConfig.channelMask() != sinkChannelMask
785                    || sinkConfig.format() != sinkFormat) {
786                // Check for compatibility and reset to default if necessary.
787                if (!intArrayContains(mAudioSink.samplingRates(), sinkSamplingRate)
788                        && mAudioSink.samplingRates().length > 0) {
789                    sinkSamplingRate = mAudioSink.samplingRates()[0];
790                }
791                if (!intArrayContains(mAudioSink.channelMasks(), sinkChannelMask)) {
792                    sinkChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT;
793                }
794                if (!intArrayContains(mAudioSink.formats(), sinkFormat)) {
795                    sinkFormat = AudioFormat.ENCODING_DEFAULT;
796                }
797                sinkConfig = mAudioSink.buildConfig(sinkSamplingRate, sinkChannelMask,
798                        sinkFormat, null);
799                shouldRecreateAudioPatch = true;
800            }
801            if (sourceConfig == null || sourceGainConfig != null) {
802                int sourceSamplingRate = 0;
803                if (intArrayContains(mAudioSource.samplingRates(), sinkConfig.samplingRate())) {
804                    sourceSamplingRate = sinkConfig.samplingRate();
805                } else if (mAudioSource.samplingRates().length > 0) {
806                    // Use any sampling rate and hope audio patch can handle resampling...
807                    sourceSamplingRate = mAudioSource.samplingRates()[0];
808                }
809                int sourceChannelMask = AudioFormat.CHANNEL_IN_DEFAULT;
810                for (int inChannelMask : mAudioSource.channelMasks()) {
811                    if (AudioFormat.channelCountFromOutChannelMask(sinkConfig.channelMask())
812                            == AudioFormat.channelCountFromInChannelMask(inChannelMask)) {
813                        sourceChannelMask = inChannelMask;
814                        break;
815                    }
816                }
817                int sourceFormat = AudioFormat.ENCODING_DEFAULT;
818                if (intArrayContains(mAudioSource.formats(), sinkConfig.format())) {
819                    sourceFormat = sinkConfig.format();
820                }
821                sourceConfig = mAudioSource.buildConfig(sourceSamplingRate, sourceChannelMask,
822                        sourceFormat, sourceGainConfig);
823                shouldRecreateAudioPatch = true;
824            }
825            if (shouldRecreateAudioPatch) {
826                mCommittedVolume = mVolume;
827                mAudioManager.createAudioPatch(
828                        audioPatchArray,
829                        new AudioPortConfig[] { sourceConfig },
830                        new AudioPortConfig[] { sinkConfig });
831                mAudioPatch = audioPatchArray[0];
832                if (sourceGainConfig != null) {
833                    mAudioManager.setAudioPortGain(mAudioSource, sourceGainConfig);
834                }
835            }
836        }
837
838        @Override
839        public void setStreamVolume(float volume) throws RemoteException {
840            synchronized (mImplLock) {
841                if (mReleased) {
842                    throw new IllegalStateException("Device already released.");
843                }
844                mVolume = volume;
845                updateAudioConfigLocked();
846            }
847        }
848
849        @Override
850        public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException {
851            synchronized (mImplLock) {
852                if (mReleased) {
853                    throw new IllegalStateException("Device already released.");
854                }
855            }
856            if (mInfo.getType() != TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
857                return false;
858            }
859            // TODO(hdmi): mHdmiClient.sendKeyEvent(event);
860            return false;
861        }
862
863        private boolean startCapture(Surface surface, TvStreamConfig config) {
864            synchronized (mImplLock) {
865                if (mReleased) {
866                    return false;
867                }
868                if (surface == null || config == null) {
869                    return false;
870                }
871                if (config.getType() != TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) {
872                    return false;
873                }
874
875                int result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config);
876                return result == TvInputHal.SUCCESS;
877            }
878        }
879
880        private boolean stopCapture(TvStreamConfig config) {
881            synchronized (mImplLock) {
882                if (mReleased) {
883                    return false;
884                }
885                if (config == null) {
886                    return false;
887                }
888
889                int result = mHal.removeStream(mInfo.getDeviceId(), config);
890                return result == TvInputHal.SUCCESS;
891            }
892        }
893
894        private boolean updateAudioSourceLocked() {
895            if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) {
896                return false;
897            }
898            AudioDevicePort previousSource = mAudioSource;
899            mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress());
900            return mAudioSource == null ? (previousSource != null)
901                    : !mAudioSource.equals(previousSource);
902        }
903
904        private boolean updateAudioSinkLocked() {
905            if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) {
906                return false;
907            }
908            AudioDevicePort previousSink = mAudioSink;
909            if (mOverrideAudioType == AudioManager.DEVICE_NONE) {
910                mAudioSink = findAudioSinkFromAudioPolicy();
911            } else {
912                AudioDevicePort audioSink =
913                        findAudioDevicePort(mOverrideAudioType, mOverrideAudioAddress);
914                if (audioSink != null) {
915                    mAudioSink = audioSink;
916                }
917            }
918            return mAudioSink == null ? (previousSink != null) : !mAudioSink.equals(previousSink);
919        }
920
921        private void handleAudioSinkUpdated() {
922            synchronized (mImplLock) {
923                updateAudioConfigLocked();
924            }
925        }
926
927        @Override
928        public void overrideAudioSink(int audioType, String audioAddress, int samplingRate,
929                int channelMask, int format) {
930            synchronized (mImplLock) {
931                mOverrideAudioType = audioType;
932                mOverrideAudioAddress = audioAddress;
933
934                mDesiredSamplingRate = samplingRate;
935                mDesiredChannelMask = channelMask;
936                mDesiredFormat = format;
937
938                updateAudioConfigLocked();
939            }
940        }
941    }
942
943    interface Listener {
944        public void onStateChanged(String inputId, int state);
945        public void onHardwareDeviceAdded(TvInputHardwareInfo info);
946        public void onHardwareDeviceRemoved(TvInputHardwareInfo info);
947        public void onHdmiDeviceAdded(HdmiDeviceInfo device);
948        public void onHdmiDeviceRemoved(HdmiDeviceInfo device);
949        public void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo device);
950    }
951
952    private class ListenerHandler extends Handler {
953        private static final int STATE_CHANGED = 1;
954        private static final int HARDWARE_DEVICE_ADDED = 2;
955        private static final int HARDWARE_DEVICE_REMOVED = 3;
956        private static final int HDMI_DEVICE_ADDED = 4;
957        private static final int HDMI_DEVICE_REMOVED = 5;
958        private static final int HDMI_DEVICE_UPDATED = 6;
959
960        @Override
961        public final void handleMessage(Message msg) {
962            switch (msg.what) {
963                case STATE_CHANGED: {
964                    String inputId = (String) msg.obj;
965                    int state = msg.arg1;
966                    mListener.onStateChanged(inputId, state);
967                    break;
968                }
969                case HARDWARE_DEVICE_ADDED: {
970                    TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
971                    mListener.onHardwareDeviceAdded(info);
972                    break;
973                }
974                case HARDWARE_DEVICE_REMOVED: {
975                    TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
976                    mListener.onHardwareDeviceRemoved(info);
977                    break;
978                }
979                case HDMI_DEVICE_ADDED: {
980                    HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
981                    mListener.onHdmiDeviceAdded(info);
982                    break;
983                }
984                case HDMI_DEVICE_REMOVED: {
985                    HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
986                    mListener.onHdmiDeviceRemoved(info);
987                    break;
988                }
989                case HDMI_DEVICE_UPDATED: {
990                    HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
991                    String inputId = null;
992                    synchronized (mLock) {
993                        inputId = mHdmiInputIdMap.get(info.getId());
994                    }
995                    if (inputId != null) {
996                        mListener.onHdmiDeviceUpdated(inputId, info);
997                    } else {
998                        Slog.w(TAG, "Could not resolve input ID matching the device info; "
999                                + "ignoring.");
1000                    }
1001                    break;
1002                }
1003                default: {
1004                    Slog.w(TAG, "Unhandled message: " + msg);
1005                    break;
1006                }
1007            }
1008        }
1009    }
1010
1011    // Listener implementations for HdmiControlService
1012
1013    private final class HdmiHotplugEventListener extends IHdmiHotplugEventListener.Stub {
1014        @Override
1015        public void onReceived(HdmiHotplugEvent event) {
1016            synchronized (mLock) {
1017                mHdmiStateMap.put(event.getPort(), event.isConnected());
1018                TvInputHardwareInfo hardwareInfo =
1019                        findHardwareInfoForHdmiPortLocked(event.getPort());
1020                if (hardwareInfo == null) {
1021                    return;
1022                }
1023                String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId());
1024                if (inputId == null) {
1025                    return;
1026                }
1027                mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
1028                        convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget();
1029            }
1030        }
1031    }
1032
1033    private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub {
1034        @Override
1035        public void onStatusChanged(HdmiDeviceInfo deviceInfo, int status) {
1036            synchronized (mLock) {
1037                int messageType = 0;
1038                Object obj = null;
1039                switch (status) {
1040                    case HdmiControlManager.DEVICE_EVENT_ADD_DEVICE: {
1041                        if (findHdmiDeviceInfo(deviceInfo.getId()) == null) {
1042                            mHdmiDeviceList.add(deviceInfo);
1043                        } else {
1044                            Slog.w(TAG, "The list already contains " + deviceInfo + "; ignoring.");
1045                            return;
1046                        }
1047                        messageType = ListenerHandler.HDMI_DEVICE_ADDED;
1048                        obj = deviceInfo;
1049                        break;
1050                    }
1051                    case HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE: {
1052                        HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId());
1053                        if (!mHdmiDeviceList.remove(originalDeviceInfo)) {
1054                            Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring.");
1055                            return;
1056                        }
1057                        messageType = ListenerHandler.HDMI_DEVICE_REMOVED;
1058                        obj = deviceInfo;
1059                        break;
1060                    }
1061                    case HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE: {
1062                        HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId());
1063                        if (!mHdmiDeviceList.remove(originalDeviceInfo)) {
1064                            Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring.");
1065                            return;
1066                        }
1067                        mHdmiDeviceList.add(deviceInfo);
1068                        messageType = ListenerHandler.HDMI_DEVICE_UPDATED;
1069                        obj = deviceInfo;
1070                        break;
1071                    }
1072                }
1073
1074                Message msg = mHandler.obtainMessage(messageType, 0, 0, obj);
1075                if (findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId()) != null) {
1076                    msg.sendToTarget();
1077                } else {
1078                    mPendingHdmiDeviceEvents.add(msg);
1079                }
1080            }
1081        }
1082
1083        private HdmiDeviceInfo findHdmiDeviceInfo(int id) {
1084            for (HdmiDeviceInfo info : mHdmiDeviceList) {
1085                if (info.getId() == id) {
1086                    return info;
1087                }
1088            }
1089            return null;
1090        }
1091    }
1092
1093    private final class HdmiSystemAudioModeChangeListener extends
1094        IHdmiSystemAudioModeChangeListener.Stub {
1095        @Override
1096        public void onStatusChanged(boolean enabled) throws RemoteException {
1097            synchronized (mLock) {
1098                for (int i = 0; i < mConnections.size(); ++i) {
1099                    TvInputHardwareImpl impl = mConnections.valueAt(i).getHardwareImplLocked();
1100                    if (impl != null) {
1101                        impl.handleAudioSinkUpdated();
1102                    }
1103                }
1104            }
1105        }
1106    }
1107}
1108