TvInputHardwareManager.java revision b683e6e4c70da3644427cb5ec6a8342b3a91bb23
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.HdmiDeviceInfo; 25import android.hardware.hdmi.HdmiHotplugEvent; 26import android.hardware.hdmi.IHdmiControlService; 27import android.hardware.hdmi.IHdmiDeviceEventListener; 28import android.hardware.hdmi.IHdmiHotplugEventListener; 29import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener; 30import android.media.AudioDevicePort; 31import android.media.AudioFormat; 32import android.media.AudioGain; 33import android.media.AudioGainConfig; 34import android.media.AudioManager; 35import android.media.AudioPatch; 36import android.media.AudioPort; 37import android.media.AudioPortConfig; 38import android.media.tv.ITvInputHardware; 39import android.media.tv.ITvInputHardwareCallback; 40import android.media.tv.TvInputHardwareInfo; 41import android.media.tv.TvInputInfo; 42import android.media.tv.TvStreamConfig; 43import android.os.Handler; 44import android.os.IBinder; 45import android.os.Message; 46import android.os.RemoteException; 47import android.os.ServiceManager; 48import android.util.ArrayMap; 49import android.util.Slog; 50import android.util.SparseArray; 51import android.util.SparseBooleanArray; 52import android.view.KeyEvent; 53import android.view.Surface; 54 55import com.android.internal.os.SomeArgs; 56import com.android.server.SystemService; 57 58import java.util.ArrayList; 59import java.util.Arrays; 60import java.util.Collections; 61import java.util.Iterator; 62import java.util.LinkedList; 63import java.util.List; 64import java.util.Map; 65 66/** 67 * A helper class for TvInputManagerService to handle TV input hardware. 68 * 69 * This class does a basic connection management and forwarding calls to TvInputHal which eventually 70 * calls to tv_input HAL module. 71 * 72 * @hide 73 */ 74class TvInputHardwareManager implements TvInputHal.Callback { 75 private static final String TAG = TvInputHardwareManager.class.getSimpleName(); 76 77 private final Listener mListener; 78 private final TvInputHal mHal = new TvInputHal(this); 79 private final SparseArray<Connection> mConnections = new SparseArray<>(); 80 private final List<TvInputHardwareInfo> mHardwareList = new ArrayList<>(); 81 private final List<HdmiDeviceInfo> mHdmiDeviceList = new LinkedList<>(); 82 /* A map from a device ID to the matching TV input ID. */ 83 private final SparseArray<String> mHardwareInputIdMap = new SparseArray<>(); 84 /* A map from a HDMI logical address to the matching TV input ID. */ 85 private final SparseArray<String> mHdmiInputIdMap = new SparseArray<>(); 86 private final Map<String, TvInputInfo> mInputMap = new ArrayMap<>(); 87 88 private final AudioManager mAudioManager; 89 private IHdmiControlService mHdmiControlService; 90 private final IHdmiHotplugEventListener mHdmiHotplugEventListener = 91 new HdmiHotplugEventListener(); 92 private final IHdmiDeviceEventListener mHdmiDeviceEventListener = new HdmiDeviceEventListener(); 93 private final IHdmiSystemAudioModeChangeListener mHdmiSystemAudioModeChangeListener = 94 new HdmiSystemAudioModeChangeListener(); 95 96 // TODO: Should handle STANDBY case. 97 private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray(); 98 private final List<Message> mPendingHdmiDeviceEvents = new LinkedList<>(); 99 100 // Calls to mListener should happen here. 101 private final Handler mHandler = new ListenerHandler(); 102 103 private final Object mLock = new Object(); 104 105 public TvInputHardwareManager(Context context, Listener listener) { 106 mListener = listener; 107 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 108 mHal.init(); 109 } 110 111 public void onBootPhase(int phase) { 112 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { 113 mHdmiControlService = IHdmiControlService.Stub.asInterface(ServiceManager.getService( 114 Context.HDMI_CONTROL_SERVICE)); 115 if (mHdmiControlService != null) { 116 try { 117 mHdmiControlService.addHotplugEventListener(mHdmiHotplugEventListener); 118 mHdmiControlService.addDeviceEventListener(mHdmiDeviceEventListener); 119 mHdmiControlService.addSystemAudioModeChangeListener( 120 mHdmiSystemAudioModeChangeListener); 121 mHdmiDeviceList.addAll(mHdmiControlService.getInputDevices()); 122 } catch (RemoteException e) { 123 Slog.w(TAG, "Error registering listeners to HdmiControlService:", e); 124 } 125 } else { 126 Slog.w(TAG, "HdmiControlService is not available"); 127 } 128 } 129 } 130 131 @Override 132 public void onDeviceAvailable(TvInputHardwareInfo info, TvStreamConfig[] configs) { 133 synchronized (mLock) { 134 Connection connection = new Connection(info); 135 connection.updateConfigsLocked(configs); 136 mConnections.put(info.getDeviceId(), connection); 137 buildHardwareListLocked(); 138 mHandler.obtainMessage( 139 ListenerHandler.HARDWARE_DEVICE_ADDED, 0, 0, info).sendToTarget(); 140 if (info.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) { 141 processPendingHdmiDeviceEventsLocked(); 142 } 143 } 144 } 145 146 private void buildHardwareListLocked() { 147 mHardwareList.clear(); 148 for (int i = 0; i < mConnections.size(); ++i) { 149 mHardwareList.add(mConnections.valueAt(i).getHardwareInfoLocked()); 150 } 151 } 152 153 @Override 154 public void onDeviceUnavailable(int deviceId) { 155 synchronized (mLock) { 156 Connection connection = mConnections.get(deviceId); 157 if (connection == null) { 158 Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId); 159 return; 160 } 161 connection.resetLocked(null, null, null, null, null); 162 mConnections.remove(deviceId); 163 buildHardwareListLocked(); 164 TvInputHardwareInfo info = connection.getHardwareInfoLocked(); 165 if (info.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) { 166 // Remove HDMI devices linked with this hardware. 167 for (Iterator<HdmiDeviceInfo> it = mHdmiDeviceList.iterator(); it.hasNext();) { 168 HdmiDeviceInfo deviceInfo = it.next(); 169 if (deviceInfo.getPortId() == info.getHdmiPortId()) { 170 mHandler.obtainMessage(ListenerHandler.HDMI_DEVICE_REMOVED, 0, 0, 171 deviceInfo).sendToTarget(); 172 it.remove(); 173 } 174 } 175 } 176 mHandler.obtainMessage( 177 ListenerHandler.HARDWARE_DEVICE_REMOVED, 0, 0, info).sendToTarget(); 178 } 179 } 180 181 @Override 182 public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs) { 183 synchronized (mLock) { 184 Connection connection = mConnections.get(deviceId); 185 if (connection == null) { 186 Slog.e(TAG, "StreamConfigurationChanged: Cannot find a connection with " 187 + deviceId); 188 return; 189 } 190 connection.updateConfigsLocked(configs); 191 String inputId = mHardwareInputIdMap.get(deviceId); 192 if (inputId != null) { 193 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, 194 convertConnectedToState(configs.length > 0), 0, inputId).sendToTarget(); 195 } 196 try { 197 connection.getCallbackLocked().onStreamConfigChanged(configs); 198 } catch (RemoteException e) { 199 Slog.e(TAG, "error in onStreamConfigurationChanged", e); 200 } 201 } 202 } 203 204 @Override 205 public void onFirstFrameCaptured(int deviceId, int streamId) { 206 synchronized (mLock) { 207 Connection connection = mConnections.get(deviceId); 208 if (connection == null) { 209 Slog.e(TAG, "FirstFrameCaptured: Cannot find a connection with " 210 + deviceId); 211 return; 212 } 213 Runnable runnable = connection.getOnFirstFrameCapturedLocked(); 214 if (runnable != null) { 215 runnable.run(); 216 connection.setOnFirstFrameCapturedLocked(null); 217 } 218 } 219 } 220 221 public List<TvInputHardwareInfo> getHardwareList() { 222 synchronized (mLock) { 223 return Collections.unmodifiableList(mHardwareList); 224 } 225 } 226 227 public List<HdmiDeviceInfo> getHdmiDeviceList() { 228 synchronized (mLock) { 229 return Collections.unmodifiableList(mHdmiDeviceList); 230 } 231 } 232 233 private boolean checkUidChangedLocked( 234 Connection connection, int callingUid, int resolvedUserId) { 235 Integer connectionCallingUid = connection.getCallingUidLocked(); 236 Integer connectionResolvedUserId = connection.getResolvedUserIdLocked(); 237 if (connectionCallingUid == null || connectionResolvedUserId == null) { 238 return true; 239 } 240 if (connectionCallingUid != callingUid || connectionResolvedUserId != resolvedUserId) { 241 return true; 242 } 243 return false; 244 } 245 246 private int convertConnectedToState(boolean connected) { 247 if (connected) { 248 return INPUT_STATE_CONNECTED; 249 } else { 250 return INPUT_STATE_DISCONNECTED; 251 } 252 } 253 254 public void addHardwareTvInput(int deviceId, TvInputInfo info) { 255 synchronized (mLock) { 256 String oldInputId = mHardwareInputIdMap.get(deviceId); 257 if (oldInputId != null) { 258 Slog.w(TAG, "Trying to override previous registration: old = " 259 + mInputMap.get(oldInputId) + ":" + deviceId + ", new = " 260 + info + ":" + deviceId); 261 } 262 mHardwareInputIdMap.put(deviceId, info.getId()); 263 mInputMap.put(info.getId(), info); 264 265 // Process pending state changes 266 267 // For logical HDMI devices, they have information from HDMI CEC signals. 268 for (int i = 0; i < mHdmiStateMap.size(); ++i) { 269 TvInputHardwareInfo hardwareInfo = 270 findHardwareInfoForHdmiPortLocked(mHdmiStateMap.keyAt(i)); 271 if (hardwareInfo == null) { 272 continue; 273 } 274 String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId()); 275 if (inputId != null && inputId.equals(info.getId())) { 276 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, 277 convertConnectedToState(mHdmiStateMap.valueAt(i)), 0, 278 inputId).sendToTarget(); 279 return; 280 } 281 } 282 // For the rest of the devices, we can tell by the number of available streams. 283 Connection connection = mConnections.get(deviceId); 284 if (connection != null) { 285 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, 286 convertConnectedToState(connection.getConfigsLocked().length > 0), 0, 287 info.getId()).sendToTarget(); 288 return; 289 } 290 } 291 } 292 293 private static <T> int indexOfEqualValue(SparseArray<T> map, T value) { 294 for (int i = 0; i < map.size(); ++i) { 295 if (map.valueAt(i).equals(value)) { 296 return i; 297 } 298 } 299 return -1; 300 } 301 302 public void addHdmiTvInput(int id, TvInputInfo info) { 303 if (info.getType() != TvInputInfo.TYPE_HDMI) { 304 throw new IllegalArgumentException("info (" + info + ") has non-HDMI type."); 305 } 306 synchronized (mLock) { 307 String parentId = info.getParentId(); 308 int parentIndex = indexOfEqualValue(mHardwareInputIdMap, parentId); 309 if (parentIndex < 0) { 310 throw new IllegalArgumentException("info (" + info + ") has invalid parentId."); 311 } 312 String oldInputId = mHdmiInputIdMap.get(id); 313 if (oldInputId != null) { 314 Slog.w(TAG, "Trying to override previous registration: old = " 315 + mInputMap.get(oldInputId) + ":" + id + ", new = " 316 + info + ":" + id); 317 } 318 mHdmiInputIdMap.put(id, info.getId()); 319 mInputMap.put(info.getId(), info); 320 } 321 } 322 323 public void removeTvInput(String inputId) { 324 synchronized (mLock) { 325 mInputMap.remove(inputId); 326 int hardwareIndex = indexOfEqualValue(mHardwareInputIdMap, inputId); 327 if (hardwareIndex >= 0) { 328 mHardwareInputIdMap.removeAt(hardwareIndex); 329 } 330 int deviceIndex = indexOfEqualValue(mHdmiInputIdMap, inputId); 331 if (deviceIndex >= 0) { 332 mHdmiInputIdMap.removeAt(deviceIndex); 333 } 334 } 335 } 336 337 /** 338 * Create a TvInputHardware object with a specific deviceId. One service at a time can access 339 * the object, and if more than one process attempts to create hardware with the same deviceId, 340 * the latest service will get the object and all the other hardware are released. The 341 * release is notified via ITvInputHardwareCallback.onReleased(). 342 */ 343 public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback, 344 TvInputInfo info, int callingUid, int resolvedUserId) { 345 if (callback == null) { 346 throw new NullPointerException(); 347 } 348 synchronized (mLock) { 349 Connection connection = mConnections.get(deviceId); 350 if (connection == null) { 351 Slog.e(TAG, "Invalid deviceId : " + deviceId); 352 return null; 353 } 354 if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) { 355 TvInputHardwareImpl hardware = 356 new TvInputHardwareImpl(connection.getHardwareInfoLocked()); 357 try { 358 callback.asBinder().linkToDeath(connection, 0); 359 } catch (RemoteException e) { 360 hardware.release(); 361 return null; 362 } 363 connection.resetLocked(hardware, callback, info, callingUid, resolvedUserId); 364 } 365 return connection.getHardwareLocked(); 366 } 367 } 368 369 /** 370 * Release the specified hardware. 371 */ 372 public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid, 373 int resolvedUserId) { 374 synchronized (mLock) { 375 Connection connection = mConnections.get(deviceId); 376 if (connection == null) { 377 Slog.e(TAG, "Invalid deviceId : " + deviceId); 378 return; 379 } 380 if (connection.getHardwareLocked() != hardware 381 || checkUidChangedLocked(connection, callingUid, resolvedUserId)) { 382 return; 383 } 384 connection.resetLocked(null, null, null, null, null); 385 } 386 } 387 388 private TvInputHardwareInfo findHardwareInfoForHdmiPortLocked(int port) { 389 for (TvInputHardwareInfo hardwareInfo : mHardwareList) { 390 if (hardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI 391 && hardwareInfo.getHdmiPortId() == port) { 392 return hardwareInfo; 393 } 394 } 395 return null; 396 } 397 398 private int findDeviceIdForInputIdLocked(String inputId) { 399 for (int i = 0; i < mConnections.size(); ++i) { 400 Connection connection = mConnections.get(i); 401 if (connection.getInfoLocked().getId().equals(inputId)) { 402 return i; 403 } 404 } 405 return -1; 406 } 407 408 /** 409 * Get the list of TvStreamConfig which is buffered mode. 410 */ 411 public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int callingUid, 412 int resolvedUserId) { 413 List<TvStreamConfig> configsList = new ArrayList<TvStreamConfig>(); 414 synchronized (mLock) { 415 int deviceId = findDeviceIdForInputIdLocked(inputId); 416 if (deviceId < 0) { 417 Slog.e(TAG, "Invalid inputId : " + inputId); 418 return configsList; 419 } 420 Connection connection = mConnections.get(deviceId); 421 for (TvStreamConfig config : connection.getConfigsLocked()) { 422 if (config.getType() == TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) { 423 configsList.add(config); 424 } 425 } 426 } 427 return configsList; 428 } 429 430 /** 431 * Take a snapshot of the given TV input into the provided Surface. 432 */ 433 public boolean captureFrame(String inputId, Surface surface, final TvStreamConfig config, 434 int callingUid, int resolvedUserId) { 435 synchronized (mLock) { 436 int deviceId = findDeviceIdForInputIdLocked(inputId); 437 if (deviceId < 0) { 438 Slog.e(TAG, "Invalid inputId : " + inputId); 439 return false; 440 } 441 Connection connection = mConnections.get(deviceId); 442 final TvInputHardwareImpl hardwareImpl = connection.getHardwareImplLocked(); 443 if (hardwareImpl != null) { 444 // Stop previous capture. 445 Runnable runnable = connection.getOnFirstFrameCapturedLocked(); 446 if (runnable != null) { 447 runnable.run(); 448 connection.setOnFirstFrameCapturedLocked(null); 449 } 450 451 boolean result = hardwareImpl.startCapture(surface, config); 452 if (result) { 453 connection.setOnFirstFrameCapturedLocked(new Runnable() { 454 @Override 455 public void run() { 456 hardwareImpl.stopCapture(config); 457 } 458 }); 459 } 460 return result; 461 } 462 } 463 return false; 464 } 465 466 private void processPendingHdmiDeviceEventsLocked() { 467 for (Iterator<Message> it = mPendingHdmiDeviceEvents.iterator(); it.hasNext(); ) { 468 Message msg = it.next(); 469 HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj; 470 TvInputHardwareInfo hardwareInfo = 471 findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId()); 472 if (hardwareInfo != null) { 473 msg.sendToTarget(); 474 it.remove(); 475 } 476 } 477 } 478 479 private class Connection implements IBinder.DeathRecipient { 480 private final TvInputHardwareInfo mHardwareInfo; 481 private TvInputInfo mInfo; 482 private TvInputHardwareImpl mHardware = null; 483 private ITvInputHardwareCallback mCallback; 484 private TvStreamConfig[] mConfigs = null; 485 private Integer mCallingUid = null; 486 private Integer mResolvedUserId = null; 487 private Runnable mOnFirstFrameCaptured; 488 489 public Connection(TvInputHardwareInfo hardwareInfo) { 490 mHardwareInfo = hardwareInfo; 491 } 492 493 // *Locked methods assume TvInputHardwareManager.mLock is held. 494 495 public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback, 496 TvInputInfo info, Integer callingUid, Integer resolvedUserId) { 497 if (mHardware != null) { 498 try { 499 mCallback.onReleased(); 500 } catch (RemoteException e) { 501 Slog.e(TAG, "error in Connection::resetLocked", e); 502 } 503 mHardware.release(); 504 } 505 mHardware = hardware; 506 mCallback = callback; 507 mInfo = info; 508 mCallingUid = callingUid; 509 mResolvedUserId = resolvedUserId; 510 mOnFirstFrameCaptured = null; 511 512 if (mHardware != null && mCallback != null) { 513 try { 514 mCallback.onStreamConfigChanged(getConfigsLocked()); 515 } catch (RemoteException e) { 516 Slog.e(TAG, "error in Connection::resetLocked", e); 517 } 518 } 519 } 520 521 public void updateConfigsLocked(TvStreamConfig[] configs) { 522 mConfigs = configs; 523 } 524 525 public TvInputHardwareInfo getHardwareInfoLocked() { 526 return mHardwareInfo; 527 } 528 529 public TvInputInfo getInfoLocked() { 530 return mInfo; 531 } 532 533 public ITvInputHardware getHardwareLocked() { 534 return mHardware; 535 } 536 537 public TvInputHardwareImpl getHardwareImplLocked() { 538 return mHardware; 539 } 540 541 public ITvInputHardwareCallback getCallbackLocked() { 542 return mCallback; 543 } 544 545 public TvStreamConfig[] getConfigsLocked() { 546 return mConfigs; 547 } 548 549 public Integer getCallingUidLocked() { 550 return mCallingUid; 551 } 552 553 public Integer getResolvedUserIdLocked() { 554 return mResolvedUserId; 555 } 556 557 public void setOnFirstFrameCapturedLocked(Runnable runnable) { 558 mOnFirstFrameCaptured = runnable; 559 } 560 561 public Runnable getOnFirstFrameCapturedLocked() { 562 return mOnFirstFrameCaptured; 563 } 564 565 @Override 566 public void binderDied() { 567 synchronized (mLock) { 568 resetLocked(null, null, null, null, null); 569 } 570 } 571 } 572 573 private class TvInputHardwareImpl extends ITvInputHardware.Stub { 574 private final TvInputHardwareInfo mInfo; 575 private boolean mReleased = false; 576 private final Object mImplLock = new Object(); 577 578 private final AudioManager.OnAudioPortUpdateListener mAudioListener = 579 new AudioManager.OnAudioPortUpdateListener() { 580 @Override 581 public void onAudioPortListUpdate(AudioPort[] portList) { 582 synchronized (mImplLock) { 583 updateAudioConfigLocked(); 584 } 585 } 586 587 @Override 588 public void onAudioPatchListUpdate(AudioPatch[] patchList) { 589 // No-op 590 } 591 592 @Override 593 public void onServiceDied() { 594 synchronized (mImplLock) { 595 mAudioSource = null; 596 mAudioSink = null; 597 mAudioPatch = null; 598 } 599 } 600 }; 601 private int mOverrideAudioType = AudioManager.DEVICE_NONE; 602 private String mOverrideAudioAddress = ""; 603 private AudioDevicePort mAudioSource; 604 private AudioDevicePort mAudioSink; 605 private AudioPatch mAudioPatch = null; 606 private float mCommittedVolume = 0.0f; 607 private float mVolume = 0.0f; 608 609 private TvStreamConfig mActiveConfig = null; 610 611 private int mDesiredSamplingRate = 0; 612 private int mDesiredChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT; 613 private int mDesiredFormat = AudioFormat.ENCODING_DEFAULT; 614 615 public TvInputHardwareImpl(TvInputHardwareInfo info) { 616 mInfo = info; 617 mAudioManager.registerAudioPortUpdateListener(mAudioListener); 618 if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) { 619 mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress()); 620 mAudioSink = findAudioSinkFromAudioPolicy(); 621 } 622 } 623 624 private AudioDevicePort findAudioSinkFromAudioPolicy() { 625 ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>(); 626 if (mAudioManager.listAudioDevicePorts(devicePorts) == AudioManager.SUCCESS) { 627 int sinkDevice = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC); 628 for (AudioPort port : devicePorts) { 629 AudioDevicePort devicePort = (AudioDevicePort) port; 630 if ((devicePort.type() & sinkDevice) != 0) { 631 return devicePort; 632 } 633 } 634 } 635 return null; 636 } 637 638 private AudioDevicePort findAudioDevicePort(int type, String address) { 639 if (type == AudioManager.DEVICE_NONE) { 640 return null; 641 } 642 ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>(); 643 if (mAudioManager.listAudioDevicePorts(devicePorts) != AudioManager.SUCCESS) { 644 return null; 645 } 646 for (AudioPort port : devicePorts) { 647 AudioDevicePort devicePort = (AudioDevicePort) port; 648 if (devicePort.type() == type && devicePort.address().equals(address)) { 649 return devicePort; 650 } 651 } 652 return null; 653 } 654 655 public void release() { 656 synchronized (mImplLock) { 657 mAudioManager.unregisterAudioPortUpdateListener(mAudioListener); 658 if (mAudioPatch != null) { 659 mAudioManager.releaseAudioPatch(mAudioPatch); 660 mAudioPatch = null; 661 } 662 mReleased = true; 663 } 664 } 665 666 // A TvInputHardwareImpl object holds only one active session. Therefore, if a client 667 // attempts to call setSurface with different TvStreamConfig objects, the last call will 668 // prevail. 669 @Override 670 public boolean setSurface(Surface surface, TvStreamConfig config) 671 throws RemoteException { 672 synchronized (mImplLock) { 673 if (mReleased) { 674 throw new IllegalStateException("Device already released."); 675 } 676 677 int result = TvInputHal.ERROR_UNKNOWN; 678 if (surface == null) { 679 // The value of config is ignored when surface == null. 680 if (mActiveConfig != null) { 681 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig); 682 mActiveConfig = null; 683 } else { 684 // We already have no active stream. 685 return true; 686 } 687 } else { 688 // It's impossible to set a non-null surface with a null config. 689 if (config == null) { 690 return false; 691 } 692 // Remove stream only if we have an existing active configuration. 693 if (mActiveConfig != null && !config.equals(mActiveConfig)) { 694 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig); 695 if (result != TvInputHal.SUCCESS) { 696 mActiveConfig = null; 697 } 698 } 699 // Proceed only if all previous operations succeeded. 700 if (result == TvInputHal.SUCCESS) { 701 result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config); 702 if (result == TvInputHal.SUCCESS) { 703 mActiveConfig = config; 704 } 705 } 706 } 707 updateAudioConfigLocked(); 708 return result == TvInputHal.SUCCESS; 709 } 710 } 711 712 /** 713 * Update audio configuration (source, sink, patch) all up to current state. 714 */ 715 private void updateAudioConfigLocked() { 716 boolean sinkUpdated = updateAudioSinkLocked(); 717 boolean sourceUpdated = updateAudioSourceLocked(); 718 // We can't do updated = updateAudioSinkLocked() || updateAudioSourceLocked() here 719 // because Java won't evaluate the latter if the former is true. 720 721 if (mAudioSource == null || mAudioSink == null || mActiveConfig == null) { 722 if (mAudioPatch != null) { 723 mAudioManager.releaseAudioPatch(mAudioPatch); 724 mAudioPatch = null; 725 } 726 return; 727 } 728 729 AudioGainConfig sourceGainConfig = null; 730 if (mAudioSource.gains().length > 0 && mVolume != mCommittedVolume) { 731 AudioGain sourceGain = null; 732 for (AudioGain gain : mAudioSource.gains()) { 733 if ((gain.mode() & AudioGain.MODE_JOINT) != 0) { 734 sourceGain = gain; 735 break; 736 } 737 } 738 // NOTE: we only change the source gain in MODE_JOINT here. 739 if (sourceGain != null) { 740 int steps = (sourceGain.maxValue() - sourceGain.minValue()) 741 / sourceGain.stepValue(); 742 int gainValue = sourceGain.minValue(); 743 if (mVolume < 1.0f) { 744 gainValue += sourceGain.stepValue() * (int) (mVolume * steps + 0.5); 745 } else { 746 gainValue = sourceGain.maxValue(); 747 } 748 int numChannels = 0; 749 for (int mask = sourceGain.channelMask(); mask > 0; mask >>= 1) { 750 numChannels += (mask & 1); 751 } 752 int[] gainValues = new int[numChannels]; 753 Arrays.fill(gainValues, gainValue); 754 sourceGainConfig = sourceGain.buildConfig(AudioGain.MODE_JOINT, 755 sourceGain.channelMask(), gainValues, 0); 756 } else { 757 Slog.w(TAG, "No audio source gain with MODE_JOINT support exists."); 758 } 759 } 760 761 AudioPortConfig sourceConfig = mAudioSource.activeConfig(); 762 AudioPortConfig sinkConfig = mAudioSink.activeConfig(); 763 AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch }; 764 boolean shouldRecreateAudioPatch = sourceUpdated || sinkUpdated; 765 if (sinkConfig == null 766 || (mDesiredSamplingRate != 0 767 && sinkConfig.samplingRate() != mDesiredSamplingRate) 768 || (mDesiredChannelMask != AudioFormat.CHANNEL_OUT_DEFAULT 769 && sinkConfig.channelMask() != mDesiredChannelMask) 770 || (mDesiredFormat != AudioFormat.ENCODING_DEFAULT 771 && sinkConfig.format() != mDesiredFormat)) { 772 sinkConfig = mAudioSink.buildConfig(mDesiredSamplingRate, mDesiredChannelMask, 773 mDesiredFormat, null); 774 shouldRecreateAudioPatch = true; 775 } 776 if (sourceConfig == null || sourceGainConfig != null) { 777 sourceConfig = mAudioSource.buildConfig(sinkConfig.samplingRate(), 778 sinkConfig.channelMask(), sinkConfig.format(), sourceGainConfig); 779 shouldRecreateAudioPatch = true; 780 } 781 if (shouldRecreateAudioPatch) { 782 mCommittedVolume = mVolume; 783 mAudioManager.createAudioPatch( 784 audioPatchArray, 785 new AudioPortConfig[] { sourceConfig }, 786 new AudioPortConfig[] { sinkConfig }); 787 mAudioPatch = audioPatchArray[0]; 788 } 789 } 790 791 @Override 792 public void setStreamVolume(float volume) throws RemoteException { 793 synchronized (mImplLock) { 794 if (mReleased) { 795 throw new IllegalStateException("Device already released."); 796 } 797 mVolume = volume; 798 updateAudioConfigLocked(); 799 } 800 } 801 802 @Override 803 public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException { 804 synchronized (mImplLock) { 805 if (mReleased) { 806 throw new IllegalStateException("Device already released."); 807 } 808 } 809 if (mInfo.getType() != TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) { 810 return false; 811 } 812 // TODO(hdmi): mHdmiClient.sendKeyEvent(event); 813 return false; 814 } 815 816 private boolean startCapture(Surface surface, TvStreamConfig config) { 817 synchronized (mImplLock) { 818 if (mReleased) { 819 return false; 820 } 821 if (surface == null || config == null) { 822 return false; 823 } 824 if (config.getType() != TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) { 825 return false; 826 } 827 828 int result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config); 829 return result == TvInputHal.SUCCESS; 830 } 831 } 832 833 private boolean stopCapture(TvStreamConfig config) { 834 synchronized (mImplLock) { 835 if (mReleased) { 836 return false; 837 } 838 if (config == null) { 839 return false; 840 } 841 842 int result = mHal.removeStream(mInfo.getDeviceId(), config); 843 return result == TvInputHal.SUCCESS; 844 } 845 } 846 847 private boolean updateAudioSourceLocked() { 848 if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) { 849 return false; 850 } 851 AudioDevicePort previousSource = mAudioSource; 852 mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress()); 853 return mAudioSource == null ? (previousSource != null) 854 : !mAudioSource.equals(previousSource); 855 } 856 857 private boolean updateAudioSinkLocked() { 858 if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) { 859 return false; 860 } 861 AudioDevicePort previousSink = mAudioSink; 862 if (mOverrideAudioType == AudioManager.DEVICE_NONE) { 863 mAudioSink = findAudioSinkFromAudioPolicy(); 864 } else { 865 AudioDevicePort audioSink = 866 findAudioDevicePort(mOverrideAudioType, mOverrideAudioAddress); 867 if (audioSink != null) { 868 mAudioSink = audioSink; 869 } 870 } 871 return mAudioSink == null ? (previousSink != null) : !mAudioSink.equals(previousSink); 872 } 873 874 private void handleAudioSinkUpdated() { 875 synchronized (mImplLock) { 876 updateAudioConfigLocked(); 877 } 878 } 879 880 @Override 881 public void overrideAudioSink(int audioType, String audioAddress, int samplingRate, 882 int channelMask, int format) { 883 synchronized (mImplLock) { 884 mOverrideAudioType = audioType; 885 mOverrideAudioAddress = audioAddress; 886 887 mDesiredSamplingRate = samplingRate; 888 mDesiredChannelMask = channelMask; 889 mDesiredFormat = format; 890 891 updateAudioConfigLocked(); 892 } 893 } 894 } 895 896 interface Listener { 897 public void onStateChanged(String inputId, int state); 898 public void onHardwareDeviceAdded(TvInputHardwareInfo info); 899 public void onHardwareDeviceRemoved(TvInputHardwareInfo info); 900 public void onHdmiDeviceAdded(HdmiDeviceInfo device); 901 public void onHdmiDeviceRemoved(HdmiDeviceInfo device); 902 public void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo device); 903 } 904 905 private class ListenerHandler extends Handler { 906 private static final int STATE_CHANGED = 1; 907 private static final int HARDWARE_DEVICE_ADDED = 2; 908 private static final int HARDWARE_DEVICE_REMOVED = 3; 909 private static final int HDMI_DEVICE_ADDED = 4; 910 private static final int HDMI_DEVICE_REMOVED = 5; 911 private static final int HDMI_DEVICE_UPDATED = 6; 912 913 @Override 914 public final void handleMessage(Message msg) { 915 switch (msg.what) { 916 case STATE_CHANGED: { 917 String inputId = (String) msg.obj; 918 int state = msg.arg1; 919 mListener.onStateChanged(inputId, state); 920 break; 921 } 922 case HARDWARE_DEVICE_ADDED: { 923 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj; 924 mListener.onHardwareDeviceAdded(info); 925 break; 926 } 927 case HARDWARE_DEVICE_REMOVED: { 928 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj; 929 mListener.onHardwareDeviceRemoved(info); 930 break; 931 } 932 case HDMI_DEVICE_ADDED: { 933 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj; 934 mListener.onHdmiDeviceAdded(info); 935 break; 936 } 937 case HDMI_DEVICE_REMOVED: { 938 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj; 939 mListener.onHdmiDeviceRemoved(info); 940 break; 941 } 942 case HDMI_DEVICE_UPDATED: { 943 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj; 944 String inputId = null; 945 synchronized (mLock) { 946 inputId = mHdmiInputIdMap.get(info.getId()); 947 } 948 if (inputId != null) { 949 mListener.onHdmiDeviceUpdated(inputId, info); 950 } else { 951 Slog.w(TAG, "Could not resolve input ID matching the device info; " 952 + "ignoring."); 953 } 954 break; 955 } 956 default: { 957 Slog.w(TAG, "Unhandled message: " + msg); 958 break; 959 } 960 } 961 } 962 } 963 964 // Listener implementations for HdmiControlService 965 966 private final class HdmiHotplugEventListener extends IHdmiHotplugEventListener.Stub { 967 @Override 968 public void onReceived(HdmiHotplugEvent event) { 969 synchronized (mLock) { 970 mHdmiStateMap.put(event.getPort(), event.isConnected()); 971 TvInputHardwareInfo hardwareInfo = 972 findHardwareInfoForHdmiPortLocked(event.getPort()); 973 if (hardwareInfo == null) { 974 return; 975 } 976 String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId()); 977 if (inputId == null) { 978 return; 979 } 980 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, 981 convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget(); 982 } 983 } 984 } 985 986 private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub { 987 @Override 988 public void onStatusChanged(HdmiDeviceInfo deviceInfo, int status) { 989 synchronized (mLock) { 990 int messageType = 0; 991 Object obj = null; 992 switch (status) { 993 case HdmiControlManager.DEVICE_EVENT_ADD_DEVICE: { 994 if (findHdmiDeviceInfo(deviceInfo.getId()) == null) { 995 mHdmiDeviceList.add(deviceInfo); 996 } else { 997 Slog.w(TAG, "The list already contains " + deviceInfo + "; ignoring."); 998 return; 999 } 1000 messageType = ListenerHandler.HDMI_DEVICE_ADDED; 1001 obj = deviceInfo; 1002 break; 1003 } 1004 case HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE: { 1005 HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId()); 1006 if (!mHdmiDeviceList.remove(originalDeviceInfo)) { 1007 Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring."); 1008 return; 1009 } 1010 messageType = ListenerHandler.HDMI_DEVICE_REMOVED; 1011 obj = deviceInfo; 1012 break; 1013 } 1014 case HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE: { 1015 HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId()); 1016 if (!mHdmiDeviceList.remove(originalDeviceInfo)) { 1017 Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring."); 1018 return; 1019 } 1020 mHdmiDeviceList.add(deviceInfo); 1021 messageType = ListenerHandler.HDMI_DEVICE_UPDATED; 1022 obj = deviceInfo; 1023 break; 1024 } 1025 } 1026 1027 Message msg = mHandler.obtainMessage(messageType, 0, 0, obj); 1028 if (findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId()) != null) { 1029 msg.sendToTarget(); 1030 } else { 1031 mPendingHdmiDeviceEvents.add(msg); 1032 } 1033 } 1034 } 1035 1036 private HdmiDeviceInfo findHdmiDeviceInfo(int id) { 1037 for (HdmiDeviceInfo info : mHdmiDeviceList) { 1038 if (info.getId() == id) { 1039 return info; 1040 } 1041 } 1042 return null; 1043 } 1044 } 1045 1046 private final class HdmiSystemAudioModeChangeListener extends 1047 IHdmiSystemAudioModeChangeListener.Stub { 1048 @Override 1049 public void onStatusChanged(boolean enabled) throws RemoteException { 1050 synchronized (mLock) { 1051 for (int i = 0; i < mConnections.size(); ++i) { 1052 TvInputHardwareImpl impl = mConnections.valueAt(i).getHardwareImplLocked(); 1053 if (impl != null) { 1054 impl.handleAudioSinkUpdated(); 1055 } 1056 } 1057 } 1058 } 1059 } 1060} 1061