TvInputHardwareManager.java revision 969167dc05a6485a32d160895871cff46fd81884
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.tv;
18
19import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
20import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
21
22import android.content.Context;
23import android.hardware.hdmi.HdmiControlManager;
24import android.hardware.hdmi.HdmiHotplugEvent;
25import android.media.AudioDevicePort;
26import android.media.AudioManager;
27import android.media.AudioPatch;
28import android.media.AudioPort;
29import android.media.AudioPortConfig;
30import android.media.tv.ITvInputHardware;
31import android.media.tv.ITvInputHardwareCallback;
32import android.media.tv.TvInputHardwareInfo;
33import android.media.tv.TvInputInfo;
34import android.media.tv.TvStreamConfig;
35import android.os.Handler;
36import android.os.HandlerThread;
37import android.os.IBinder;
38import android.os.Looper;
39import android.os.Message;
40import android.os.RemoteException;
41import android.util.Slog;
42import android.util.SparseArray;
43import android.util.SparseBooleanArray;
44import android.view.KeyEvent;
45import android.view.Surface;
46
47import com.android.server.SystemService;
48
49import java.util.ArrayList;
50import java.util.HashSet;
51import java.util.List;
52import java.util.Set;
53
54/**
55 * A helper class for TvInputManagerService to handle TV input hardware.
56 *
57 * This class does a basic connection management and forwarding calls to TvInputHal which eventually
58 * calls to tv_input HAL module.
59 *
60 * @hide
61 */
62class TvInputHardwareManager
63        implements TvInputHal.Callback, HdmiControlManager.HotplugEventListener {
64    private static final String TAG = TvInputHardwareManager.class.getSimpleName();
65    private final TvInputHal mHal = new TvInputHal(this);
66    private final SparseArray<Connection> mConnections = new SparseArray<Connection>();
67    private final List<TvInputHardwareInfo> mInfoList = new ArrayList<TvInputHardwareInfo>();
68    private final Context mContext;
69    private final TvInputManagerService.Client mClient;
70    private final Set<Integer> mActiveHdmiSources = new HashSet<Integer>();
71    private final AudioManager mAudioManager;
72    private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray();
73    // TODO: Should handle INACTIVE case.
74    private final SparseArray<TvInputInfo> mTvInputInfoMap = new SparseArray<TvInputInfo>();
75
76    // Calls to mClient should happen here.
77    private final HandlerThread mHandlerThread = new HandlerThread(TAG);
78    private final Handler mHandler;
79
80    private final Object mLock = new Object();
81
82    public TvInputHardwareManager(Context context, TvInputManagerService.Client client) {
83        mContext = context;
84        mClient = client;
85        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
86        mHal.init();
87
88        mHandlerThread.start();
89        mHandler = new ClientHandler(mHandlerThread.getLooper());
90    }
91
92    public void onBootPhase(int phase) {
93        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
94            HdmiControlManager hdmiControlManager =
95                    (HdmiControlManager) mContext.getSystemService(Context.HDMI_CONTROL_SERVICE);
96            hdmiControlManager.addHotplugEventListener(this);
97        }
98    }
99
100    @Override
101    public void onDeviceAvailable(
102            TvInputHardwareInfo info, TvStreamConfig[] configs) {
103        synchronized (mLock) {
104            Connection connection = new Connection(info);
105            connection.updateConfigsLocked(configs);
106            mConnections.put(info.getDeviceId(), connection);
107            buildInfoListLocked();
108            // TODO: notify if necessary
109        }
110    }
111
112    private void buildInfoListLocked() {
113        mInfoList.clear();
114        for (int i = 0; i < mConnections.size(); ++i) {
115            mInfoList.add(mConnections.valueAt(i).getHardwareInfoLocked());
116        }
117    }
118
119    @Override
120    public void onDeviceUnavailable(int deviceId) {
121        synchronized (mLock) {
122            Connection connection = mConnections.get(deviceId);
123            if (connection == null) {
124                Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId);
125                return;
126            }
127            connection.resetLocked(null, null, null, null, null);
128            mConnections.remove(deviceId);
129            buildInfoListLocked();
130            // TODO: notify if necessary
131        }
132    }
133
134    @Override
135    public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs) {
136        synchronized (mLock) {
137            Connection connection = mConnections.get(deviceId);
138            if (connection == null) {
139                Slog.e(TAG, "StreamConfigurationChanged: Cannot find a connection with "
140                        + deviceId);
141                return;
142            }
143            connection.updateConfigsLocked(configs);
144            try {
145                connection.getCallbackLocked().onStreamConfigChanged(configs);
146            } catch (RemoteException e) {
147                Slog.e(TAG, "onStreamConfigurationChanged: " + e);
148            }
149        }
150    }
151
152    public List<TvInputHardwareInfo> getHardwareList() {
153        synchronized (mLock) {
154            return mInfoList;
155        }
156    }
157
158    private boolean checkUidChangedLocked(
159            Connection connection, int callingUid, int resolvedUserId) {
160        Integer connectionCallingUid = connection.getCallingUidLocked();
161        Integer connectionResolvedUserId = connection.getResolvedUserIdLocked();
162        if (connectionCallingUid == null || connectionResolvedUserId == null) {
163            return true;
164        }
165        if (connectionCallingUid != callingUid || connectionResolvedUserId != resolvedUserId) {
166            return true;
167        }
168        return false;
169    }
170
171    private int convertConnectedToState(boolean connected) {
172        if (connected) {
173            return INPUT_STATE_CONNECTED;
174        } else {
175            return INPUT_STATE_DISCONNECTED;
176        }
177    }
178
179    public void registerTvInputInfo(TvInputInfo info, int deviceId) {
180        if (info.getType() == TvInputInfo.TYPE_VIRTUAL) {
181            throw new IllegalArgumentException("info (" + info + ") has virtual type.");
182        }
183        synchronized (mLock) {
184            if (mTvInputInfoMap.indexOfKey(deviceId) >= 0) {
185                Slog.w(TAG, "Trying to override previous registration: old = "
186                        + mTvInputInfoMap.get(deviceId) + ":" + deviceId + ", new = "
187                        + info + ":" + deviceId);
188            }
189            mTvInputInfoMap.put(deviceId, info);
190
191            for (int i = 0; i < mHdmiStateMap.size(); ++i) {
192                String inputId = findInputIdForHdmiPortLocked(mHdmiStateMap.keyAt(i));
193                if (inputId != null && inputId.equals(info.getId())) {
194                    mHandler.obtainMessage(ClientHandler.DO_SET_AVAILABLE,
195                            convertConnectedToState(mHdmiStateMap.valueAt(i)), 0,
196                            inputId).sendToTarget();
197                }
198            }
199        }
200    }
201
202    /**
203     * Create a TvInputHardware object with a specific deviceId. One service at a time can access
204     * the object, and if more than one process attempts to create hardware with the same deviceId,
205     * the latest service will get the object and all the other hardware are released. The
206     * release is notified via ITvInputHardwareCallback.onReleased().
207     */
208    public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback,
209            TvInputInfo info, int callingUid, int resolvedUserId) {
210        if (callback == null) {
211            throw new NullPointerException();
212        }
213        synchronized (mLock) {
214            Connection connection = mConnections.get(deviceId);
215            if (connection == null) {
216                Slog.e(TAG, "Invalid deviceId : " + deviceId);
217                return null;
218            }
219            if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
220                TvInputHardwareImpl hardware =
221                        new TvInputHardwareImpl(connection.getHardwareInfoLocked());
222                try {
223                    callback.asBinder().linkToDeath(connection, 0);
224                } catch (RemoteException e) {
225                    hardware.release();
226                    return null;
227                }
228                connection.resetLocked(hardware, callback, info, callingUid, resolvedUserId);
229            }
230            return connection.getHardwareLocked();
231        }
232    }
233
234    /**
235     * Release the specified hardware.
236     */
237    public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid,
238            int resolvedUserId) {
239        synchronized (mLock) {
240            Connection connection = mConnections.get(deviceId);
241            if (connection == null) {
242                Slog.e(TAG, "Invalid deviceId : " + deviceId);
243                return;
244            }
245            if (connection.getHardwareLocked() != hardware
246                    || checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
247                return;
248            }
249            connection.resetLocked(null, null, null, null, null);
250        }
251    }
252
253    private String findInputIdForHdmiPortLocked(int port) {
254        for (TvInputHardwareInfo hardwareInfo : mInfoList) {
255            if (hardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI
256                    && hardwareInfo.getHdmiPortId() == port) {
257                TvInputInfo info = mTvInputInfoMap.get(hardwareInfo.getDeviceId());
258                return (info == null) ? null : info.getId();
259            }
260        }
261        return null;
262    }
263
264    // HdmiControlManager.HotplugEventListener implementation.
265
266    @Override
267    public void onReceived(HdmiHotplugEvent event) {
268        String inputId = null;
269
270        synchronized (mLock) {
271            mHdmiStateMap.put(event.getPort(), event.isConnected());
272            inputId = findInputIdForHdmiPortLocked(event.getPort());
273            if (inputId == null) {
274                return;
275            }
276            mHandler.obtainMessage(ClientHandler.DO_SET_AVAILABLE,
277                    convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget();
278        }
279    }
280
281    private class Connection implements IBinder.DeathRecipient {
282        private final TvInputHardwareInfo mHardwareInfo;
283        private TvInputInfo mInfo;
284        private TvInputHardwareImpl mHardware = null;
285        private ITvInputHardwareCallback mCallback;
286        private TvStreamConfig[] mConfigs = null;
287        private Integer mCallingUid = null;
288        private Integer mResolvedUserId = null;
289
290        public Connection(TvInputHardwareInfo hardwareInfo) {
291            mHardwareInfo = hardwareInfo;
292        }
293
294        // *Locked methods assume TvInputHardwareManager.mLock is held.
295
296        public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback,
297                TvInputInfo info, Integer callingUid, Integer resolvedUserId) {
298            if (mHardware != null) {
299                try {
300                    mCallback.onReleased();
301                } catch (RemoteException e) {
302                    Slog.e(TAG, "Connection::resetHardware: " + e);
303                }
304                mHardware.release();
305            }
306            mHardware = hardware;
307            mCallback = callback;
308            mInfo = info;
309            mCallingUid = callingUid;
310            mResolvedUserId = resolvedUserId;
311
312            if (mHardware != null && mCallback != null) {
313                try {
314                    mCallback.onStreamConfigChanged(getConfigsLocked());
315                } catch (RemoteException e) {
316                    Slog.e(TAG, "Connection::resetHardware: " + e);
317                }
318            }
319        }
320
321        public void updateConfigsLocked(TvStreamConfig[] configs) {
322            mConfigs = configs;
323        }
324
325        public TvInputHardwareInfo getHardwareInfoLocked() {
326            return mHardwareInfo;
327        }
328
329        public TvInputInfo getInfoLocked() {
330            return mInfo;
331        }
332
333        public ITvInputHardware getHardwareLocked() {
334            return mHardware;
335        }
336
337        public ITvInputHardwareCallback getCallbackLocked() {
338            return mCallback;
339        }
340
341        public TvStreamConfig[] getConfigsLocked() {
342            return mConfigs;
343        }
344
345        public Integer getCallingUidLocked() {
346            return mCallingUid;
347        }
348
349        public Integer getResolvedUserIdLocked() {
350            return mResolvedUserId;
351        }
352
353        @Override
354        public void binderDied() {
355            synchronized (mLock) {
356                resetLocked(null, null, null, null, null);
357            }
358        }
359    }
360
361    private class TvInputHardwareImpl extends ITvInputHardware.Stub {
362        private final TvInputHardwareInfo mInfo;
363        private boolean mReleased = false;
364        private final Object mImplLock = new Object();
365
366        private final AudioDevicePort mAudioSource;
367        private final AudioDevicePort mAudioSink;
368        private AudioPatch mAudioPatch = null;
369
370        private TvStreamConfig mActiveConfig = null;
371
372        public TvInputHardwareImpl(TvInputHardwareInfo info) {
373            mInfo = info;
374            AudioDevicePort audioSource = null;
375            AudioDevicePort audioSink = null;
376            if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) {
377                ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>();
378                if (mAudioManager.listAudioDevicePorts(devicePorts) == AudioManager.SUCCESS) {
379                    // Find source
380                    for (AudioPort port : devicePorts) {
381                        AudioDevicePort devicePort = (AudioDevicePort) port;
382                        if (devicePort.type() == mInfo.getAudioType() &&
383                                devicePort.address().equals(mInfo.getAudioAddress())) {
384                            audioSource = devicePort;
385                            break;
386                        }
387                    }
388                    // Find sink
389                    // TODO: App may want to specify sink device?
390                    int sinkDevices = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC);
391                    for (AudioPort port : devicePorts) {
392                        AudioDevicePort devicePort = (AudioDevicePort) port;
393                        if (devicePort.type() == sinkDevices) {
394                            audioSink = devicePort;
395                            break;
396                        }
397                    }
398                }
399            }
400            mAudioSource = audioSource;
401            mAudioSink = audioSink;
402        }
403
404        public void release() {
405            synchronized (mImplLock) {
406                if (mAudioPatch != null) {
407                    mAudioManager.releaseAudioPatch(mAudioPatch);
408                    mAudioPatch = null;
409                }
410                mReleased = true;
411            }
412        }
413
414        // A TvInputHardwareImpl object holds only one active session. Therefore, if a client
415        // attempts to call setSurface with different TvStreamConfig objects, the last call will
416        // prevail.
417        @Override
418        public boolean setSurface(Surface surface, TvStreamConfig config)
419                throws RemoteException {
420            synchronized (mImplLock) {
421                if (mReleased) {
422                    throw new IllegalStateException("Device already released.");
423                }
424                if (surface != null && config == null) {
425                    return false;
426                }
427                if (surface == null && mActiveConfig == null) {
428                    return false;
429                }
430                if (mInfo.getType() == TvInputHal.TYPE_HDMI) {
431                    if (surface != null) {
432                        // Set "Active Source" for HDMI.
433                        // TODO(hdmi): mHdmiClient.deviceSelect(...);
434                        mActiveHdmiSources.add(mInfo.getDeviceId());
435                    } else {
436                        mActiveHdmiSources.remove(mInfo.getDeviceId());
437                        if (mActiveHdmiSources.size() == 0) {
438                            // Tell HDMI that no HDMI source is active
439                            // TODO(hdmi): mHdmiClient.portSelect(null);
440                        }
441                    }
442                }
443                if (mAudioSource != null && mAudioSink != null) {
444                    if (surface != null) {
445                        AudioPortConfig sourceConfig = mAudioSource.activeConfig();
446                        AudioPortConfig sinkConfig = mAudioSink.activeConfig();
447                        AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch };
448                        // TODO: build config if activeConfig() == null
449                        mAudioManager.createAudioPatch(
450                                audioPatchArray,
451                                new AudioPortConfig[] { sourceConfig },
452                                new AudioPortConfig[] { sinkConfig });
453                        mAudioPatch = audioPatchArray[0];
454                    } else {
455                        mAudioManager.releaseAudioPatch(mAudioPatch);
456                        mAudioPatch = null;
457                    }
458                }
459                int result = TvInputHal.ERROR_UNKNOWN;
460                if (surface == null) {
461                    result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
462                    mActiveConfig = null;
463                } else {
464                    if (config != mActiveConfig && mActiveConfig != null) {
465                        result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
466                        if (result != TvInputHal.SUCCESS) {
467                            mActiveConfig = null;
468                            return false;
469                        }
470                    }
471                    result = mHal.addStream(mInfo.getDeviceId(), surface, config);
472                    if (result == TvInputHal.SUCCESS) {
473                        mActiveConfig = config;
474                    }
475                }
476                return result == TvInputHal.SUCCESS;
477            }
478        }
479
480        @Override
481        public void setVolume(float volume) throws RemoteException {
482            synchronized (mImplLock) {
483                if (mReleased) {
484                    throw new IllegalStateException("Device already released.");
485                }
486            }
487            // TODO: Use AudioGain?
488        }
489
490        @Override
491        public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException {
492            synchronized (mImplLock) {
493                if (mReleased) {
494                    throw new IllegalStateException("Device already released.");
495                }
496            }
497            if (mInfo.getType() != TvInputHal.TYPE_HDMI) {
498                return false;
499            }
500            // TODO(hdmi): mHdmiClient.sendKeyEvent(event);
501            return false;
502        }
503    }
504
505    private class ClientHandler extends Handler {
506        private static final int DO_SET_AVAILABLE = 1;
507
508        ClientHandler(Looper looper) {
509            super(looper);
510        }
511
512        @Override
513        public final void handleMessage(Message msg) {
514            switch (msg.what) {
515                case DO_SET_AVAILABLE: {
516                    String inputId = (String) msg.obj;
517                    int state = msg.arg1;
518                    mClient.setState(inputId, state);
519                    break;
520                }
521                default: {
522                    Slog.w(TAG, "Unhandled message: " + msg);
523                    break;
524                }
525            }
526        }
527    }
528}
529