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