TvInputHardwareManager.java revision d7c29189aa639bfac1e6efcd222e65c2c8ecf3f1
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 android.content.Context;
20import android.media.AudioDevicePort;
21import android.media.AudioManager;
22import android.media.AudioPatch;
23import android.media.AudioPort;
24import android.media.AudioPortConfig;
25import android.media.tv.ITvInputHardware;
26import android.media.tv.ITvInputHardwareCallback;
27import android.media.tv.TvInputHardwareInfo;
28import android.media.tv.TvStreamConfig;
29import android.os.IBinder;
30import android.os.RemoteException;
31import android.util.Slog;
32import android.util.SparseArray;
33import android.view.KeyEvent;
34import android.view.Surface;
35
36import java.util.ArrayList;
37import java.util.HashSet;
38import java.util.List;
39import java.util.Set;
40
41/**
42 * A helper class for TvInputManagerService to handle TV input hardware.
43 *
44 * This class does a basic connection management and forwarding calls to TvInputHal which eventually
45 * calls to tv_input HAL module.
46 *
47 * @hide
48 */
49class TvInputHardwareManager implements TvInputHal.Callback {
50    private static final String TAG = TvInputHardwareManager.class.getSimpleName();
51    private final TvInputHal mHal = new TvInputHal(this);
52    private final SparseArray<Connection> mConnections = new SparseArray<Connection>();
53    private final List<TvInputHardwareInfo> mInfoList = new ArrayList<TvInputHardwareInfo>();
54    private final Context mContext;
55    private final Set<Integer> mActiveHdmiSources = new HashSet<Integer>();
56    private final AudioManager mAudioManager;
57
58    private final Object mLock = new Object();
59
60    public TvInputHardwareManager(Context context) {
61        mContext = context;
62        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
63        // TODO(hdmi): mHdmiManager = mContext.getSystemService(...);
64        // TODO(hdmi): mHdmiClient = mHdmiManager.getTvClient();
65        mHal.init();
66    }
67
68    @Override
69    public void onDeviceAvailable(
70            TvInputHardwareInfo info, TvStreamConfig[] configs) {
71        synchronized (mLock) {
72            Connection connection = new Connection(info);
73            connection.updateConfigsLocked(configs);
74            mConnections.put(info.getDeviceId(), connection);
75            buildInfoListLocked();
76            // TODO: notify if necessary
77        }
78    }
79
80    private void buildInfoListLocked() {
81        mInfoList.clear();
82        for (int i = 0; i < mConnections.size(); ++i) {
83            mInfoList.add(mConnections.valueAt(i).getInfoLocked());
84        }
85    }
86
87    @Override
88    public void onDeviceUnavailable(int deviceId) {
89        synchronized (mLock) {
90            Connection connection = mConnections.get(deviceId);
91            if (connection == null) {
92                Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId);
93                return;
94            }
95            connection.resetLocked(null, null, null, null);
96            mConnections.remove(deviceId);
97            buildInfoListLocked();
98            // TODO: notify if necessary
99        }
100    }
101
102    @Override
103    public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs) {
104        synchronized (mLock) {
105            Connection connection = mConnections.get(deviceId);
106            if (connection == null) {
107                Slog.e(TAG, "StreamConfigurationChanged: Cannot find a connection with "
108                        + deviceId);
109                return;
110            }
111            connection.updateConfigsLocked(configs);
112            try {
113                connection.getCallbackLocked().onStreamConfigChanged(configs);
114            } catch (RemoteException e) {
115                Slog.e(TAG, "onStreamConfigurationChanged: " + e);
116            }
117        }
118    }
119
120    public List<TvInputHardwareInfo> getHardwareList() {
121        synchronized (mLock) {
122            return mInfoList;
123        }
124    }
125
126    private boolean checkUidChangedLocked(
127            Connection connection, int callingUid, int resolvedUserId) {
128        Integer connectionCallingUid = connection.getCallingUidLocked();
129        Integer connectionResolvedUserId = connection.getResolvedUserIdLocked();
130        if (connectionCallingUid == null || connectionResolvedUserId == null) {
131            return true;
132        }
133        if (connectionCallingUid != callingUid || connectionResolvedUserId != resolvedUserId) {
134            return true;
135        }
136        return false;
137    }
138
139    /**
140     * Create a TvInputHardware object with a specific deviceId. One service at a time can access
141     * the object, and if more than one process attempts to create hardware with the same deviceId,
142     * the latest service will get the object and all the other hardware are released. The
143     * release is notified via ITvInputHardwareCallback.onReleased().
144     */
145    public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback,
146            int callingUid, int resolvedUserId) {
147        if (callback == null) {
148            throw new NullPointerException();
149        }
150        synchronized (mLock) {
151            Connection connection = mConnections.get(deviceId);
152            if (connection == null) {
153                Slog.e(TAG, "Invalid deviceId : " + deviceId);
154                return null;
155            }
156            if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
157                TvInputHardwareImpl hardware = new TvInputHardwareImpl(connection.getInfoLocked());
158                try {
159                    callback.asBinder().linkToDeath(connection, 0);
160                } catch (RemoteException e) {
161                    hardware.release();
162                    return null;
163                }
164                connection.resetLocked(hardware, callback, callingUid, resolvedUserId);
165            }
166            return connection.getHardwareLocked();
167        }
168    }
169
170    /**
171     * Release the specified hardware.
172     */
173    public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid,
174            int resolvedUserId) {
175        synchronized (mLock) {
176            Connection connection = mConnections.get(deviceId);
177            if (connection == null) {
178                Slog.e(TAG, "Invalid deviceId : " + deviceId);
179                return;
180            }
181            if (connection.getHardwareLocked() != hardware
182                    || checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
183                return;
184            }
185            connection.resetLocked(null, null, null, null);
186        }
187    }
188
189    private class Connection implements IBinder.DeathRecipient {
190        private final TvInputHardwareInfo mInfo;
191        private TvInputHardwareImpl mHardware = null;
192        private ITvInputHardwareCallback mCallback;
193        private TvStreamConfig[] mConfigs = null;
194        private Integer mCallingUid = null;
195        private Integer mResolvedUserId = null;
196
197        public Connection(TvInputHardwareInfo info) {
198            mInfo = info;
199        }
200
201        // *Locked methods assume TvInputHardwareManager.mLock is held.
202
203        public void resetLocked(TvInputHardwareImpl hardware,
204                ITvInputHardwareCallback callback, Integer callingUid, Integer resolvedUserId) {
205            if (mHardware != null) {
206                try {
207                    mCallback.onReleased();
208                } catch (RemoteException e) {
209                    Slog.e(TAG, "Connection::resetHardware: " + e);
210                }
211                mHardware.release();
212            }
213            mHardware = hardware;
214            mCallback = callback;
215            mCallingUid = callingUid;
216            mResolvedUserId = resolvedUserId;
217
218            if (mHardware != null && mCallback != null) {
219                try {
220                    mCallback.onStreamConfigChanged(getConfigsLocked());
221                } catch (RemoteException e) {
222                    Slog.e(TAG, "Connection::resetHardware: " + e);
223                }
224            }
225        }
226
227        public void updateConfigsLocked(TvStreamConfig[] configs) {
228            mConfigs = configs;
229        }
230
231        public TvInputHardwareInfo getInfoLocked() {
232            return mInfo;
233        }
234
235        public ITvInputHardware getHardwareLocked() {
236            return mHardware;
237        }
238
239        public ITvInputHardwareCallback getCallbackLocked() {
240            return mCallback;
241        }
242
243        public TvStreamConfig[] getConfigsLocked() {
244            return mConfigs;
245        }
246
247        public Integer getCallingUidLocked() {
248            return mCallingUid;
249        }
250
251        public Integer getResolvedUserIdLocked() {
252            return mResolvedUserId;
253        }
254
255        @Override
256        public void binderDied() {
257            synchronized (mLock) {
258                resetLocked(null, null, null, null);
259            }
260        }
261    }
262
263    private class TvInputHardwareImpl extends ITvInputHardware.Stub {
264        private final TvInputHardwareInfo mInfo;
265        private boolean mReleased = false;
266        private final Object mImplLock = new Object();
267
268        private final AudioDevicePort mAudioSource;
269        private final AudioDevicePort mAudioSink;
270        private AudioPatch mAudioPatch = null;
271
272        public TvInputHardwareImpl(TvInputHardwareInfo info) {
273            mInfo = info;
274            AudioDevicePort audioSource = null;
275            AudioDevicePort audioSink = null;
276            if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) {
277                ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>();
278                if (mAudioManager.listAudioDevicePorts(devicePorts) == AudioManager.SUCCESS) {
279                    // Find source
280                    for (AudioPort port : devicePorts) {
281                        AudioDevicePort devicePort = (AudioDevicePort) port;
282                        if (devicePort.type() == mInfo.getAudioType() &&
283                                devicePort.address().equals(mInfo.getAudioAddress())) {
284                            audioSource = devicePort;
285                            break;
286                        }
287                    }
288                    // Find sink
289                    // TODO: App may want to specify sink device?
290                    int sinkDevices = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC);
291                    for (AudioPort port : devicePorts) {
292                        AudioDevicePort devicePort = (AudioDevicePort) port;
293                        if (devicePort.type() == sinkDevices) {
294                            audioSink = devicePort;
295                            break;
296                        }
297                    }
298                }
299            }
300            mAudioSource = audioSource;
301            mAudioSink = audioSink;
302        }
303
304        public void release() {
305            synchronized (mImplLock) {
306                if (mAudioPatch != null) {
307                    mAudioManager.releaseAudioPatch(mAudioPatch);
308                    mAudioPatch = null;
309                }
310                mReleased = true;
311            }
312        }
313
314        @Override
315        public boolean setSurface(Surface surface, TvStreamConfig config)
316                throws RemoteException {
317            synchronized (mImplLock) {
318                if (mReleased) {
319                    throw new IllegalStateException("Device already released.");
320                }
321                if (mInfo.getType() == TvInputHal.TYPE_HDMI) {
322                    if (surface != null) {
323                        // Set "Active Source" for HDMI.
324                        // TODO(hdmi): mHdmiClient.deviceSelect(...);
325                        mActiveHdmiSources.add(mInfo.getDeviceId());
326                    } else {
327                        mActiveHdmiSources.remove(mInfo.getDeviceId());
328                        if (mActiveHdmiSources.size() == 0) {
329                            // Tell HDMI that no HDMI source is active
330                            // TODO(hdmi): mHdmiClient.portSelect(null);
331                        }
332                    }
333                }
334                if (mAudioSource != null && mAudioSink != null) {
335                    if (surface != null) {
336                        AudioPortConfig sourceConfig = mAudioSource.activeConfig();
337                        AudioPortConfig sinkConfig = mAudioSink.activeConfig();
338                        AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch };
339                        // TODO: build config if activeConfig() == null
340                        mAudioManager.createAudioPatch(
341                                audioPatchArray,
342                                new AudioPortConfig[] { sourceConfig },
343                                new AudioPortConfig[] { sinkConfig });
344                        mAudioPatch = audioPatchArray[0];
345                    } else {
346                        mAudioManager.releaseAudioPatch(mAudioPatch);
347                        mAudioPatch = null;
348                    }
349                }
350                return mHal.setSurface(mInfo.getDeviceId(), surface, config) == TvInputHal.SUCCESS;
351            }
352        }
353
354        @Override
355        public void setVolume(float volume) throws RemoteException {
356            synchronized (mImplLock) {
357                if (mReleased) {
358                    throw new IllegalStateException("Device already released.");
359                }
360            }
361            // TODO: Use AudioGain?
362        }
363
364        @Override
365        public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException {
366            synchronized (mImplLock) {
367                if (mReleased) {
368                    throw new IllegalStateException("Device already released.");
369                }
370            }
371            if (mInfo.getType() != TvInputHal.TYPE_HDMI) {
372                return false;
373            }
374            // TODO(hdmi): mHdmiClient.sendKeyEvent(event);
375            return false;
376        }
377    }
378}
379