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