blob: efe543beb2dfcdd0a0346d708fb3c2f781ecfb7a [file] [log] [blame]
Wonsik Kimc22dbb62014-05-26 02:26:04 +00001/*
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;
Wonsik Kimd7c29182014-05-27 10:38:21 +090020import android.media.AudioDevicePort;
21import android.media.AudioManager;
22import android.media.AudioPatch;
23import android.media.AudioPort;
24import android.media.AudioPortConfig;
Jae Seod5cc4a22014-05-30 16:57:43 -070025import android.media.tv.ITvInputHardware;
26import android.media.tv.ITvInputHardwareCallback;
27import android.media.tv.TvInputHardwareInfo;
28import android.media.tv.TvStreamConfig;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000029import android.os.IBinder;
30import android.os.RemoteException;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000031import 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>();
Wonsik Kimd7c29182014-05-27 10:38:21 +090056 private final AudioManager mAudioManager;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000057
58 private final Object mLock = new Object();
59
60 public TvInputHardwareManager(Context context) {
61 mContext = context;
Wonsik Kimd7c29182014-05-27 10:38:21 +090062 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
Wonsik Kimc22dbb62014-05-26 02:26:04 +000063 // 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
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900126 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
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000139 /**
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 }
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900156 if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000157 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
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900182 || checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000183 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
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900247 public Integer getCallingUidLocked() {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000248 return mCallingUid;
249 }
250
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900251 public Integer getResolvedUserIdLocked() {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000252 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
Wonsik Kimd7c29182014-05-27 10:38:21 +0900268 private final AudioDevicePort mAudioSource;
269 private final AudioDevicePort mAudioSink;
270 private AudioPatch mAudioPatch = null;
271
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900272 private TvStreamConfig mActiveConfig = null;
273
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000274 public TvInputHardwareImpl(TvInputHardwareInfo info) {
275 mInfo = info;
Wonsik Kimd7c29182014-05-27 10:38:21 +0900276 AudioDevicePort audioSource = null;
277 AudioDevicePort audioSink = null;
278 if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) {
279 ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>();
280 if (mAudioManager.listAudioDevicePorts(devicePorts) == AudioManager.SUCCESS) {
281 // Find source
282 for (AudioPort port : devicePorts) {
283 AudioDevicePort devicePort = (AudioDevicePort) port;
284 if (devicePort.type() == mInfo.getAudioType() &&
285 devicePort.address().equals(mInfo.getAudioAddress())) {
286 audioSource = devicePort;
287 break;
288 }
289 }
290 // Find sink
291 // TODO: App may want to specify sink device?
292 int sinkDevices = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC);
293 for (AudioPort port : devicePorts) {
294 AudioDevicePort devicePort = (AudioDevicePort) port;
295 if (devicePort.type() == sinkDevices) {
296 audioSink = devicePort;
297 break;
298 }
299 }
300 }
301 }
302 mAudioSource = audioSource;
303 mAudioSink = audioSink;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000304 }
305
306 public void release() {
307 synchronized (mImplLock) {
Wonsik Kimd7c29182014-05-27 10:38:21 +0900308 if (mAudioPatch != null) {
309 mAudioManager.releaseAudioPatch(mAudioPatch);
310 mAudioPatch = null;
311 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000312 mReleased = true;
313 }
314 }
315
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900316 // A TvInputHardwareImpl object holds only one active session. Therefore, if a client
317 // attempts to call setSurface with different TvStreamConfig objects, the last call will
318 // prevail.
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000319 @Override
320 public boolean setSurface(Surface surface, TvStreamConfig config)
321 throws RemoteException {
322 synchronized (mImplLock) {
323 if (mReleased) {
324 throw new IllegalStateException("Device already released.");
325 }
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900326 if (surface != null && config == null) {
327 return false;
328 }
329 if (surface == null && mActiveConfig == null) {
330 return false;
331 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000332 if (mInfo.getType() == TvInputHal.TYPE_HDMI) {
333 if (surface != null) {
334 // Set "Active Source" for HDMI.
335 // TODO(hdmi): mHdmiClient.deviceSelect(...);
336 mActiveHdmiSources.add(mInfo.getDeviceId());
337 } else {
338 mActiveHdmiSources.remove(mInfo.getDeviceId());
339 if (mActiveHdmiSources.size() == 0) {
340 // Tell HDMI that no HDMI source is active
341 // TODO(hdmi): mHdmiClient.portSelect(null);
342 }
343 }
344 }
Wonsik Kimd7c29182014-05-27 10:38:21 +0900345 if (mAudioSource != null && mAudioSink != null) {
346 if (surface != null) {
347 AudioPortConfig sourceConfig = mAudioSource.activeConfig();
348 AudioPortConfig sinkConfig = mAudioSink.activeConfig();
349 AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch };
350 // TODO: build config if activeConfig() == null
351 mAudioManager.createAudioPatch(
352 audioPatchArray,
353 new AudioPortConfig[] { sourceConfig },
354 new AudioPortConfig[] { sinkConfig });
355 mAudioPatch = audioPatchArray[0];
356 } else {
357 mAudioManager.releaseAudioPatch(mAudioPatch);
358 mAudioPatch = null;
359 }
360 }
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900361 int result = TvInputHal.ERROR_UNKNOWN;
362 if (surface == null) {
363 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
364 mActiveConfig = null;
365 } else {
366 if (config != mActiveConfig && mActiveConfig != null) {
367 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig);
368 if (result != TvInputHal.SUCCESS) {
369 mActiveConfig = null;
370 return false;
371 }
372 }
373 result = mHal.addStream(mInfo.getDeviceId(), surface, config);
374 if (result == TvInputHal.SUCCESS) {
375 mActiveConfig = config;
376 }
377 }
378 return result == TvInputHal.SUCCESS;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000379 }
380 }
381
382 @Override
383 public void setVolume(float volume) throws RemoteException {
384 synchronized (mImplLock) {
385 if (mReleased) {
386 throw new IllegalStateException("Device already released.");
387 }
388 }
Wonsik Kimd7c29182014-05-27 10:38:21 +0900389 // TODO: Use AudioGain?
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000390 }
391
392 @Override
393 public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException {
394 synchronized (mImplLock) {
395 if (mReleased) {
396 throw new IllegalStateException("Device already released.");
397 }
398 }
399 if (mInfo.getType() != TvInputHal.TYPE_HDMI) {
400 return false;
401 }
402 // TODO(hdmi): mHdmiClient.sendKeyEvent(event);
403 return false;
404 }
405 }
406}