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