TvInputHardwareManager.java revision 5b820a8aa1a200824e44aede6bc1e381a6d69dd5
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                if (surface != null && config == null) {
677                    return false;
678                }
679                if (surface == null && mActiveConfig == null) {
680                    return false;
681                }
682
683                int result = TvInputHal.ERROR_UNKNOWN;
684                if (surface == null) {
685                    result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
686                    mActiveConfig = null;
687                } else {
688                    if (!config.equals(mActiveConfig)) {
689                        result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
690                        if (result != TvInputHal.SUCCESS) {
691                            mActiveConfig = null;
692                            return false;
693                        }
694                    }
695                    result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config);
696                    if (result == TvInputHal.SUCCESS) {
697                        mActiveConfig = config;
698                    }
699                }
700                updateAudioConfigLocked();
701                return result == TvInputHal.SUCCESS;
702            }
703        }
704
705        /**
706         * Update audio configuration (source, sink, patch) all up to current state.
707         */
708        private void updateAudioConfigLocked() {
709            boolean sinkUpdated = updateAudioSinkLocked();
710            boolean sourceUpdated = updateAudioSourceLocked();
711            // We can't do updated = updateAudioSinkLocked() || updateAudioSourceLocked() here
712            // because Java won't evaluate the latter if the former is true.
713
714            if (mAudioSource == null || mAudioSink == null || mActiveConfig == null) {
715                if (mAudioPatch != null) {
716                    mAudioManager.releaseAudioPatch(mAudioPatch);
717                    mAudioPatch = null;
718                }
719                return;
720            }
721
722            AudioGainConfig sourceGainConfig = null;
723            if (mAudioSource.gains().length > 0 && mVolume != mCommittedVolume) {
724                AudioGain sourceGain = null;
725                for (AudioGain gain : mAudioSource.gains()) {
726                    if ((gain.mode() & AudioGain.MODE_JOINT) != 0) {
727                        sourceGain = gain;
728                        break;
729                    }
730                }
731                // NOTE: we only change the source gain in MODE_JOINT here.
732                if (sourceGain != null) {
733                    int steps = (sourceGain.maxValue() - sourceGain.minValue())
734                            / sourceGain.stepValue();
735                    int gainValue = sourceGain.minValue();
736                    if (mVolume < 1.0f) {
737                        gainValue += sourceGain.stepValue() * (int) (mVolume * steps + 0.5);
738                    } else {
739                        gainValue = sourceGain.maxValue();
740                    }
741                    int numChannels = 0;
742                    for (int mask = sourceGain.channelMask(); mask > 0; mask >>= 1) {
743                        numChannels += (mask & 1);
744                    }
745                    int[] gainValues = new int[numChannels];
746                    Arrays.fill(gainValues, gainValue);
747                    sourceGainConfig = sourceGain.buildConfig(AudioGain.MODE_JOINT,
748                            sourceGain.channelMask(), gainValues, 0);
749                } else {
750                    Slog.w(TAG, "No audio source gain with MODE_JOINT support exists.");
751                }
752            }
753
754            AudioPortConfig sourceConfig = mAudioSource.activeConfig();
755            AudioPortConfig sinkConfig = mAudioSink.activeConfig();
756            AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch };
757            boolean shouldRecreateAudioPatch = sourceUpdated || sinkUpdated;
758            if (sinkConfig == null
759                    || (mDesiredSamplingRate != 0
760                            && sinkConfig.samplingRate() != mDesiredSamplingRate)
761                    || (mDesiredChannelMask != AudioFormat.CHANNEL_OUT_DEFAULT
762                            && sinkConfig.channelMask() != mDesiredChannelMask)
763                    || (mDesiredFormat != AudioFormat.ENCODING_DEFAULT
764                            && sinkConfig.format() != mDesiredFormat)) {
765                sinkConfig = mAudioSink.buildConfig(mDesiredSamplingRate, mDesiredChannelMask,
766                        mDesiredFormat, null);
767                shouldRecreateAudioPatch = true;
768            }
769            if (sourceConfig == null || sourceGainConfig != null) {
770                sourceConfig = mAudioSource.buildConfig(sinkConfig.samplingRate(),
771                        sinkConfig.channelMask(), sinkConfig.format(), sourceGainConfig);
772                shouldRecreateAudioPatch = true;
773            }
774            if (shouldRecreateAudioPatch) {
775                mCommittedVolume = mVolume;
776                mAudioManager.createAudioPatch(
777                        audioPatchArray,
778                        new AudioPortConfig[] { sourceConfig },
779                        new AudioPortConfig[] { sinkConfig });
780                mAudioPatch = audioPatchArray[0];
781            }
782        }
783
784        @Override
785        public void setStreamVolume(float volume) throws RemoteException {
786            synchronized (mImplLock) {
787                if (mReleased) {
788                    throw new IllegalStateException("Device already released.");
789                }
790                mVolume = volume;
791                updateAudioConfigLocked();
792            }
793        }
794
795        @Override
796        public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException {
797            synchronized (mImplLock) {
798                if (mReleased) {
799                    throw new IllegalStateException("Device already released.");
800                }
801            }
802            if (mInfo.getType() != TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
803                return false;
804            }
805            // TODO(hdmi): mHdmiClient.sendKeyEvent(event);
806            return false;
807        }
808
809        private boolean startCapture(Surface surface, TvStreamConfig config) {
810            synchronized (mImplLock) {
811                if (mReleased) {
812                    return false;
813                }
814                if (surface == null || config == null) {
815                    return false;
816                }
817                if (config.getType() != TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) {
818                    return false;
819                }
820
821                int result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config);
822                return result == TvInputHal.SUCCESS;
823            }
824        }
825
826        private boolean stopCapture(TvStreamConfig config) {
827            synchronized (mImplLock) {
828                if (mReleased) {
829                    return false;
830                }
831                if (config == null) {
832                    return false;
833                }
834
835                int result = mHal.removeStream(mInfo.getDeviceId(), config);
836                return result == TvInputHal.SUCCESS;
837            }
838        }
839
840        private boolean updateAudioSourceLocked() {
841            if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) {
842                return false;
843            }
844            AudioDevicePort previousSource = mAudioSource;
845            mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress());
846            return mAudioSource == null ? (previousSource != null)
847                    : !mAudioSource.equals(previousSource);
848        }
849
850        private boolean updateAudioSinkLocked() {
851            if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) {
852                return false;
853            }
854            AudioDevicePort previousSink = mAudioSink;
855            if (mOverrideAudioType == AudioManager.DEVICE_NONE) {
856                mAudioSink = findAudioSinkFromAudioPolicy();
857            } else {
858                AudioDevicePort audioSink =
859                        findAudioDevicePort(mOverrideAudioType, mOverrideAudioAddress);
860                if (audioSink != null) {
861                    mAudioSink = audioSink;
862                }
863            }
864            return mAudioSink == null ? (previousSink != null) : !mAudioSink.equals(previousSink);
865        }
866
867        private void handleAudioSinkUpdated() {
868            synchronized (mImplLock) {
869                updateAudioConfigLocked();
870            }
871        }
872
873        @Override
874        public void overrideAudioSink(int audioType, String audioAddress, int samplingRate,
875                int channelMask, int format) {
876            synchronized (mImplLock) {
877                mOverrideAudioType = audioType;
878                mOverrideAudioAddress = audioAddress;
879
880                mDesiredSamplingRate = samplingRate;
881                mDesiredChannelMask = channelMask;
882                mDesiredFormat = format;
883
884                updateAudioConfigLocked();
885            }
886        }
887    }
888
889    interface Listener {
890        public void onStateChanged(String inputId, int state);
891        public void onHardwareDeviceAdded(TvInputHardwareInfo info);
892        public void onHardwareDeviceRemoved(TvInputHardwareInfo info);
893        public void onHdmiDeviceAdded(HdmiDeviceInfo device);
894        public void onHdmiDeviceRemoved(HdmiDeviceInfo device);
895        public void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo device);
896    }
897
898    private class ListenerHandler extends Handler {
899        private static final int STATE_CHANGED = 1;
900        private static final int HARDWARE_DEVICE_ADDED = 2;
901        private static final int HARDWARE_DEVICE_REMOVED = 3;
902        private static final int HDMI_DEVICE_ADDED = 4;
903        private static final int HDMI_DEVICE_REMOVED = 5;
904        private static final int HDMI_DEVICE_UPDATED = 6;
905
906        @Override
907        public final void handleMessage(Message msg) {
908            switch (msg.what) {
909                case STATE_CHANGED: {
910                    String inputId = (String) msg.obj;
911                    int state = msg.arg1;
912                    mListener.onStateChanged(inputId, state);
913                    break;
914                }
915                case HARDWARE_DEVICE_ADDED: {
916                    TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
917                    mListener.onHardwareDeviceAdded(info);
918                    break;
919                }
920                case HARDWARE_DEVICE_REMOVED: {
921                    TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
922                    mListener.onHardwareDeviceRemoved(info);
923                    break;
924                }
925                case HDMI_DEVICE_ADDED: {
926                    HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
927                    mListener.onHdmiDeviceAdded(info);
928                    break;
929                }
930                case HDMI_DEVICE_REMOVED: {
931                    HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
932                    mListener.onHdmiDeviceRemoved(info);
933                    break;
934                }
935                case HDMI_DEVICE_UPDATED: {
936                    HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
937                    String inputId = null;
938                    synchronized (mLock) {
939                        inputId = mHdmiInputIdMap.get(info.getId());
940                    }
941                    if (inputId != null) {
942                        mListener.onHdmiDeviceUpdated(inputId, info);
943                    } else {
944                        Slog.w(TAG, "Could not resolve input ID matching the device info; "
945                                + "ignoring.");
946                    }
947                    break;
948                }
949                default: {
950                    Slog.w(TAG, "Unhandled message: " + msg);
951                    break;
952                }
953            }
954        }
955    }
956
957    // Listener implementations for HdmiControlService
958
959    private final class HdmiHotplugEventListener extends IHdmiHotplugEventListener.Stub {
960        @Override
961        public void onReceived(HdmiHotplugEvent event) {
962            synchronized (mLock) {
963                mHdmiStateMap.put(event.getPort(), event.isConnected());
964                TvInputHardwareInfo hardwareInfo =
965                        findHardwareInfoForHdmiPortLocked(event.getPort());
966                if (hardwareInfo == null) {
967                    return;
968                }
969                String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId());
970                if (inputId == null) {
971                    return;
972                }
973                mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
974                        convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget();
975            }
976        }
977    }
978
979    private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub {
980        @Override
981        public void onStatusChanged(HdmiDeviceInfo deviceInfo, int status) {
982            synchronized (mLock) {
983                int messageType = 0;
984                Object obj = null;
985                switch (status) {
986                    case HdmiControlManager.DEVICE_EVENT_ADD_DEVICE: {
987                        if (findHdmiDeviceInfo(deviceInfo.getId()) == null) {
988                            mHdmiDeviceList.add(deviceInfo);
989                        } else {
990                            Slog.w(TAG, "The list already contains " + deviceInfo + "; ignoring.");
991                            return;
992                        }
993                        messageType = ListenerHandler.HDMI_DEVICE_ADDED;
994                        obj = deviceInfo;
995                        break;
996                    }
997                    case HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE: {
998                        HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId());
999                        if (!mHdmiDeviceList.remove(originalDeviceInfo)) {
1000                            Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring.");
1001                            return;
1002                        }
1003                        messageType = ListenerHandler.HDMI_DEVICE_REMOVED;
1004                        obj = deviceInfo;
1005                        break;
1006                    }
1007                    case HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE: {
1008                        HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId());
1009                        if (!mHdmiDeviceList.remove(originalDeviceInfo)) {
1010                            Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring.");
1011                            return;
1012                        }
1013                        mHdmiDeviceList.add(deviceInfo);
1014                        messageType = ListenerHandler.HDMI_DEVICE_UPDATED;
1015                        obj = deviceInfo;
1016                        break;
1017                    }
1018                }
1019
1020                Message msg = mHandler.obtainMessage(messageType, 0, 0, obj);
1021                if (findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId()) != null) {
1022                    msg.sendToTarget();
1023                } else {
1024                    mPendingHdmiDeviceEvents.add(msg);
1025                }
1026            }
1027        }
1028
1029        private HdmiDeviceInfo findHdmiDeviceInfo(int id) {
1030            for (HdmiDeviceInfo info : mHdmiDeviceList) {
1031                if (info.getId() == id) {
1032                    return info;
1033                }
1034            }
1035            return null;
1036        }
1037    }
1038
1039    private final class HdmiSystemAudioModeChangeListener extends
1040        IHdmiSystemAudioModeChangeListener.Stub {
1041        @Override
1042        public void onStatusChanged(boolean enabled) throws RemoteException {
1043            synchronized (mLock) {
1044                for (int i = 0; i < mConnections.size(); ++i) {
1045                    TvInputHardwareImpl impl = mConnections.valueAt(i).getHardwareImplLocked();
1046                    if (impl != null) {
1047                        impl.handleAudioSinkUpdated();
1048                    }
1049                }
1050            }
1051        }
1052    }
1053}
1054