TvInputHardwareManager.java revision 71dfa96f536e2ee6fbd4c5b0e517afcc1086d1fb
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 private static boolean intArrayContains(int[] array, int value) { 303 for (int element : array) { 304 if (element == value) return true; 305 } 306 return false; 307 } 308 309 public void addHdmiTvInput(int id, TvInputInfo info) { 310 if (info.getType() != TvInputInfo.TYPE_HDMI) { 311 throw new IllegalArgumentException("info (" + info + ") has non-HDMI type."); 312 } 313 synchronized (mLock) { 314 String parentId = info.getParentId(); 315 int parentIndex = indexOfEqualValue(mHardwareInputIdMap, parentId); 316 if (parentIndex < 0) { 317 throw new IllegalArgumentException("info (" + info + ") has invalid parentId."); 318 } 319 String oldInputId = mHdmiInputIdMap.get(id); 320 if (oldInputId != null) { 321 Slog.w(TAG, "Trying to override previous registration: old = " 322 + mInputMap.get(oldInputId) + ":" + id + ", new = " 323 + info + ":" + id); 324 } 325 mHdmiInputIdMap.put(id, info.getId()); 326 mInputMap.put(info.getId(), info); 327 } 328 } 329 330 public void removeTvInput(String inputId) { 331 synchronized (mLock) { 332 mInputMap.remove(inputId); 333 int hardwareIndex = indexOfEqualValue(mHardwareInputIdMap, inputId); 334 if (hardwareIndex >= 0) { 335 mHardwareInputIdMap.removeAt(hardwareIndex); 336 } 337 int deviceIndex = indexOfEqualValue(mHdmiInputIdMap, inputId); 338 if (deviceIndex >= 0) { 339 mHdmiInputIdMap.removeAt(deviceIndex); 340 } 341 } 342 } 343 344 /** 345 * Create a TvInputHardware object with a specific deviceId. One service at a time can access 346 * the object, and if more than one process attempts to create hardware with the same deviceId, 347 * the latest service will get the object and all the other hardware are released. The 348 * release is notified via ITvInputHardwareCallback.onReleased(). 349 */ 350 public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback, 351 TvInputInfo info, int callingUid, int resolvedUserId) { 352 if (callback == null) { 353 throw new NullPointerException(); 354 } 355 synchronized (mLock) { 356 Connection connection = mConnections.get(deviceId); 357 if (connection == null) { 358 Slog.e(TAG, "Invalid deviceId : " + deviceId); 359 return null; 360 } 361 if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) { 362 TvInputHardwareImpl hardware = 363 new TvInputHardwareImpl(connection.getHardwareInfoLocked()); 364 try { 365 callback.asBinder().linkToDeath(connection, 0); 366 } catch (RemoteException e) { 367 hardware.release(); 368 return null; 369 } 370 connection.resetLocked(hardware, callback, info, callingUid, resolvedUserId); 371 } 372 return connection.getHardwareLocked(); 373 } 374 } 375 376 /** 377 * Release the specified hardware. 378 */ 379 public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid, 380 int resolvedUserId) { 381 synchronized (mLock) { 382 Connection connection = mConnections.get(deviceId); 383 if (connection == null) { 384 Slog.e(TAG, "Invalid deviceId : " + deviceId); 385 return; 386 } 387 if (connection.getHardwareLocked() != hardware 388 || checkUidChangedLocked(connection, callingUid, resolvedUserId)) { 389 return; 390 } 391 connection.resetLocked(null, null, null, null, null); 392 } 393 } 394 395 private TvInputHardwareInfo findHardwareInfoForHdmiPortLocked(int port) { 396 for (TvInputHardwareInfo hardwareInfo : mHardwareList) { 397 if (hardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI 398 && hardwareInfo.getHdmiPortId() == port) { 399 return hardwareInfo; 400 } 401 } 402 return null; 403 } 404 405 private int findDeviceIdForInputIdLocked(String inputId) { 406 for (int i = 0; i < mConnections.size(); ++i) { 407 Connection connection = mConnections.get(i); 408 if (connection.getInfoLocked().getId().equals(inputId)) { 409 return i; 410 } 411 } 412 return -1; 413 } 414 415 /** 416 * Get the list of TvStreamConfig which is buffered mode. 417 */ 418 public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int callingUid, 419 int resolvedUserId) { 420 List<TvStreamConfig> configsList = new ArrayList<TvStreamConfig>(); 421 synchronized (mLock) { 422 int deviceId = findDeviceIdForInputIdLocked(inputId); 423 if (deviceId < 0) { 424 Slog.e(TAG, "Invalid inputId : " + inputId); 425 return configsList; 426 } 427 Connection connection = mConnections.get(deviceId); 428 for (TvStreamConfig config : connection.getConfigsLocked()) { 429 if (config.getType() == TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) { 430 configsList.add(config); 431 } 432 } 433 } 434 return configsList; 435 } 436 437 /** 438 * Take a snapshot of the given TV input into the provided Surface. 439 */ 440 public boolean captureFrame(String inputId, Surface surface, final TvStreamConfig config, 441 int callingUid, int resolvedUserId) { 442 synchronized (mLock) { 443 int deviceId = findDeviceIdForInputIdLocked(inputId); 444 if (deviceId < 0) { 445 Slog.e(TAG, "Invalid inputId : " + inputId); 446 return false; 447 } 448 Connection connection = mConnections.get(deviceId); 449 final TvInputHardwareImpl hardwareImpl = connection.getHardwareImplLocked(); 450 if (hardwareImpl != null) { 451 // Stop previous capture. 452 Runnable runnable = connection.getOnFirstFrameCapturedLocked(); 453 if (runnable != null) { 454 runnable.run(); 455 connection.setOnFirstFrameCapturedLocked(null); 456 } 457 458 boolean result = hardwareImpl.startCapture(surface, config); 459 if (result) { 460 connection.setOnFirstFrameCapturedLocked(new Runnable() { 461 @Override 462 public void run() { 463 hardwareImpl.stopCapture(config); 464 } 465 }); 466 } 467 return result; 468 } 469 } 470 return false; 471 } 472 473 private void processPendingHdmiDeviceEventsLocked() { 474 for (Iterator<Message> it = mPendingHdmiDeviceEvents.iterator(); it.hasNext(); ) { 475 Message msg = it.next(); 476 HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj; 477 TvInputHardwareInfo hardwareInfo = 478 findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId()); 479 if (hardwareInfo != null) { 480 msg.sendToTarget(); 481 it.remove(); 482 } 483 } 484 } 485 486 private class Connection implements IBinder.DeathRecipient { 487 private final TvInputHardwareInfo mHardwareInfo; 488 private TvInputInfo mInfo; 489 private TvInputHardwareImpl mHardware = null; 490 private ITvInputHardwareCallback mCallback; 491 private TvStreamConfig[] mConfigs = null; 492 private Integer mCallingUid = null; 493 private Integer mResolvedUserId = null; 494 private Runnable mOnFirstFrameCaptured; 495 496 public Connection(TvInputHardwareInfo hardwareInfo) { 497 mHardwareInfo = hardwareInfo; 498 } 499 500 // *Locked methods assume TvInputHardwareManager.mLock is held. 501 502 public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback, 503 TvInputInfo info, Integer callingUid, Integer resolvedUserId) { 504 if (mHardware != null) { 505 try { 506 mCallback.onReleased(); 507 } catch (RemoteException e) { 508 Slog.e(TAG, "error in Connection::resetLocked", e); 509 } 510 mHardware.release(); 511 } 512 mHardware = hardware; 513 mCallback = callback; 514 mInfo = info; 515 mCallingUid = callingUid; 516 mResolvedUserId = resolvedUserId; 517 mOnFirstFrameCaptured = null; 518 519 if (mHardware != null && mCallback != null) { 520 try { 521 mCallback.onStreamConfigChanged(getConfigsLocked()); 522 } catch (RemoteException e) { 523 Slog.e(TAG, "error in Connection::resetLocked", e); 524 } 525 } 526 } 527 528 public void updateConfigsLocked(TvStreamConfig[] configs) { 529 mConfigs = configs; 530 } 531 532 public TvInputHardwareInfo getHardwareInfoLocked() { 533 return mHardwareInfo; 534 } 535 536 public TvInputInfo getInfoLocked() { 537 return mInfo; 538 } 539 540 public ITvInputHardware getHardwareLocked() { 541 return mHardware; 542 } 543 544 public TvInputHardwareImpl getHardwareImplLocked() { 545 return mHardware; 546 } 547 548 public ITvInputHardwareCallback getCallbackLocked() { 549 return mCallback; 550 } 551 552 public TvStreamConfig[] getConfigsLocked() { 553 return mConfigs; 554 } 555 556 public Integer getCallingUidLocked() { 557 return mCallingUid; 558 } 559 560 public Integer getResolvedUserIdLocked() { 561 return mResolvedUserId; 562 } 563 564 public void setOnFirstFrameCapturedLocked(Runnable runnable) { 565 mOnFirstFrameCaptured = runnable; 566 } 567 568 public Runnable getOnFirstFrameCapturedLocked() { 569 return mOnFirstFrameCaptured; 570 } 571 572 @Override 573 public void binderDied() { 574 synchronized (mLock) { 575 resetLocked(null, null, null, null, null); 576 } 577 } 578 } 579 580 private class TvInputHardwareImpl extends ITvInputHardware.Stub { 581 private final TvInputHardwareInfo mInfo; 582 private boolean mReleased = false; 583 private final Object mImplLock = new Object(); 584 585 private final AudioManager.OnAudioPortUpdateListener mAudioListener = 586 new AudioManager.OnAudioPortUpdateListener() { 587 @Override 588 public void onAudioPortListUpdate(AudioPort[] portList) { 589 synchronized (mImplLock) { 590 updateAudioConfigLocked(); 591 } 592 } 593 594 @Override 595 public void onAudioPatchListUpdate(AudioPatch[] patchList) { 596 // No-op 597 } 598 599 @Override 600 public void onServiceDied() { 601 synchronized (mImplLock) { 602 mAudioSource = null; 603 mAudioSink = null; 604 mAudioPatch = null; 605 } 606 } 607 }; 608 private int mOverrideAudioType = AudioManager.DEVICE_NONE; 609 private String mOverrideAudioAddress = ""; 610 private AudioDevicePort mAudioSource; 611 private AudioDevicePort mAudioSink; 612 private AudioPatch mAudioPatch = null; 613 private float mCommittedVolume = 0.0f; 614 private float mVolume = 0.0f; 615 616 private TvStreamConfig mActiveConfig = null; 617 618 private int mDesiredSamplingRate = 0; 619 private int mDesiredChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT; 620 private int mDesiredFormat = AudioFormat.ENCODING_DEFAULT; 621 622 public TvInputHardwareImpl(TvInputHardwareInfo info) { 623 mInfo = info; 624 mAudioManager.registerAudioPortUpdateListener(mAudioListener); 625 if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) { 626 mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress()); 627 mAudioSink = findAudioSinkFromAudioPolicy(); 628 } 629 } 630 631 private AudioDevicePort findAudioSinkFromAudioPolicy() { 632 ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>(); 633 if (mAudioManager.listAudioDevicePorts(devicePorts) == AudioManager.SUCCESS) { 634 int sinkDevice = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC); 635 for (AudioPort port : devicePorts) { 636 AudioDevicePort devicePort = (AudioDevicePort) port; 637 if ((devicePort.type() & sinkDevice) != 0) { 638 return devicePort; 639 } 640 } 641 } 642 return null; 643 } 644 645 private AudioDevicePort findAudioDevicePort(int type, String address) { 646 if (type == AudioManager.DEVICE_NONE) { 647 return null; 648 } 649 ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>(); 650 if (mAudioManager.listAudioDevicePorts(devicePorts) != AudioManager.SUCCESS) { 651 return null; 652 } 653 for (AudioPort port : devicePorts) { 654 AudioDevicePort devicePort = (AudioDevicePort) port; 655 if (devicePort.type() == type && devicePort.address().equals(address)) { 656 return devicePort; 657 } 658 } 659 return null; 660 } 661 662 public void release() { 663 synchronized (mImplLock) { 664 mAudioManager.unregisterAudioPortUpdateListener(mAudioListener); 665 if (mAudioPatch != null) { 666 mAudioManager.releaseAudioPatch(mAudioPatch); 667 mAudioPatch = null; 668 } 669 mReleased = true; 670 } 671 } 672 673 // A TvInputHardwareImpl object holds only one active session. Therefore, if a client 674 // attempts to call setSurface with different TvStreamConfig objects, the last call will 675 // prevail. 676 @Override 677 public boolean setSurface(Surface surface, TvStreamConfig config) 678 throws RemoteException { 679 synchronized (mImplLock) { 680 if (mReleased) { 681 throw new IllegalStateException("Device already released."); 682 } 683 if (surface != null && config == null) { 684 return false; 685 } 686 if (surface == null && mActiveConfig == null) { 687 return false; 688 } 689 690 int result = TvInputHal.ERROR_UNKNOWN; 691 if (surface == null) { 692 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig); 693 mActiveConfig = null; 694 } else { 695 if (!config.equals(mActiveConfig)) { 696 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig); 697 if (result != TvInputHal.SUCCESS) { 698 mActiveConfig = null; 699 return false; 700 } 701 } 702 result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config); 703 if (result == TvInputHal.SUCCESS) { 704 mActiveConfig = config; 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 766 int sinkSamplingRate = mDesiredSamplingRate; 767 int sinkChannelMask = mDesiredChannelMask; 768 int sinkFormat = mDesiredFormat; 769 // If sinkConfig != null and values are set to default, fill in the sinkConfig values. 770 if (sinkConfig != null) { 771 if (sinkSamplingRate == 0) { 772 sinkSamplingRate = sinkConfig.samplingRate(); 773 } 774 if (sinkChannelMask == AudioFormat.CHANNEL_OUT_DEFAULT) { 775 sinkChannelMask = sinkConfig.channelMask(); 776 } 777 if (sinkFormat == AudioFormat.ENCODING_DEFAULT) { 778 sinkChannelMask = sinkConfig.format(); 779 } 780 } 781 782 if (sinkConfig == null 783 || sinkConfig.samplingRate() != sinkSamplingRate 784 || sinkConfig.channelMask() != sinkChannelMask 785 || sinkConfig.format() != sinkFormat) { 786 // Check for compatibility and reset to default if necessary. 787 if (!intArrayContains(mAudioSink.samplingRates(), sinkSamplingRate) 788 && mAudioSink.samplingRates().length > 0) { 789 sinkSamplingRate = mAudioSink.samplingRates()[0]; 790 } 791 if (!intArrayContains(mAudioSink.channelMasks(), sinkChannelMask)) { 792 sinkChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT; 793 } 794 if (!intArrayContains(mAudioSink.formats(), sinkFormat)) { 795 sinkFormat = AudioFormat.ENCODING_DEFAULT; 796 } 797 sinkConfig = mAudioSink.buildConfig(sinkSamplingRate, sinkChannelMask, 798 sinkFormat, null); 799 shouldRecreateAudioPatch = true; 800 } 801 if (sourceConfig == null || sourceGainConfig != null) { 802 int sourceSamplingRate = 0; 803 if (intArrayContains(mAudioSource.samplingRates(), sinkConfig.samplingRate())) { 804 sourceSamplingRate = sinkConfig.samplingRate(); 805 } else if (mAudioSource.samplingRates().length > 0) { 806 // Use any sampling rate and hope audio patch can handle resampling... 807 sourceSamplingRate = mAudioSource.samplingRates()[0]; 808 } 809 int sourceChannelMask = AudioFormat.CHANNEL_IN_DEFAULT; 810 for (int inChannelMask : mAudioSource.channelMasks()) { 811 if (AudioFormat.channelCountFromOutChannelMask(sinkConfig.channelMask()) 812 == AudioFormat.channelCountFromInChannelMask(inChannelMask)) { 813 sourceChannelMask = inChannelMask; 814 break; 815 } 816 } 817 int sourceFormat = AudioFormat.ENCODING_DEFAULT; 818 if (intArrayContains(mAudioSource.formats(), sinkConfig.format())) { 819 sourceFormat = sinkConfig.format(); 820 } 821 sourceConfig = mAudioSource.buildConfig(sourceSamplingRate, sourceChannelMask, 822 sourceFormat, sourceGainConfig); 823 shouldRecreateAudioPatch = true; 824 } 825 if (shouldRecreateAudioPatch) { 826 mCommittedVolume = mVolume; 827 mAudioManager.createAudioPatch( 828 audioPatchArray, 829 new AudioPortConfig[] { sourceConfig }, 830 new AudioPortConfig[] { sinkConfig }); 831 mAudioPatch = audioPatchArray[0]; 832 if (sourceGainConfig != null) { 833 mAudioManager.setAudioPortGain(mAudioSource, sourceGainConfig); 834 } 835 } 836 } 837 838 @Override 839 public void setStreamVolume(float volume) throws RemoteException { 840 synchronized (mImplLock) { 841 if (mReleased) { 842 throw new IllegalStateException("Device already released."); 843 } 844 mVolume = volume; 845 updateAudioConfigLocked(); 846 } 847 } 848 849 @Override 850 public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException { 851 synchronized (mImplLock) { 852 if (mReleased) { 853 throw new IllegalStateException("Device already released."); 854 } 855 } 856 if (mInfo.getType() != TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) { 857 return false; 858 } 859 // TODO(hdmi): mHdmiClient.sendKeyEvent(event); 860 return false; 861 } 862 863 private boolean startCapture(Surface surface, TvStreamConfig config) { 864 synchronized (mImplLock) { 865 if (mReleased) { 866 return false; 867 } 868 if (surface == null || config == null) { 869 return false; 870 } 871 if (config.getType() != TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) { 872 return false; 873 } 874 875 int result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config); 876 return result == TvInputHal.SUCCESS; 877 } 878 } 879 880 private boolean stopCapture(TvStreamConfig config) { 881 synchronized (mImplLock) { 882 if (mReleased) { 883 return false; 884 } 885 if (config == null) { 886 return false; 887 } 888 889 int result = mHal.removeStream(mInfo.getDeviceId(), config); 890 return result == TvInputHal.SUCCESS; 891 } 892 } 893 894 private boolean updateAudioSourceLocked() { 895 if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) { 896 return false; 897 } 898 AudioDevicePort previousSource = mAudioSource; 899 mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress()); 900 return mAudioSource == null ? (previousSource != null) 901 : !mAudioSource.equals(previousSource); 902 } 903 904 private boolean updateAudioSinkLocked() { 905 if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) { 906 return false; 907 } 908 AudioDevicePort previousSink = mAudioSink; 909 if (mOverrideAudioType == AudioManager.DEVICE_NONE) { 910 mAudioSink = findAudioSinkFromAudioPolicy(); 911 } else { 912 AudioDevicePort audioSink = 913 findAudioDevicePort(mOverrideAudioType, mOverrideAudioAddress); 914 if (audioSink != null) { 915 mAudioSink = audioSink; 916 } 917 } 918 return mAudioSink == null ? (previousSink != null) : !mAudioSink.equals(previousSink); 919 } 920 921 private void handleAudioSinkUpdated() { 922 synchronized (mImplLock) { 923 updateAudioConfigLocked(); 924 } 925 } 926 927 @Override 928 public void overrideAudioSink(int audioType, String audioAddress, int samplingRate, 929 int channelMask, int format) { 930 synchronized (mImplLock) { 931 mOverrideAudioType = audioType; 932 mOverrideAudioAddress = audioAddress; 933 934 mDesiredSamplingRate = samplingRate; 935 mDesiredChannelMask = channelMask; 936 mDesiredFormat = format; 937 938 updateAudioConfigLocked(); 939 } 940 } 941 } 942 943 interface Listener { 944 public void onStateChanged(String inputId, int state); 945 public void onHardwareDeviceAdded(TvInputHardwareInfo info); 946 public void onHardwareDeviceRemoved(TvInputHardwareInfo info); 947 public void onHdmiDeviceAdded(HdmiDeviceInfo device); 948 public void onHdmiDeviceRemoved(HdmiDeviceInfo device); 949 public void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo device); 950 } 951 952 private class ListenerHandler extends Handler { 953 private static final int STATE_CHANGED = 1; 954 private static final int HARDWARE_DEVICE_ADDED = 2; 955 private static final int HARDWARE_DEVICE_REMOVED = 3; 956 private static final int HDMI_DEVICE_ADDED = 4; 957 private static final int HDMI_DEVICE_REMOVED = 5; 958 private static final int HDMI_DEVICE_UPDATED = 6; 959 960 @Override 961 public final void handleMessage(Message msg) { 962 switch (msg.what) { 963 case STATE_CHANGED: { 964 String inputId = (String) msg.obj; 965 int state = msg.arg1; 966 mListener.onStateChanged(inputId, state); 967 break; 968 } 969 case HARDWARE_DEVICE_ADDED: { 970 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj; 971 mListener.onHardwareDeviceAdded(info); 972 break; 973 } 974 case HARDWARE_DEVICE_REMOVED: { 975 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj; 976 mListener.onHardwareDeviceRemoved(info); 977 break; 978 } 979 case HDMI_DEVICE_ADDED: { 980 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj; 981 mListener.onHdmiDeviceAdded(info); 982 break; 983 } 984 case HDMI_DEVICE_REMOVED: { 985 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj; 986 mListener.onHdmiDeviceRemoved(info); 987 break; 988 } 989 case HDMI_DEVICE_UPDATED: { 990 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj; 991 String inputId = null; 992 synchronized (mLock) { 993 inputId = mHdmiInputIdMap.get(info.getId()); 994 } 995 if (inputId != null) { 996 mListener.onHdmiDeviceUpdated(inputId, info); 997 } else { 998 Slog.w(TAG, "Could not resolve input ID matching the device info; " 999 + "ignoring."); 1000 } 1001 break; 1002 } 1003 default: { 1004 Slog.w(TAG, "Unhandled message: " + msg); 1005 break; 1006 } 1007 } 1008 } 1009 } 1010 1011 // Listener implementations for HdmiControlService 1012 1013 private final class HdmiHotplugEventListener extends IHdmiHotplugEventListener.Stub { 1014 @Override 1015 public void onReceived(HdmiHotplugEvent event) { 1016 synchronized (mLock) { 1017 mHdmiStateMap.put(event.getPort(), event.isConnected()); 1018 TvInputHardwareInfo hardwareInfo = 1019 findHardwareInfoForHdmiPortLocked(event.getPort()); 1020 if (hardwareInfo == null) { 1021 return; 1022 } 1023 String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId()); 1024 if (inputId == null) { 1025 return; 1026 } 1027 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, 1028 convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget(); 1029 } 1030 } 1031 } 1032 1033 private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub { 1034 @Override 1035 public void onStatusChanged(HdmiDeviceInfo deviceInfo, int status) { 1036 synchronized (mLock) { 1037 int messageType = 0; 1038 Object obj = null; 1039 switch (status) { 1040 case HdmiControlManager.DEVICE_EVENT_ADD_DEVICE: { 1041 if (findHdmiDeviceInfo(deviceInfo.getId()) == null) { 1042 mHdmiDeviceList.add(deviceInfo); 1043 } else { 1044 Slog.w(TAG, "The list already contains " + deviceInfo + "; ignoring."); 1045 return; 1046 } 1047 messageType = ListenerHandler.HDMI_DEVICE_ADDED; 1048 obj = deviceInfo; 1049 break; 1050 } 1051 case HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE: { 1052 HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId()); 1053 if (!mHdmiDeviceList.remove(originalDeviceInfo)) { 1054 Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring."); 1055 return; 1056 } 1057 messageType = ListenerHandler.HDMI_DEVICE_REMOVED; 1058 obj = deviceInfo; 1059 break; 1060 } 1061 case HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE: { 1062 HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId()); 1063 if (!mHdmiDeviceList.remove(originalDeviceInfo)) { 1064 Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring."); 1065 return; 1066 } 1067 mHdmiDeviceList.add(deviceInfo); 1068 messageType = ListenerHandler.HDMI_DEVICE_UPDATED; 1069 obj = deviceInfo; 1070 break; 1071 } 1072 } 1073 1074 Message msg = mHandler.obtainMessage(messageType, 0, 0, obj); 1075 if (findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId()) != null) { 1076 msg.sendToTarget(); 1077 } else { 1078 mPendingHdmiDeviceEvents.add(msg); 1079 } 1080 } 1081 } 1082 1083 private HdmiDeviceInfo findHdmiDeviceInfo(int id) { 1084 for (HdmiDeviceInfo info : mHdmiDeviceList) { 1085 if (info.getId() == id) { 1086 return info; 1087 } 1088 } 1089 return null; 1090 } 1091 } 1092 1093 private final class HdmiSystemAudioModeChangeListener extends 1094 IHdmiSystemAudioModeChangeListener.Stub { 1095 @Override 1096 public void onStatusChanged(boolean enabled) throws RemoteException { 1097 synchronized (mLock) { 1098 for (int i = 0; i < mConnections.size(); ++i) { 1099 TvInputHardwareImpl impl = mConnections.valueAt(i).getHardwareImplLocked(); 1100 if (impl != null) { 1101 impl.handleAudioSinkUpdated(); 1102 } 1103 } 1104 } 1105 } 1106 } 1107} 1108