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