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