TvInputHardwareManager.java revision 187423c0bc4b27479bc8c23bd86969429094b296
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.HdmiControlManager; 25import android.hardware.hdmi.HdmiHotplugEvent; 26import android.hardware.hdmi.IHdmiDeviceEventListener; 27import android.media.AudioDevicePort; 28import android.media.AudioManager; 29import android.media.AudioPatch; 30import android.media.AudioPort; 31import android.media.AudioPortConfig; 32import android.media.tv.ITvInputHardware; 33import android.media.tv.ITvInputHardwareCallback; 34import android.media.tv.TvInputHardwareInfo; 35import android.media.tv.TvInputInfo; 36import android.media.tv.TvStreamConfig; 37import android.os.Handler; 38import android.os.IBinder; 39import android.os.Looper; 40import android.os.Message; 41import android.os.RemoteException; 42import android.util.Slog; 43import android.util.SparseArray; 44import android.util.SparseBooleanArray; 45import android.view.KeyEvent; 46import android.view.Surface; 47 48import com.android.server.SystemService; 49 50import java.util.ArrayList; 51import java.util.HashSet; 52import java.util.List; 53import java.util.Set; 54 55/** 56 * A helper class for TvInputManagerService to handle TV input hardware. 57 * 58 * This class does a basic connection management and forwarding calls to TvInputHal which eventually 59 * calls to tv_input HAL module. 60 * 61 * @hide 62 */ 63class TvInputHardwareManager 64 implements TvInputHal.Callback, HdmiControlManager.HotplugEventListener { 65 private static final String TAG = TvInputHardwareManager.class.getSimpleName(); 66 private final TvInputHal mHal = new TvInputHal(this); 67 private final SparseArray<Connection> mConnections = new SparseArray<Connection>(); 68 private final List<TvInputHardwareInfo> mInfoList = new ArrayList<TvInputHardwareInfo>(); 69 private final Context mContext; 70 private final Listener mListener; 71 private final Set<Integer> mActiveHdmiSources = new HashSet<Integer>(); 72 private final AudioManager mAudioManager; 73 private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray(); 74 // TODO: Should handle INACTIVE case. 75 private final SparseArray<TvInputInfo> mTvInputInfoMap = new SparseArray<TvInputInfo>(); 76 private final IHdmiDeviceEventListener mHdmiDeviceEventListener = new HdmiDeviceEventListener(); 77 78 // Calls to mListener should happen here. 79 private final Handler mHandler = new ListenerHandler(); 80 81 private final Object mLock = new Object(); 82 83 public TvInputHardwareManager(Context context, Listener listener) { 84 mContext = context; 85 mListener = listener; 86 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 87 mHal.init(); 88 } 89 90 public void onBootPhase(int phase) { 91 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { 92 HdmiControlManager hdmiControlManager = 93 (HdmiControlManager) mContext.getSystemService(Context.HDMI_CONTROL_SERVICE); 94 hdmiControlManager.addHotplugEventListener(this); 95 } 96 } 97 98 @Override 99 public void onDeviceAvailable( 100 TvInputHardwareInfo info, TvStreamConfig[] configs) { 101 synchronized (mLock) { 102 Connection connection = new Connection(info); 103 connection.updateConfigsLocked(configs); 104 mConnections.put(info.getDeviceId(), connection); 105 buildInfoListLocked(); 106 mHandler.obtainMessage( 107 ListenerHandler.HARDWARE_DEVICE_ADDED, 0, 0, info).sendToTarget(); 108 } 109 } 110 111 private void buildInfoListLocked() { 112 mInfoList.clear(); 113 for (int i = 0; i < mConnections.size(); ++i) { 114 mInfoList.add(mConnections.valueAt(i).getHardwareInfoLocked()); 115 } 116 } 117 118 @Override 119 public void onDeviceUnavailable(int deviceId) { 120 synchronized (mLock) { 121 Connection connection = mConnections.get(deviceId); 122 if (connection == null) { 123 Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId); 124 return; 125 } 126 connection.resetLocked(null, null, null, null, null); 127 mConnections.remove(deviceId); 128 buildInfoListLocked(); 129 mHandler.obtainMessage( 130 ListenerHandler.HARDWARE_DEVICE_REMOVED, deviceId, 0).sendToTarget(); 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(ListenerHandler.STATE_CHANGED, 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(ListenerHandler.STATE_CHANGED, 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 interface Listener { 506 public void onStateChanged(String inputId, int state); 507 public void onHardwareDeviceAdded(TvInputHardwareInfo info); 508 public void onHardwareDeviceRemoved(int deviceId); 509 public void onHdmiCecDeviceAdded(HdmiCecDeviceInfo cecDevice); 510 public void onHdmiCecDeviceRemoved(HdmiCecDeviceInfo cecDevice); 511 } 512 513 private class ListenerHandler extends Handler { 514 private static final int STATE_CHANGED = 1; 515 private static final int HARDWARE_DEVICE_ADDED = 2; 516 private static final int HARDWARE_DEVICE_REMOVED = 3; 517 private static final int CEC_DEVICE_ADDED = 4; 518 private static final int CEC_DEVICE_REMOVED = 5; 519 520 @Override 521 public final void handleMessage(Message msg) { 522 switch (msg.what) { 523 case STATE_CHANGED: { 524 String inputId = (String) msg.obj; 525 int state = msg.arg1; 526 mListener.onStateChanged(inputId, state); 527 break; 528 } 529 case HARDWARE_DEVICE_ADDED: { 530 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj; 531 mListener.onHardwareDeviceAdded(info); 532 break; 533 } 534 case HARDWARE_DEVICE_REMOVED: { 535 int deviceId = msg.arg1; 536 mListener.onHardwareDeviceRemoved(deviceId); 537 break; 538 } 539 case CEC_DEVICE_ADDED: { 540 HdmiCecDeviceInfo info = (HdmiCecDeviceInfo) msg.obj; 541 mListener.onHdmiCecDeviceAdded(info); 542 break; 543 } 544 case CEC_DEVICE_REMOVED: { 545 HdmiCecDeviceInfo info = (HdmiCecDeviceInfo) msg.obj; 546 mListener.onHdmiCecDeviceRemoved(info); 547 break; 548 } 549 default: { 550 Slog.w(TAG, "Unhandled message: " + msg); 551 break; 552 } 553 } 554 } 555 } 556 557 private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub { 558 @Override 559 public void onStatusChanged(HdmiCecDeviceInfo deviceInfo, boolean activated) { 560 mHandler.obtainMessage( 561 activated ? ListenerHandler.CEC_DEVICE_ADDED 562 : ListenerHandler.CEC_DEVICE_REMOVED, 563 0, 0, deviceInfo).sendToTarget(); 564 } 565 } 566} 567