TvInputHardwareManager.java revision d38bf476f6a31602e92a3207a4ceb29bf965f9aa
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 logicalAddress, 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(logicalAddress);
298            if (oldInputId != null) {
299                Slog.w(TAG, "Trying to override previous registration: old = "
300                        + mInputMap.get(oldInputId) + ":" + logicalAddress + ", new = "
301                        + info + ":" + logicalAddress);
302            }
303            mHdmiInputIdMap.put(logicalAddress, 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                    updateAudioSinkLocked();
569                    if (mInfo.getAudioType() != AudioManager.DEVICE_NONE && mAudioSource == null) {
570                        mAudioSource = findAudioDevicePort(mInfo.getAudioType(),
571                                mInfo.getAudioAddress());
572                        if (mActiveConfig != null) {
573                            updateAudioPatchLocked();
574                        }
575                    }
576                }
577            }
578
579            @Override
580            public void onAudioPatchListUpdate(AudioPatch[] patchList) {
581                // No-op
582            }
583
584            @Override
585            public void onServiceDied() {
586                synchronized (mImplLock) {
587                    mAudioSource = null;
588                    mAudioSink = null;
589                    mAudioPatch = null;
590                }
591            }
592        };
593        private int mOverrideAudioType = AudioManager.DEVICE_NONE;
594        private String mOverrideAudioAddress = "";
595        private AudioDevicePort mAudioSource;
596        private AudioDevicePort mAudioSink;
597        private AudioPatch mAudioPatch = null;
598        private float mCommittedVolume = 0.0f;
599        private float mVolume = 0.0f;
600
601        private TvStreamConfig mActiveConfig = null;
602
603        private int mDesiredSamplingRate = 0;
604        private int mDesiredChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT;
605        private int mDesiredFormat = AudioFormat.ENCODING_DEFAULT;
606
607        public TvInputHardwareImpl(TvInputHardwareInfo info) {
608            mInfo = info;
609            mAudioManager.registerAudioPortUpdateListener(mAudioListener);
610            if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) {
611                mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress());
612                mAudioSink = findAudioSinkFromAudioPolicy();
613            }
614        }
615
616        private AudioDevicePort findAudioSinkFromAudioPolicy() {
617            ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>();
618            if (mAudioManager.listAudioDevicePorts(devicePorts) == AudioManager.SUCCESS) {
619                int sinkDevice = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC);
620                for (AudioPort port : devicePorts) {
621                    AudioDevicePort devicePort = (AudioDevicePort) port;
622                    if ((devicePort.type() & sinkDevice) != 0) {
623                        return devicePort;
624                    }
625                }
626            }
627            return null;
628        }
629
630        private AudioDevicePort findAudioDevicePort(int type, String address) {
631            if (type == AudioManager.DEVICE_NONE) {
632                return null;
633            }
634            ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>();
635            if (mAudioManager.listAudioDevicePorts(devicePorts) != AudioManager.SUCCESS) {
636                return null;
637            }
638            for (AudioPort port : devicePorts) {
639                AudioDevicePort devicePort = (AudioDevicePort) port;
640                if (devicePort.type() == type && devicePort.address().equals(address)) {
641                    return devicePort;
642                }
643            }
644            return null;
645        }
646
647        public void release() {
648            synchronized (mImplLock) {
649                mAudioManager.unregisterAudioPortUpdateListener(mAudioListener);
650                if (mAudioPatch != null) {
651                    mAudioManager.releaseAudioPatch(mAudioPatch);
652                    mAudioPatch = null;
653                }
654                mReleased = true;
655            }
656        }
657
658        // A TvInputHardwareImpl object holds only one active session. Therefore, if a client
659        // attempts to call setSurface with different TvStreamConfig objects, the last call will
660        // prevail.
661        @Override
662        public boolean setSurface(Surface surface, TvStreamConfig config)
663                throws RemoteException {
664            synchronized (mImplLock) {
665                if (mReleased) {
666                    throw new IllegalStateException("Device already released.");
667                }
668                if (surface != null && config == null) {
669                    return false;
670                }
671                if (surface == null && mActiveConfig == null) {
672                    return false;
673                }
674                if (mAudioSource != null && mAudioSink != null) {
675                    if (surface != null) {
676                        updateAudioPatchLocked();
677                    } else {
678                        mAudioManager.releaseAudioPatch(mAudioPatch);
679                        mAudioPatch = null;
680                    }
681                }
682                int result = TvInputHal.ERROR_UNKNOWN;
683                if (surface == null) {
684                    result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
685                    mActiveConfig = null;
686                } else {
687                    if (config != mActiveConfig && mActiveConfig != null) {
688                        result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
689                        if (result != TvInputHal.SUCCESS) {
690                            mActiveConfig = null;
691                            return false;
692                        }
693                    }
694                    result = mHal.addStream(mInfo.getDeviceId(), surface, config);
695                    if (result == TvInputHal.SUCCESS) {
696                        mActiveConfig = config;
697                    }
698                }
699                return result == TvInputHal.SUCCESS;
700            }
701        }
702
703        private void updateAudioPatchLocked() {
704            if (mAudioSource == null || mAudioSink == null) {
705                if (mAudioPatch != null) {
706                    throw new IllegalStateException("Audio patch should be null if audio source "
707                            + "or sink is null.");
708                }
709                return;
710            }
711
712            AudioGainConfig sourceGainConfig = null;
713            if (mAudioSource.gains().length > 0 && mVolume != mCommittedVolume) {
714                AudioGain sourceGain = null;
715                for (AudioGain gain : mAudioSource.gains()) {
716                    if ((gain.mode() & AudioGain.MODE_JOINT) != 0) {
717                        sourceGain = gain;
718                        break;
719                    }
720                }
721                // NOTE: we only change the source gain in MODE_JOINT here.
722                if (sourceGain != null) {
723                    int steps = (sourceGain.maxValue() - sourceGain.minValue())
724                            / sourceGain.stepValue();
725                    int gainValue = sourceGain.minValue();
726                    if (mVolume < 1.0f) {
727                        gainValue += sourceGain.stepValue() * (int) (mVolume * steps + 0.5);
728                    } else {
729                        gainValue = sourceGain.maxValue();
730                    }
731                    int numChannels = 0;
732                    for (int mask = sourceGain.channelMask(); mask > 0; mask >>= 1) {
733                        numChannels += (mask & 1);
734                    }
735                    int[] gainValues = new int[numChannels];
736                    Arrays.fill(gainValues, gainValue);
737                    sourceGainConfig = sourceGain.buildConfig(AudioGain.MODE_JOINT,
738                            sourceGain.channelMask(), gainValues, 0);
739                } else {
740                    Slog.w(TAG, "No audio source gain with MODE_JOINT support exists.");
741                }
742            }
743
744            AudioPortConfig sourceConfig = mAudioSource.activeConfig();
745            AudioPortConfig sinkConfig = mAudioSink.activeConfig();
746            AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch };
747            boolean shouldRecreateAudioPatch = false;
748            if (sinkConfig == null
749                    || (mDesiredSamplingRate != 0
750                            && sinkConfig.samplingRate() != mDesiredSamplingRate)
751                    || (mDesiredChannelMask != AudioFormat.CHANNEL_OUT_DEFAULT
752                            && sinkConfig.channelMask() != mDesiredChannelMask)
753                    || (mDesiredFormat != AudioFormat.ENCODING_DEFAULT
754                            && sinkConfig.format() != mDesiredFormat)) {
755                sinkConfig = mAudioSource.buildConfig(mDesiredSamplingRate, mDesiredChannelMask,
756                        mDesiredFormat, null);
757                shouldRecreateAudioPatch = true;
758            }
759            if (sourceConfig == null || sourceGainConfig != null) {
760                sourceConfig = mAudioSource.buildConfig(sinkConfig.samplingRate(),
761                        sinkConfig.channelMask(), sinkConfig.format(), sourceGainConfig);
762                shouldRecreateAudioPatch = true;
763            }
764            if (shouldRecreateAudioPatch) {
765                mCommittedVolume = mVolume;
766                mAudioManager.createAudioPatch(
767                        audioPatchArray,
768                        new AudioPortConfig[] { sourceConfig },
769                        new AudioPortConfig[] { sinkConfig });
770                mAudioPatch = audioPatchArray[0];
771            }
772        }
773
774        @Override
775        public void setStreamVolume(float volume) throws RemoteException {
776            synchronized (mImplLock) {
777                if (mReleased) {
778                    throw new IllegalStateException("Device already released.");
779                }
780                mVolume = volume;
781                updateAudioPatchLocked();
782            }
783        }
784
785        @Override
786        public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException {
787            synchronized (mImplLock) {
788                if (mReleased) {
789                    throw new IllegalStateException("Device already released.");
790                }
791            }
792            if (mInfo.getType() != TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
793                return false;
794            }
795            // TODO(hdmi): mHdmiClient.sendKeyEvent(event);
796            return false;
797        }
798
799        private boolean startCapture(Surface surface, TvStreamConfig config) {
800            synchronized (mImplLock) {
801                if (mReleased) {
802                    return false;
803                }
804                if (surface == null || config == null) {
805                    return false;
806                }
807                if (config.getType() != TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) {
808                    return false;
809                }
810
811                int result = mHal.addStream(mInfo.getDeviceId(), surface, config);
812                return result == TvInputHal.SUCCESS;
813            }
814        }
815
816        private boolean stopCapture(TvStreamConfig config) {
817            synchronized (mImplLock) {
818                if (mReleased) {
819                    return false;
820                }
821                if (config == null) {
822                    return false;
823                }
824
825                int result = mHal.removeStream(mInfo.getDeviceId(), config);
826                return result == TvInputHal.SUCCESS;
827            }
828        }
829
830        private void updateAudioSinkLocked() {
831            if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) {
832                return;
833            }
834            if (mOverrideAudioType == AudioManager.DEVICE_NONE) {
835                mAudioSink = findAudioSinkFromAudioPolicy();
836            } else {
837                AudioDevicePort audioSink =
838                        findAudioDevicePort(mOverrideAudioType, mOverrideAudioAddress);
839                if (audioSink != null) {
840                    mAudioSink = audioSink;
841                }
842            }
843        }
844
845        private void handleAudioSinkUpdated() {
846            synchronized (mImplLock) {
847                updateAudioSinkLocked();
848                updateAudioPatchLocked();
849            }
850        }
851
852        @Override
853        public void overrideAudioSink(int audioType, String audioAddress, int samplingRate,
854                int channelMask, int format) {
855            synchronized (mImplLock) {
856                mOverrideAudioType = audioType;
857                mOverrideAudioAddress = audioAddress;
858                updateAudioSinkLocked();
859
860                mDesiredSamplingRate = samplingRate;
861                mDesiredChannelMask = channelMask;
862                mDesiredFormat = format;
863
864                if (mAudioPatch != null) {
865                    updateAudioPatchLocked();
866                }
867            }
868        }
869    }
870
871    interface Listener {
872        public void onStateChanged(String inputId, int state);
873        public void onHardwareDeviceAdded(TvInputHardwareInfo info);
874        public void onHardwareDeviceRemoved(TvInputHardwareInfo info);
875        public void onHdmiDeviceAdded(HdmiDeviceInfo device);
876        public void onHdmiDeviceRemoved(HdmiDeviceInfo device);
877        public void onHdmiDeviceUpdated(HdmiDeviceInfo device);
878    }
879
880    private class ListenerHandler extends Handler {
881        private static final int STATE_CHANGED = 1;
882        private static final int HARDWARE_DEVICE_ADDED = 2;
883        private static final int HARDWARE_DEVICE_REMOVED = 3;
884        private static final int HDMI_DEVICE_ADDED = 4;
885        private static final int HDMI_DEVICE_REMOVED = 5;
886        private static final int HDMI_DEVICE_UPDATED = 6;
887
888        @Override
889        public final void handleMessage(Message msg) {
890            switch (msg.what) {
891                case STATE_CHANGED: {
892                    String inputId = (String) msg.obj;
893                    int state = msg.arg1;
894                    mListener.onStateChanged(inputId, state);
895                    break;
896                }
897                case HARDWARE_DEVICE_ADDED: {
898                    TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
899                    mListener.onHardwareDeviceAdded(info);
900                    break;
901                }
902                case HARDWARE_DEVICE_REMOVED: {
903                    TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
904                    mListener.onHardwareDeviceRemoved(info);
905                    break;
906                }
907                case HDMI_DEVICE_ADDED: {
908                    HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
909                    mListener.onHdmiDeviceAdded(info);
910                    break;
911                }
912                case HDMI_DEVICE_REMOVED: {
913                    HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
914                    mListener.onHdmiDeviceRemoved(info);
915                    break;
916                }
917                case HDMI_DEVICE_UPDATED: {
918                    HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
919                    mListener.onHdmiDeviceUpdated(info);
920                }
921                default: {
922                    Slog.w(TAG, "Unhandled message: " + msg);
923                    break;
924                }
925            }
926        }
927    }
928
929    // Listener implementations for HdmiControlService
930
931    private final class HdmiHotplugEventListener extends IHdmiHotplugEventListener.Stub {
932        @Override
933        public void onReceived(HdmiHotplugEvent event) {
934            synchronized (mLock) {
935                mHdmiStateMap.put(event.getPort(), event.isConnected());
936                TvInputHardwareInfo hardwareInfo =
937                        findHardwareInfoForHdmiPortLocked(event.getPort());
938                if (hardwareInfo == null) {
939                    return;
940                }
941                String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId());
942                if (inputId == null) {
943                    return;
944                }
945                mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
946                        convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget();
947            }
948        }
949    }
950
951    private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub {
952        @Override
953        public void onStatusChanged(HdmiDeviceInfo deviceInfo, int status) {
954            synchronized (mLock) {
955                int messageType = 0;
956                switch (status) {
957                    case HdmiControlManager.DEVICE_EVENT_ADD_DEVICE: {
958                        if (!mHdmiDeviceList.contains(deviceInfo)) {
959                            mHdmiDeviceList.add(deviceInfo);
960                        } else {
961                            Slog.w(TAG, "The list already contains " + deviceInfo + "; ignoring.");
962                            return;
963                        }
964                        messageType = ListenerHandler.HDMI_DEVICE_ADDED;
965                        break;
966                    }
967                    case HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE: {
968                        if (!mHdmiDeviceList.remove(deviceInfo)) {
969                            Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring.");
970                            return;
971                        }
972                        messageType = ListenerHandler.HDMI_DEVICE_REMOVED;
973                        break;
974                    }
975                    case HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE: {
976                        if (!mHdmiDeviceList.remove(deviceInfo)) {
977                            Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring.");
978                            return;
979                        }
980                        mHdmiDeviceList.add(deviceInfo);
981                        messageType = ListenerHandler.HDMI_DEVICE_UPDATED;
982                        break;
983                    }
984                }
985
986                Message msg = mHandler.obtainMessage(messageType, 0, 0, deviceInfo);
987                if (findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId()) != null) {
988                    msg.sendToTarget();
989                } else {
990                    mPendingHdmiDeviceEvents.add(msg);
991                }
992            }
993        }
994    }
995
996    private final class HdmiSystemAudioModeChangeListener extends
997        IHdmiSystemAudioModeChangeListener.Stub {
998        @Override
999        public void onStatusChanged(boolean enabled) throws RemoteException {
1000            synchronized (mLock) {
1001                for (int i = 0; i < mConnections.size(); ++i) {
1002                    TvInputHardwareImpl impl = mConnections.valueAt(i).getHardwareImplLocked();
1003                    impl.handleAudioSinkUpdated();
1004                }
1005            }
1006        }
1007    }
1008}
1009