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