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