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