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