blob: 8f237db51078c6247fe300d7575a863f2b0779ef [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
Wonsik Kim969167d2014-06-24 16:33:17 +090019import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
20import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED;
21
Wonsik Kimc22dbb62014-05-26 02:26:04 +000022import android.content.Context;
Wonsik Kim969167d2014-06-24 16:33:17 +090023import android.hardware.hdmi.HdmiControlManager;
24import android.hardware.hdmi.HdmiHotplugEvent;
Wonsik Kimd7c29182014-05-27 10:38:21 +090025import android.media.AudioDevicePort;
26import android.media.AudioManager;
27import android.media.AudioPatch;
28import android.media.AudioPort;
29import android.media.AudioPortConfig;
Jae Seod5cc4a22014-05-30 16:57:43 -070030import android.media.tv.ITvInputHardware;
31import android.media.tv.ITvInputHardwareCallback;
32import android.media.tv.TvInputHardwareInfo;
Wonsik Kim969167d2014-06-24 16:33:17 +090033import android.media.tv.TvInputInfo;
Jae Seod5cc4a22014-05-30 16:57:43 -070034import android.media.tv.TvStreamConfig;
Wonsik Kim969167d2014-06-24 16:33:17 +090035import android.os.Handler;
36import android.os.HandlerThread;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000037import android.os.IBinder;
Wonsik Kim969167d2014-06-24 16:33:17 +090038import android.os.Looper;
39import android.os.Message;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000040import android.os.RemoteException;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000041import android.util.Slog;
42import android.util.SparseArray;
Wonsik Kim969167d2014-06-24 16:33:17 +090043import android.util.SparseBooleanArray;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000044import android.view.KeyEvent;
45import android.view.Surface;
46
Wonsik Kim969167d2014-06-24 16:33:17 +090047import com.android.server.SystemService;
48
Wonsik Kimc22dbb62014-05-26 02:26:04 +000049import 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 */
Wonsik Kim969167d2014-06-24 16:33:17 +090062class TvInputHardwareManager
63 implements TvInputHal.Callback, HdmiControlManager.HotplugEventListener {
Wonsik Kimc22dbb62014-05-26 02:26:04 +000064 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;
Wonsik Kim969167d2014-06-24 16:33:17 +090069 private final TvInputManagerService.Client mClient;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000070 private final Set<Integer> mActiveHdmiSources = new HashSet<Integer>();
Wonsik Kimd7c29182014-05-27 10:38:21 +090071 private final AudioManager mAudioManager;
Wonsik Kim969167d2014-06-24 16:33:17 +090072 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;
Wonsik Kimc22dbb62014-05-26 02:26:04 +000079
80 private final Object mLock = new Object();
81
Wonsik Kim969167d2014-06-24 16:33:17 +090082 public TvInputHardwareManager(Context context, TvInputManagerService.Client client) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +000083 mContext = context;
Wonsik Kim969167d2014-06-24 16:33:17 +090084 mClient = client;
Wonsik Kimd7c29182014-05-27 10:38:21 +090085 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
Wonsik Kimc22dbb62014-05-26 02:26:04 +000086 mHal.init();
Wonsik Kim969167d2014-06-24 16:33:17 +090087
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 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +000098 }
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) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900115 mInfoList.add(mConnections.valueAt(i).getHardwareInfoLocked());
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000116 }
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 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900127 connection.resetLocked(null, null, null, null, null);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000128 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
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900158 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
Wonsik Kim969167d2014-06-24 16:33:17 +0900171 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
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000202 /**
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,
Wonsik Kim969167d2014-06-24 16:33:17 +0900209 TvInputInfo info, int callingUid, int resolvedUserId) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000210 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 }
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900219 if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900220 TvInputHardwareImpl hardware =
221 new TvInputHardwareImpl(connection.getHardwareInfoLocked());
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000222 try {
223 callback.asBinder().linkToDeath(connection, 0);
224 } catch (RemoteException e) {
225 hardware.release();
226 return null;
227 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900228 connection.resetLocked(hardware, callback, info, callingUid, resolvedUserId);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000229 }
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
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900246 || checkUidChangedLocked(connection, callingUid, resolvedUserId)) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000247 return;
248 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900249 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();
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000278 }
279 }
280
281 private class Connection implements IBinder.DeathRecipient {
Wonsik Kim969167d2014-06-24 16:33:17 +0900282 private final TvInputHardwareInfo mHardwareInfo;
283 private TvInputInfo mInfo;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000284 private TvInputHardwareImpl mHardware = null;
285 private ITvInputHardwareCallback mCallback;
286 private TvStreamConfig[] mConfigs = null;
287 private Integer mCallingUid = null;
288 private Integer mResolvedUserId = null;
289
Wonsik Kim969167d2014-06-24 16:33:17 +0900290 public Connection(TvInputHardwareInfo hardwareInfo) {
291 mHardwareInfo = hardwareInfo;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000292 }
293
294 // *Locked methods assume TvInputHardwareManager.mLock is held.
295
Wonsik Kim969167d2014-06-24 16:33:17 +0900296 public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback,
297 TvInputInfo info, Integer callingUid, Integer resolvedUserId) {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000298 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;
Wonsik Kim969167d2014-06-24 16:33:17 +0900308 mInfo = info;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000309 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
Wonsik Kim969167d2014-06-24 16:33:17 +0900325 public TvInputHardwareInfo getHardwareInfoLocked() {
326 return mHardwareInfo;
327 }
328
329 public TvInputInfo getInfoLocked() {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000330 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
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900345 public Integer getCallingUidLocked() {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000346 return mCallingUid;
347 }
348
Wonsik Kime7ae0ce2014-06-19 00:48:35 +0900349 public Integer getResolvedUserIdLocked() {
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000350 return mResolvedUserId;
351 }
352
353 @Override
354 public void binderDied() {
355 synchronized (mLock) {
Wonsik Kim969167d2014-06-24 16:33:17 +0900356 resetLocked(null, null, null, null, null);
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000357 }
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
Wonsik Kimd7c29182014-05-27 10:38:21 +0900366 private final AudioDevicePort mAudioSource;
367 private final AudioDevicePort mAudioSink;
368 private AudioPatch mAudioPatch = null;
369
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900370 private TvStreamConfig mActiveConfig = null;
371
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000372 public TvInputHardwareImpl(TvInputHardwareInfo info) {
373 mInfo = info;
Wonsik Kimd7c29182014-05-27 10:38:21 +0900374 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;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000402 }
403
404 public void release() {
405 synchronized (mImplLock) {
Wonsik Kimd7c29182014-05-27 10:38:21 +0900406 if (mAudioPatch != null) {
407 mAudioManager.releaseAudioPatch(mAudioPatch);
408 mAudioPatch = null;
409 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000410 mReleased = true;
411 }
412 }
413
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900414 // 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.
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000417 @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 }
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900424 if (surface != null && config == null) {
425 return false;
426 }
427 if (surface == null && mActiveConfig == null) {
428 return false;
429 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000430 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 }
Wonsik Kimd7c29182014-05-27 10:38:21 +0900443 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 }
Wonsik Kim839ae5f2014-07-03 19:06:56 +0900459 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;
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000477 }
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 }
Wonsik Kimd7c29182014-05-27 10:38:21 +0900487 // TODO: Use AudioGain?
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000488 }
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 }
Wonsik Kim969167d2014-06-24 16:33:17 +0900504
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 }
Wonsik Kimc22dbb62014-05-26 02:26:04 +0000528}