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