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.BroadcastReceiver;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.hardware.hdmi.HdmiControlManager;
27import android.hardware.hdmi.HdmiDeviceInfo;
28import android.hardware.hdmi.HdmiHotplugEvent;
29import android.hardware.hdmi.IHdmiControlService;
30import android.hardware.hdmi.IHdmiDeviceEventListener;
31import android.hardware.hdmi.IHdmiHotplugEventListener;
32import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener;
33import android.media.AudioDevicePort;
34import android.media.AudioFormat;
35import android.media.AudioGain;
36import android.media.AudioGainConfig;
37import android.media.AudioManager;
38import android.media.AudioPatch;
39import android.media.AudioPort;
40import android.media.AudioPortConfig;
41import android.media.tv.ITvInputHardware;
42import android.media.tv.ITvInputHardwareCallback;
43import android.media.tv.TvInputHardwareInfo;
44import android.media.tv.TvInputInfo;
45import android.media.tv.TvStreamConfig;
46import android.os.Handler;
47import android.os.IBinder;
48import android.os.Message;
49import android.os.RemoteException;
50import android.os.ServiceManager;
51import android.util.ArrayMap;
52import android.util.Slog;
53import android.util.SparseArray;
54import android.util.SparseBooleanArray;
55import android.view.KeyEvent;
56import android.view.Surface;
57
58import com.android.server.SystemService;
59
60import java.util.ArrayList;
61import java.util.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 final IHdmiHotplugEventListener mHdmiHotplugEventListener =
92            new HdmiHotplugEventListener();
93    private final IHdmiDeviceEventListener mHdmiDeviceEventListener = new HdmiDeviceEventListener();
94    private final IHdmiSystemAudioModeChangeListener mHdmiSystemAudioModeChangeListener =
95            new HdmiSystemAudioModeChangeListener();
96    private final BroadcastReceiver mVolumeReceiver = new BroadcastReceiver() {
97        @Override
98        public void onReceive(Context context, Intent intent) {
99            handleVolumeChange(context, intent);
100        }
101    };
102    private int mCurrentIndex = 0;
103    private int mCurrentMaxIndex = 0;
104
105    // TODO: Should handle STANDBY case.
106    private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray();
107    private final List<Message> mPendingHdmiDeviceEvents = new LinkedList<>();
108
109    // Calls to mListener should happen here.
110    private final Handler mHandler = new ListenerHandler();
111
112    private final Object mLock = new Object();
113
114    public TvInputHardwareManager(Context context, Listener listener) {
115        mContext = context;
116        mListener = listener;
117        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
118        mHal.init();
119    }
120
121    public void onBootPhase(int phase) {
122        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
123            IHdmiControlService hdmiControlService = IHdmiControlService.Stub.asInterface(
124                    ServiceManager.getService(Context.HDMI_CONTROL_SERVICE));
125            if (hdmiControlService != null) {
126                try {
127                    hdmiControlService.addHotplugEventListener(mHdmiHotplugEventListener);
128                    hdmiControlService.addDeviceEventListener(mHdmiDeviceEventListener);
129                    hdmiControlService.addSystemAudioModeChangeListener(
130                            mHdmiSystemAudioModeChangeListener);
131                    mHdmiDeviceList.addAll(hdmiControlService.getInputDevices());
132                } catch (RemoteException e) {
133                    Slog.w(TAG, "Error registering listeners to HdmiControlService:", e);
134                }
135            } else {
136                Slog.w(TAG, "HdmiControlService is not available");
137            }
138            final IntentFilter filter = new IntentFilter();
139            filter.addAction(AudioManager.VOLUME_CHANGED_ACTION);
140            filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION);
141            mContext.registerReceiver(mVolumeReceiver, filter);
142            updateVolume();
143        }
144    }
145
146    @Override
147    public void onDeviceAvailable(TvInputHardwareInfo info, TvStreamConfig[] configs) {
148        synchronized (mLock) {
149            Connection connection = new Connection(info);
150            connection.updateConfigsLocked(configs);
151            mConnections.put(info.getDeviceId(), connection);
152            buildHardwareListLocked();
153            mHandler.obtainMessage(
154                    ListenerHandler.HARDWARE_DEVICE_ADDED, 0, 0, info).sendToTarget();
155            if (info.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
156                processPendingHdmiDeviceEventsLocked();
157            }
158        }
159    }
160
161    private void buildHardwareListLocked() {
162        mHardwareList.clear();
163        for (int i = 0; i < mConnections.size(); ++i) {
164            mHardwareList.add(mConnections.valueAt(i).getHardwareInfoLocked());
165        }
166    }
167
168    @Override
169    public void onDeviceUnavailable(int deviceId) {
170        synchronized (mLock) {
171            Connection connection = mConnections.get(deviceId);
172            if (connection == null) {
173                Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId);
174                return;
175            }
176            connection.resetLocked(null, null, null, null, null);
177            mConnections.remove(deviceId);
178            buildHardwareListLocked();
179            TvInputHardwareInfo info = connection.getHardwareInfoLocked();
180            if (info.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
181                // Remove HDMI devices linked with this hardware.
182                for (Iterator<HdmiDeviceInfo> it = mHdmiDeviceList.iterator(); it.hasNext();) {
183                    HdmiDeviceInfo deviceInfo = it.next();
184                    if (deviceInfo.getPortId() == info.getHdmiPortId()) {
185                        mHandler.obtainMessage(ListenerHandler.HDMI_DEVICE_REMOVED, 0, 0,
186                                deviceInfo).sendToTarget();
187                        it.remove();
188                    }
189                }
190            }
191            mHandler.obtainMessage(
192                    ListenerHandler.HARDWARE_DEVICE_REMOVED, 0, 0, info).sendToTarget();
193        }
194    }
195
196    @Override
197    public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs) {
198        synchronized (mLock) {
199            Connection connection = mConnections.get(deviceId);
200            if (connection == null) {
201                Slog.e(TAG, "StreamConfigurationChanged: Cannot find a connection with "
202                        + deviceId);
203                return;
204            }
205            connection.updateConfigsLocked(configs);
206            String inputId = mHardwareInputIdMap.get(deviceId);
207            if (inputId != null) {
208                mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
209                        convertConnectedToState(configs.length > 0), 0, inputId).sendToTarget();
210            }
211            ITvInputHardwareCallback callback = connection.getCallbackLocked();
212            if (callback != null) {
213                try {
214                    callback.onStreamConfigChanged(configs);
215                } catch (RemoteException e) {
216                    Slog.e(TAG, "error in onStreamConfigurationChanged", e);
217                }
218            }
219        }
220    }
221
222    @Override
223    public void onFirstFrameCaptured(int deviceId, int streamId) {
224        synchronized (mLock) {
225            Connection connection = mConnections.get(deviceId);
226            if (connection == null) {
227                Slog.e(TAG, "FirstFrameCaptured: Cannot find a connection with "
228                        + deviceId);
229                return;
230            }
231            Runnable runnable = connection.getOnFirstFrameCapturedLocked();
232            if (runnable != null) {
233                runnable.run();
234                connection.setOnFirstFrameCapturedLocked(null);
235            }
236        }
237    }
238
239    public List<TvInputHardwareInfo> getHardwareList() {
240        synchronized (mLock) {
241            return Collections.unmodifiableList(mHardwareList);
242        }
243    }
244
245    public List<HdmiDeviceInfo> getHdmiDeviceList() {
246        synchronized (mLock) {
247            return Collections.unmodifiableList(mHdmiDeviceList);
248        }
249    }
250
251    private boolean checkUidChangedLocked(
252            Connection connection, int callingUid, int resolvedUserId) {
253        Integer connectionCallingUid = connection.getCallingUidLocked();
254        Integer connectionResolvedUserId = connection.getResolvedUserIdLocked();
255        return connectionCallingUid == null || connectionResolvedUserId == null
256                || connectionCallingUid != callingUid || connectionResolvedUserId != resolvedUserId;
257    }
258
259    private int convertConnectedToState(boolean connected) {
260        if (connected) {
261            return INPUT_STATE_CONNECTED;
262        } else {
263            return INPUT_STATE_DISCONNECTED;
264        }
265    }
266
267    public void addHardwareTvInput(int deviceId, TvInputInfo info) {
268        synchronized (mLock) {
269            String oldInputId = mHardwareInputIdMap.get(deviceId);
270            if (oldInputId != null) {
271                Slog.w(TAG, "Trying to override previous registration: old = "
272                        + mInputMap.get(oldInputId) + ":" + deviceId + ", new = "
273                        + info + ":" + deviceId);
274            }
275            mHardwareInputIdMap.put(deviceId, info.getId());
276            mInputMap.put(info.getId(), info);
277
278            // Process pending state changes
279
280            // For logical HDMI devices, they have information from HDMI CEC signals.
281            for (int i = 0; i < mHdmiStateMap.size(); ++i) {
282                TvInputHardwareInfo hardwareInfo =
283                        findHardwareInfoForHdmiPortLocked(mHdmiStateMap.keyAt(i));
284                if (hardwareInfo == null) {
285                    continue;
286                }
287                String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId());
288                if (inputId != null && inputId.equals(info.getId())) {
289                    mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
290                            convertConnectedToState(mHdmiStateMap.valueAt(i)), 0,
291                            inputId).sendToTarget();
292                    return;
293                }
294            }
295            // For the rest of the devices, we can tell by the number of available streams.
296            Connection connection = mConnections.get(deviceId);
297            if (connection != null) {
298                mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
299                        convertConnectedToState(connection.getConfigsLocked().length > 0), 0,
300                        info.getId()).sendToTarget();
301            }
302        }
303    }
304
305    private static <T> int indexOfEqualValue(SparseArray<T> map, T value) {
306        for (int i = 0; i < map.size(); ++i) {
307            if (map.valueAt(i).equals(value)) {
308                return i;
309            }
310        }
311        return -1;
312    }
313
314    private static boolean intArrayContains(int[] array, int value) {
315        for (int element : array) {
316            if (element == value) return true;
317        }
318        return false;
319    }
320
321    public void addHdmiTvInput(int id, TvInputInfo info) {
322        if (info.getType() != TvInputInfo.TYPE_HDMI) {
323            throw new IllegalArgumentException("info (" + info + ") has non-HDMI type.");
324        }
325        synchronized (mLock) {
326            String parentId = info.getParentId();
327            int parentIndex = indexOfEqualValue(mHardwareInputIdMap, parentId);
328            if (parentIndex < 0) {
329                throw new IllegalArgumentException("info (" + info + ") has invalid parentId.");
330            }
331            String oldInputId = mHdmiInputIdMap.get(id);
332            if (oldInputId != null) {
333                Slog.w(TAG, "Trying to override previous registration: old = "
334                        + mInputMap.get(oldInputId) + ":" + id + ", new = "
335                        + info + ":" + id);
336            }
337            mHdmiInputIdMap.put(id, info.getId());
338            mInputMap.put(info.getId(), info);
339        }
340    }
341
342    public void removeTvInput(String inputId) {
343        synchronized (mLock) {
344            mInputMap.remove(inputId);
345            int hardwareIndex = indexOfEqualValue(mHardwareInputIdMap, inputId);
346            if (hardwareIndex >= 0) {
347                mHardwareInputIdMap.removeAt(hardwareIndex);
348            }
349            int deviceIndex = indexOfEqualValue(mHdmiInputIdMap, inputId);
350            if (deviceIndex >= 0) {
351                mHdmiInputIdMap.removeAt(deviceIndex);
352            }
353        }
354    }
355
356    /**
357     * Create a TvInputHardware object with a specific deviceId. One service at a time can access
358     * the object, and if more than one process attempts to create hardware with the same deviceId,
359     * the latest service will get the object and all the other hardware are released. The
360     * release is notified via ITvInputHardwareCallback.onReleased().
361     */
362    public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback,
363            TvInputInfo info, int callingUid, int resolvedUserId) {
364        if (callback == null) {
365            throw new NullPointerException();
366        }
367        synchronized (mLock) {
368            Connection connection = mConnections.get(deviceId);
369            if (connection == null) {
370                Slog.e(TAG, "Invalid deviceId : " + deviceId);
371                return null;
372            }
373            if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
374                TvInputHardwareImpl hardware =
375                        new TvInputHardwareImpl(connection.getHardwareInfoLocked());
376                try {
377                    callback.asBinder().linkToDeath(connection, 0);
378                } catch (RemoteException e) {
379                    hardware.release();
380                    return null;
381                }
382                connection.resetLocked(hardware, callback, info, callingUid, resolvedUserId);
383            }
384            return connection.getHardwareLocked();
385        }
386    }
387
388    /**
389     * Release the specified hardware.
390     */
391    public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid,
392            int resolvedUserId) {
393        synchronized (mLock) {
394            Connection connection = mConnections.get(deviceId);
395            if (connection == null) {
396                Slog.e(TAG, "Invalid deviceId : " + deviceId);
397                return;
398            }
399            if (connection.getHardwareLocked() != hardware
400                    || checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
401                return;
402            }
403            connection.resetLocked(null, null, null, null, null);
404        }
405    }
406
407    private TvInputHardwareInfo findHardwareInfoForHdmiPortLocked(int port) {
408        for (TvInputHardwareInfo hardwareInfo : mHardwareList) {
409            if (hardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI
410                    && hardwareInfo.getHdmiPortId() == port) {
411                return hardwareInfo;
412            }
413        }
414        return null;
415    }
416
417    private int findDeviceIdForInputIdLocked(String inputId) {
418        for (int i = 0; i < mConnections.size(); ++i) {
419            Connection connection = mConnections.get(i);
420            if (connection.getInfoLocked().getId().equals(inputId)) {
421                return i;
422            }
423        }
424        return -1;
425    }
426
427    /**
428     * Get the list of TvStreamConfig which is buffered mode.
429     */
430    public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int callingUid,
431            int resolvedUserId) {
432        List<TvStreamConfig> configsList = new ArrayList<>();
433        synchronized (mLock) {
434            int deviceId = findDeviceIdForInputIdLocked(inputId);
435            if (deviceId < 0) {
436                Slog.e(TAG, "Invalid inputId : " + inputId);
437                return configsList;
438            }
439            Connection connection = mConnections.get(deviceId);
440            for (TvStreamConfig config : connection.getConfigsLocked()) {
441                if (config.getType() == TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) {
442                    configsList.add(config);
443                }
444            }
445        }
446        return configsList;
447    }
448
449    /**
450     * Take a snapshot of the given TV input into the provided Surface.
451     */
452    public boolean captureFrame(String inputId, Surface surface, final TvStreamConfig config,
453            int callingUid, int resolvedUserId) {
454        synchronized (mLock) {
455            int deviceId = findDeviceIdForInputIdLocked(inputId);
456            if (deviceId < 0) {
457                Slog.e(TAG, "Invalid inputId : " + inputId);
458                return false;
459            }
460            Connection connection = mConnections.get(deviceId);
461            final TvInputHardwareImpl hardwareImpl = connection.getHardwareImplLocked();
462            if (hardwareImpl != null) {
463                // Stop previous capture.
464                Runnable runnable = connection.getOnFirstFrameCapturedLocked();
465                if (runnable != null) {
466                    runnable.run();
467                    connection.setOnFirstFrameCapturedLocked(null);
468                }
469
470                boolean result = hardwareImpl.startCapture(surface, config);
471                if (result) {
472                    connection.setOnFirstFrameCapturedLocked(new Runnable() {
473                        @Override
474                        public void run() {
475                            hardwareImpl.stopCapture(config);
476                        }
477                    });
478                }
479                return result;
480            }
481        }
482        return false;
483    }
484
485    private void processPendingHdmiDeviceEventsLocked() {
486        for (Iterator<Message> it = mPendingHdmiDeviceEvents.iterator(); it.hasNext(); ) {
487            Message msg = it.next();
488            HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj;
489            TvInputHardwareInfo hardwareInfo =
490                    findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId());
491            if (hardwareInfo != null) {
492                msg.sendToTarget();
493                it.remove();
494            }
495        }
496    }
497
498    private void updateVolume() {
499        mCurrentMaxIndex = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
500        mCurrentIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
501    }
502
503    private void handleVolumeChange(Context context, Intent intent) {
504        String action = intent.getAction();
505        switch (action) {
506            case AudioManager.VOLUME_CHANGED_ACTION: {
507                int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
508                if (streamType != AudioManager.STREAM_MUSIC) {
509                    return;
510                }
511                int index = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0);
512                if (index == mCurrentIndex) {
513                    return;
514                }
515                mCurrentIndex = index;
516                break;
517            }
518            case AudioManager.STREAM_MUTE_CHANGED_ACTION: {
519                int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1);
520                if (streamType != AudioManager.STREAM_MUSIC) {
521                    return;
522                }
523                // volume index will be updated at onMediaStreamVolumeChanged() through
524                // updateVolume().
525                break;
526            }
527            default:
528                Slog.w(TAG, "Unrecognized intent: " + intent);
529                return;
530        }
531        synchronized (mLock) {
532            for (int i = 0; i < mConnections.size(); ++i) {
533                TvInputHardwareImpl hardwareImpl = mConnections.valueAt(i).getHardwareImplLocked();
534                if (hardwareImpl != null) {
535                    hardwareImpl.onMediaStreamVolumeChanged();
536                }
537            }
538        }
539    }
540
541    private float getMediaStreamVolume() {
542        return (float) mCurrentIndex / (float) mCurrentMaxIndex;
543    }
544
545    private class Connection implements IBinder.DeathRecipient {
546        private final TvInputHardwareInfo mHardwareInfo;
547        private TvInputInfo mInfo;
548        private TvInputHardwareImpl mHardware = null;
549        private ITvInputHardwareCallback mCallback;
550        private TvStreamConfig[] mConfigs = null;
551        private Integer mCallingUid = null;
552        private Integer mResolvedUserId = null;
553        private Runnable mOnFirstFrameCaptured;
554
555        public Connection(TvInputHardwareInfo hardwareInfo) {
556            mHardwareInfo = hardwareInfo;
557        }
558
559        // *Locked methods assume TvInputHardwareManager.mLock is held.
560
561        public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback,
562                TvInputInfo info, Integer callingUid, Integer resolvedUserId) {
563            if (mHardware != null) {
564                try {
565                    mCallback.onReleased();
566                } catch (RemoteException e) {
567                    Slog.e(TAG, "error in Connection::resetLocked", e);
568                }
569                mHardware.release();
570            }
571            mHardware = hardware;
572            mCallback = callback;
573            mInfo = info;
574            mCallingUid = callingUid;
575            mResolvedUserId = resolvedUserId;
576            mOnFirstFrameCaptured = null;
577
578            if (mHardware != null && mCallback != null) {
579                try {
580                    mCallback.onStreamConfigChanged(getConfigsLocked());
581                } catch (RemoteException e) {
582                    Slog.e(TAG, "error in Connection::resetLocked", e);
583                }
584            }
585        }
586
587        public void updateConfigsLocked(TvStreamConfig[] configs) {
588            mConfigs = configs;
589        }
590
591        public TvInputHardwareInfo getHardwareInfoLocked() {
592            return mHardwareInfo;
593        }
594
595        public TvInputInfo getInfoLocked() {
596            return mInfo;
597        }
598
599        public ITvInputHardware getHardwareLocked() {
600            return mHardware;
601        }
602
603        public TvInputHardwareImpl getHardwareImplLocked() {
604            return mHardware;
605        }
606
607        public ITvInputHardwareCallback getCallbackLocked() {
608            return mCallback;
609        }
610
611        public TvStreamConfig[] getConfigsLocked() {
612            return mConfigs;
613        }
614
615        public Integer getCallingUidLocked() {
616            return mCallingUid;
617        }
618
619        public Integer getResolvedUserIdLocked() {
620            return mResolvedUserId;
621        }
622
623        public void setOnFirstFrameCapturedLocked(Runnable runnable) {
624            mOnFirstFrameCaptured = runnable;
625        }
626
627        public Runnable getOnFirstFrameCapturedLocked() {
628            return mOnFirstFrameCaptured;
629        }
630
631        @Override
632        public void binderDied() {
633            synchronized (mLock) {
634                resetLocked(null, null, null, null, null);
635            }
636        }
637    }
638
639    private class TvInputHardwareImpl extends ITvInputHardware.Stub {
640        private final TvInputHardwareInfo mInfo;
641        private boolean mReleased = false;
642        private final Object mImplLock = new Object();
643
644        private final AudioManager.OnAudioPortUpdateListener mAudioListener =
645                new AudioManager.OnAudioPortUpdateListener() {
646            @Override
647            public void onAudioPortListUpdate(AudioPort[] portList) {
648                synchronized (mImplLock) {
649                    updateAudioConfigLocked();
650                }
651            }
652
653            @Override
654            public void onAudioPatchListUpdate(AudioPatch[] patchList) {
655                // No-op
656            }
657
658            @Override
659            public void onServiceDied() {
660                synchronized (mImplLock) {
661                    mAudioSource = null;
662                    mAudioSink.clear();
663                    mAudioPatch = null;
664                }
665            }
666        };
667        private int mOverrideAudioType = AudioManager.DEVICE_NONE;
668        private String mOverrideAudioAddress = "";
669        private AudioDevicePort mAudioSource;
670        private List<AudioDevicePort> mAudioSink = new ArrayList<>();
671        private AudioPatch mAudioPatch = null;
672        // Set to an invalid value for a volume, so that current volume can be applied at the
673        // first call to updateAudioConfigLocked().
674        private float mCommittedVolume = -1f;
675        private float mSourceVolume = 0.0f;
676
677        private TvStreamConfig mActiveConfig = null;
678
679        private int mDesiredSamplingRate = 0;
680        private int mDesiredChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT;
681        private int mDesiredFormat = AudioFormat.ENCODING_DEFAULT;
682
683        public TvInputHardwareImpl(TvInputHardwareInfo info) {
684            mInfo = info;
685            mAudioManager.registerAudioPortUpdateListener(mAudioListener);
686            if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) {
687                mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress());
688                findAudioSinkFromAudioPolicy(mAudioSink);
689            }
690        }
691
692        private void findAudioSinkFromAudioPolicy(List<AudioDevicePort> sinks) {
693            sinks.clear();
694            ArrayList<AudioDevicePort> devicePorts = new ArrayList<>();
695            if (mAudioManager.listAudioDevicePorts(devicePorts) != AudioManager.SUCCESS) {
696                return;
697            }
698            int sinkDevice = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC);
699            for (AudioDevicePort port : devicePorts) {
700                if ((port.type() & sinkDevice) != 0) {
701                    sinks.add(port);
702                }
703            }
704        }
705
706        private AudioDevicePort findAudioDevicePort(int type, String address) {
707            if (type == AudioManager.DEVICE_NONE) {
708                return null;
709            }
710            ArrayList<AudioDevicePort> devicePorts = new ArrayList<>();
711            if (mAudioManager.listAudioDevicePorts(devicePorts) != AudioManager.SUCCESS) {
712                return null;
713            }
714            for (AudioDevicePort port : devicePorts) {
715                if (port.type() == type && port.address().equals(address)) {
716                    return port;
717                }
718            }
719            return null;
720        }
721
722        public void release() {
723            synchronized (mImplLock) {
724                mAudioManager.unregisterAudioPortUpdateListener(mAudioListener);
725                if (mAudioPatch != null) {
726                    mAudioManager.releaseAudioPatch(mAudioPatch);
727                    mAudioPatch = null;
728                }
729                mReleased = true;
730            }
731        }
732
733        // A TvInputHardwareImpl object holds only one active session. Therefore, if a client
734        // attempts to call setSurface with different TvStreamConfig objects, the last call will
735        // prevail.
736        @Override
737        public boolean setSurface(Surface surface, TvStreamConfig config)
738                throws RemoteException {
739            synchronized (mImplLock) {
740                if (mReleased) {
741                    throw new IllegalStateException("Device already released.");
742                }
743
744                int result = TvInputHal.SUCCESS;
745                if (surface == null) {
746                    // The value of config is ignored when surface == null.
747                    if (mActiveConfig != null) {
748                        result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
749                        mActiveConfig = null;
750                    } else {
751                        // We already have no active stream.
752                        return true;
753                    }
754                } else {
755                    // It's impossible to set a non-null surface with a null config.
756                    if (config == null) {
757                        return false;
758                    }
759                    // Remove stream only if we have an existing active configuration.
760                    if (mActiveConfig != null && !config.equals(mActiveConfig)) {
761                        result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
762                        if (result != TvInputHal.SUCCESS) {
763                            mActiveConfig = null;
764                        }
765                    }
766                    // Proceed only if all previous operations succeeded.
767                    if (result == TvInputHal.SUCCESS) {
768                        result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config);
769                        if (result == TvInputHal.SUCCESS) {
770                            mActiveConfig = config;
771                        }
772                    }
773                }
774                updateAudioConfigLocked();
775                return result == TvInputHal.SUCCESS;
776            }
777        }
778
779        /**
780         * Update audio configuration (source, sink, patch) all up to current state.
781         */
782        private void updateAudioConfigLocked() {
783            boolean sinkUpdated = updateAudioSinkLocked();
784            boolean sourceUpdated = updateAudioSourceLocked();
785            // We can't do updated = updateAudioSinkLocked() || updateAudioSourceLocked() here
786            // because Java won't evaluate the latter if the former is true.
787
788            if (mAudioSource == null || mAudioSink.isEmpty() || mActiveConfig == null) {
789                if (mAudioPatch != null) {
790                    mAudioManager.releaseAudioPatch(mAudioPatch);
791                    mAudioPatch = null;
792                }
793                return;
794            }
795
796            updateVolume();
797            float volume = mSourceVolume * getMediaStreamVolume();
798            AudioGainConfig sourceGainConfig = null;
799            if (mAudioSource.gains().length > 0 && volume != mCommittedVolume) {
800                AudioGain sourceGain = null;
801                for (AudioGain gain : mAudioSource.gains()) {
802                    if ((gain.mode() & AudioGain.MODE_JOINT) != 0) {
803                        sourceGain = gain;
804                        break;
805                    }
806                }
807                // NOTE: we only change the source gain in MODE_JOINT here.
808                if (sourceGain != null) {
809                    int steps = (sourceGain.maxValue() - sourceGain.minValue())
810                            / sourceGain.stepValue();
811                    int gainValue = sourceGain.minValue();
812                    if (volume < 1.0f) {
813                        gainValue += sourceGain.stepValue() * (int) (volume * steps + 0.5);
814                    } else {
815                        gainValue = sourceGain.maxValue();
816                    }
817                    // size of gain values is 1 in MODE_JOINT
818                    int[] gainValues = new int[] { gainValue };
819                    sourceGainConfig = sourceGain.buildConfig(AudioGain.MODE_JOINT,
820                            sourceGain.channelMask(), gainValues, 0);
821                } else {
822                    Slog.w(TAG, "No audio source gain with MODE_JOINT support exists.");
823                }
824            }
825
826            AudioPortConfig sourceConfig = mAudioSource.activeConfig();
827            List<AudioPortConfig> sinkConfigs = new ArrayList<>();
828            AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch };
829            boolean shouldRecreateAudioPatch = sourceUpdated || sinkUpdated;
830
831            for (AudioDevicePort audioSink : mAudioSink) {
832                AudioPortConfig sinkConfig = audioSink.activeConfig();
833                int sinkSamplingRate = mDesiredSamplingRate;
834                int sinkChannelMask = mDesiredChannelMask;
835                int sinkFormat = mDesiredFormat;
836                // If sinkConfig != null and values are set to default,
837                // fill in the sinkConfig values.
838                if (sinkConfig != null) {
839                    if (sinkSamplingRate == 0) {
840                        sinkSamplingRate = sinkConfig.samplingRate();
841                    }
842                    if (sinkChannelMask == AudioFormat.CHANNEL_OUT_DEFAULT) {
843                        sinkChannelMask = sinkConfig.channelMask();
844                    }
845                    if (sinkFormat == AudioFormat.ENCODING_DEFAULT) {
846                        sinkChannelMask = sinkConfig.format();
847                    }
848                }
849
850                if (sinkConfig == null
851                        || sinkConfig.samplingRate() != sinkSamplingRate
852                        || sinkConfig.channelMask() != sinkChannelMask
853                        || sinkConfig.format() != sinkFormat) {
854                    // Check for compatibility and reset to default if necessary.
855                    if (!intArrayContains(audioSink.samplingRates(), sinkSamplingRate)
856                            && audioSink.samplingRates().length > 0) {
857                        sinkSamplingRate = audioSink.samplingRates()[0];
858                    }
859                    if (!intArrayContains(audioSink.channelMasks(), sinkChannelMask)) {
860                        sinkChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT;
861                    }
862                    if (!intArrayContains(audioSink.formats(), sinkFormat)) {
863                        sinkFormat = AudioFormat.ENCODING_DEFAULT;
864                    }
865                    sinkConfig = audioSink.buildConfig(sinkSamplingRate, sinkChannelMask,
866                            sinkFormat, null);
867                    shouldRecreateAudioPatch = true;
868                }
869                sinkConfigs.add(sinkConfig);
870            }
871            // sinkConfigs.size() == mAudioSink.size(), and mAudioSink is guaranteed to be
872            // non-empty at the beginning of this method.
873            AudioPortConfig sinkConfig = sinkConfigs.get(0);
874            if (sourceConfig == null || sourceGainConfig != null) {
875                int sourceSamplingRate = 0;
876                if (intArrayContains(mAudioSource.samplingRates(), sinkConfig.samplingRate())) {
877                    sourceSamplingRate = sinkConfig.samplingRate();
878                } else if (mAudioSource.samplingRates().length > 0) {
879                    // Use any sampling rate and hope audio patch can handle resampling...
880                    sourceSamplingRate = mAudioSource.samplingRates()[0];
881                }
882                int sourceChannelMask = AudioFormat.CHANNEL_IN_DEFAULT;
883                for (int inChannelMask : mAudioSource.channelMasks()) {
884                    if (AudioFormat.channelCountFromOutChannelMask(sinkConfig.channelMask())
885                            == AudioFormat.channelCountFromInChannelMask(inChannelMask)) {
886                        sourceChannelMask = inChannelMask;
887                        break;
888                    }
889                }
890                int sourceFormat = AudioFormat.ENCODING_DEFAULT;
891                if (intArrayContains(mAudioSource.formats(), sinkConfig.format())) {
892                    sourceFormat = sinkConfig.format();
893                }
894                sourceConfig = mAudioSource.buildConfig(sourceSamplingRate, sourceChannelMask,
895                        sourceFormat, sourceGainConfig);
896                shouldRecreateAudioPatch = true;
897            }
898            if (shouldRecreateAudioPatch) {
899                mCommittedVolume = volume;
900                mAudioManager.createAudioPatch(
901                        audioPatchArray,
902                        new AudioPortConfig[] { sourceConfig },
903                        sinkConfigs.toArray(new AudioPortConfig[sinkConfigs.size()]));
904                mAudioPatch = audioPatchArray[0];
905                if (sourceGainConfig != null) {
906                    mAudioManager.setAudioPortGain(mAudioSource, sourceGainConfig);
907                }
908            }
909        }
910
911        @Override
912        public void setStreamVolume(float volume) throws RemoteException {
913            synchronized (mImplLock) {
914                if (mReleased) {
915                    throw new IllegalStateException("Device already released.");
916                }
917                mSourceVolume = volume;
918                updateAudioConfigLocked();
919            }
920        }
921
922        @Override
923        public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException {
924            synchronized (mImplLock) {
925                if (mReleased) {
926                    throw new IllegalStateException("Device already released.");
927                }
928            }
929            if (mInfo.getType() != TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
930                return false;
931            }
932            // TODO(hdmi): mHdmiClient.sendKeyEvent(event);
933            return false;
934        }
935
936        private boolean startCapture(Surface surface, TvStreamConfig config) {
937            synchronized (mImplLock) {
938                if (mReleased) {
939                    return false;
940                }
941                if (surface == null || config == null) {
942                    return false;
943                }
944                if (config.getType() != TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) {
945                    return false;
946                }
947
948                int result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config);
949                return result == TvInputHal.SUCCESS;
950            }
951        }
952
953        private boolean stopCapture(TvStreamConfig config) {
954            synchronized (mImplLock) {
955                if (mReleased) {
956                    return false;
957                }
958                if (config == null) {
959                    return false;
960                }
961
962                int result = mHal.removeStream(mInfo.getDeviceId(), config);
963                return result == TvInputHal.SUCCESS;
964            }
965        }
966
967        private boolean updateAudioSourceLocked() {
968            if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) {
969                return false;
970            }
971            AudioDevicePort previousSource = mAudioSource;
972            mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress());
973            return mAudioSource == null ? (previousSource != null)
974                    : !mAudioSource.equals(previousSource);
975        }
976
977        private boolean updateAudioSinkLocked() {
978            if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) {
979                return false;
980            }
981            List<AudioDevicePort> previousSink = mAudioSink;
982            mAudioSink = new ArrayList<>();
983            if (mOverrideAudioType == AudioManager.DEVICE_NONE) {
984                findAudioSinkFromAudioPolicy(mAudioSink);
985            } else {
986                AudioDevicePort audioSink =
987                        findAudioDevicePort(mOverrideAudioType, mOverrideAudioAddress);
988                if (audioSink != null) {
989                    mAudioSink.add(audioSink);
990                }
991            }
992
993            // Returns true if mAudioSink and previousSink differs.
994            if (mAudioSink.size() != previousSink.size()) {
995                return true;
996            }
997            previousSink.removeAll(mAudioSink);
998            return !previousSink.isEmpty();
999        }
1000
1001        private void handleAudioSinkUpdated() {
1002            synchronized (mImplLock) {
1003                updateAudioConfigLocked();
1004            }
1005        }
1006
1007        @Override
1008        public void overrideAudioSink(int audioType, String audioAddress, int samplingRate,
1009                int channelMask, int format) {
1010            synchronized (mImplLock) {
1011                mOverrideAudioType = audioType;
1012                mOverrideAudioAddress = audioAddress;
1013
1014                mDesiredSamplingRate = samplingRate;
1015                mDesiredChannelMask = channelMask;
1016                mDesiredFormat = format;
1017
1018                updateAudioConfigLocked();
1019            }
1020        }
1021
1022        public void onMediaStreamVolumeChanged() {
1023            synchronized (mImplLock) {
1024                updateAudioConfigLocked();
1025            }
1026        }
1027    }
1028
1029    interface Listener {
1030        void onStateChanged(String inputId, int state);
1031        void onHardwareDeviceAdded(TvInputHardwareInfo info);
1032        void onHardwareDeviceRemoved(TvInputHardwareInfo info);
1033        void onHdmiDeviceAdded(HdmiDeviceInfo device);
1034        void onHdmiDeviceRemoved(HdmiDeviceInfo device);
1035        void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo device);
1036    }
1037
1038    private class ListenerHandler extends Handler {
1039        private static final int STATE_CHANGED = 1;
1040        private static final int HARDWARE_DEVICE_ADDED = 2;
1041        private static final int HARDWARE_DEVICE_REMOVED = 3;
1042        private static final int HDMI_DEVICE_ADDED = 4;
1043        private static final int HDMI_DEVICE_REMOVED = 5;
1044        private static final int HDMI_DEVICE_UPDATED = 6;
1045
1046        @Override
1047        public final void handleMessage(Message msg) {
1048            switch (msg.what) {
1049                case STATE_CHANGED: {
1050                    String inputId = (String) msg.obj;
1051                    int state = msg.arg1;
1052                    mListener.onStateChanged(inputId, state);
1053                    break;
1054                }
1055                case HARDWARE_DEVICE_ADDED: {
1056                    TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
1057                    mListener.onHardwareDeviceAdded(info);
1058                    break;
1059                }
1060                case HARDWARE_DEVICE_REMOVED: {
1061                    TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj;
1062                    mListener.onHardwareDeviceRemoved(info);
1063                    break;
1064                }
1065                case HDMI_DEVICE_ADDED: {
1066                    HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
1067                    mListener.onHdmiDeviceAdded(info);
1068                    break;
1069                }
1070                case HDMI_DEVICE_REMOVED: {
1071                    HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
1072                    mListener.onHdmiDeviceRemoved(info);
1073                    break;
1074                }
1075                case HDMI_DEVICE_UPDATED: {
1076                    HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj;
1077                    String inputId;
1078                    synchronized (mLock) {
1079                        inputId = mHdmiInputIdMap.get(info.getId());
1080                    }
1081                    if (inputId != null) {
1082                        mListener.onHdmiDeviceUpdated(inputId, info);
1083                    } else {
1084                        Slog.w(TAG, "Could not resolve input ID matching the device info; "
1085                                + "ignoring.");
1086                    }
1087                    break;
1088                }
1089                default: {
1090                    Slog.w(TAG, "Unhandled message: " + msg);
1091                    break;
1092                }
1093            }
1094        }
1095    }
1096
1097    // Listener implementations for HdmiControlService
1098
1099    private final class HdmiHotplugEventListener extends IHdmiHotplugEventListener.Stub {
1100        @Override
1101        public void onReceived(HdmiHotplugEvent event) {
1102            synchronized (mLock) {
1103                mHdmiStateMap.put(event.getPort(), event.isConnected());
1104                TvInputHardwareInfo hardwareInfo =
1105                        findHardwareInfoForHdmiPortLocked(event.getPort());
1106                if (hardwareInfo == null) {
1107                    return;
1108                }
1109                String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId());
1110                if (inputId == null) {
1111                    return;
1112                }
1113                mHandler.obtainMessage(ListenerHandler.STATE_CHANGED,
1114                        convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget();
1115            }
1116        }
1117    }
1118
1119    private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub {
1120        @Override
1121        public void onStatusChanged(HdmiDeviceInfo deviceInfo, int status) {
1122            if (!deviceInfo.isSourceType()) return;
1123            synchronized (mLock) {
1124                int messageType = 0;
1125                Object obj = null;
1126                switch (status) {
1127                    case HdmiControlManager.DEVICE_EVENT_ADD_DEVICE: {
1128                        if (findHdmiDeviceInfo(deviceInfo.getId()) == null) {
1129                            mHdmiDeviceList.add(deviceInfo);
1130                        } else {
1131                            Slog.w(TAG, "The list already contains " + deviceInfo + "; ignoring.");
1132                            return;
1133                        }
1134                        messageType = ListenerHandler.HDMI_DEVICE_ADDED;
1135                        obj = deviceInfo;
1136                        break;
1137                    }
1138                    case HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE: {
1139                        HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId());
1140                        if (!mHdmiDeviceList.remove(originalDeviceInfo)) {
1141                            Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring.");
1142                            return;
1143                        }
1144                        messageType = ListenerHandler.HDMI_DEVICE_REMOVED;
1145                        obj = deviceInfo;
1146                        break;
1147                    }
1148                    case HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE: {
1149                        HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId());
1150                        if (!mHdmiDeviceList.remove(originalDeviceInfo)) {
1151                            Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring.");
1152                            return;
1153                        }
1154                        mHdmiDeviceList.add(deviceInfo);
1155                        messageType = ListenerHandler.HDMI_DEVICE_UPDATED;
1156                        obj = deviceInfo;
1157                        break;
1158                    }
1159                }
1160
1161                Message msg = mHandler.obtainMessage(messageType, 0, 0, obj);
1162                if (findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId()) != null) {
1163                    msg.sendToTarget();
1164                } else {
1165                    mPendingHdmiDeviceEvents.add(msg);
1166                }
1167            }
1168        }
1169
1170        private HdmiDeviceInfo findHdmiDeviceInfo(int id) {
1171            for (HdmiDeviceInfo info : mHdmiDeviceList) {
1172                if (info.getId() == id) {
1173                    return info;
1174                }
1175            }
1176            return null;
1177        }
1178    }
1179
1180    private final class HdmiSystemAudioModeChangeListener extends
1181        IHdmiSystemAudioModeChangeListener.Stub {
1182        @Override
1183        public void onStatusChanged(boolean enabled) throws RemoteException {
1184            synchronized (mLock) {
1185                for (int i = 0; i < mConnections.size(); ++i) {
1186                    TvInputHardwareImpl impl = mConnections.valueAt(i).getHardwareImplLocked();
1187                    if (impl != null) {
1188                        impl.handleAudioSinkUpdated();
1189                    }
1190                }
1191            }
1192        }
1193    }
1194}
1195