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