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