TvInputHardwareManager.java revision 9a1036575182705bc27c6bf255dd17669821181d
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(config.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 if (surface != null && config == null) { 677 return false; 678 } 679 if (surface == null && mActiveConfig == null) { 680 return false; 681 } 682 683 int result = TvInputHal.ERROR_UNKNOWN; 684 if (surface == null) { 685 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig); 686 mActiveConfig = null; 687 } else { 688 if (config != mActiveConfig && mActiveConfig != null) { 689 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig); 690 if (result != TvInputHal.SUCCESS) { 691 mActiveConfig = null; 692 return false; 693 } 694 } 695 result = mHal.addStream(mInfo.getDeviceId(), surface, config); 696 if (result == TvInputHal.SUCCESS) { 697 mActiveConfig = config; 698 } 699 } 700 updateAudioConfigLocked(); 701 return result == TvInputHal.SUCCESS; 702 } 703 } 704 705 /** 706 * Update audio configuration (source, sink, patch) all up to current state. 707 */ 708 private void updateAudioConfigLocked() { 709 boolean sinkUpdated = updateAudioSinkLocked(); 710 boolean sourceUpdated = updateAudioSourceLocked(); 711 // We can't do updated = updateAudioSinkLocked() || updateAudioSourceLocked() here 712 // because Java won't evaluate the latter if the former is true. 713 714 if (mAudioSource == null || mAudioSink == null || mActiveConfig == null) { 715 if (mAudioPatch != null) { 716 mAudioManager.releaseAudioPatch(mAudioPatch); 717 mAudioPatch = null; 718 } 719 return; 720 } 721 722 AudioGainConfig sourceGainConfig = null; 723 if (mAudioSource.gains().length > 0 && mVolume != mCommittedVolume) { 724 AudioGain sourceGain = null; 725 for (AudioGain gain : mAudioSource.gains()) { 726 if ((gain.mode() & AudioGain.MODE_JOINT) != 0) { 727 sourceGain = gain; 728 break; 729 } 730 } 731 // NOTE: we only change the source gain in MODE_JOINT here. 732 if (sourceGain != null) { 733 int steps = (sourceGain.maxValue() - sourceGain.minValue()) 734 / sourceGain.stepValue(); 735 int gainValue = sourceGain.minValue(); 736 if (mVolume < 1.0f) { 737 gainValue += sourceGain.stepValue() * (int) (mVolume * steps + 0.5); 738 } else { 739 gainValue = sourceGain.maxValue(); 740 } 741 int numChannels = 0; 742 for (int mask = sourceGain.channelMask(); mask > 0; mask >>= 1) { 743 numChannels += (mask & 1); 744 } 745 int[] gainValues = new int[numChannels]; 746 Arrays.fill(gainValues, gainValue); 747 sourceGainConfig = sourceGain.buildConfig(AudioGain.MODE_JOINT, 748 sourceGain.channelMask(), gainValues, 0); 749 } else { 750 Slog.w(TAG, "No audio source gain with MODE_JOINT support exists."); 751 } 752 } 753 754 AudioPortConfig sourceConfig = mAudioSource.activeConfig(); 755 AudioPortConfig sinkConfig = mAudioSink.activeConfig(); 756 AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch }; 757 boolean shouldRecreateAudioPatch = sourceUpdated || sinkUpdated; 758 if (sinkConfig == null 759 || (mDesiredSamplingRate != 0 760 && sinkConfig.samplingRate() != mDesiredSamplingRate) 761 || (mDesiredChannelMask != AudioFormat.CHANNEL_OUT_DEFAULT 762 && sinkConfig.channelMask() != mDesiredChannelMask) 763 || (mDesiredFormat != AudioFormat.ENCODING_DEFAULT 764 && sinkConfig.format() != mDesiredFormat)) { 765 sinkConfig = mAudioSink.buildConfig(mDesiredSamplingRate, mDesiredChannelMask, 766 mDesiredFormat, null); 767 shouldRecreateAudioPatch = true; 768 } 769 if (sourceConfig == null || sourceGainConfig != null) { 770 sourceConfig = mAudioSource.buildConfig(sinkConfig.samplingRate(), 771 sinkConfig.channelMask(), sinkConfig.format(), sourceGainConfig); 772 shouldRecreateAudioPatch = true; 773 } 774 if (shouldRecreateAudioPatch) { 775 mCommittedVolume = mVolume; 776 mAudioManager.createAudioPatch( 777 audioPatchArray, 778 new AudioPortConfig[] { sourceConfig }, 779 new AudioPortConfig[] { sinkConfig }); 780 mAudioPatch = audioPatchArray[0]; 781 } 782 } 783 784 @Override 785 public void setStreamVolume(float volume) throws RemoteException { 786 synchronized (mImplLock) { 787 if (mReleased) { 788 throw new IllegalStateException("Device already released."); 789 } 790 mVolume = volume; 791 updateAudioConfigLocked(); 792 } 793 } 794 795 @Override 796 public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException { 797 synchronized (mImplLock) { 798 if (mReleased) { 799 throw new IllegalStateException("Device already released."); 800 } 801 } 802 if (mInfo.getType() != TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) { 803 return false; 804 } 805 // TODO(hdmi): mHdmiClient.sendKeyEvent(event); 806 return false; 807 } 808 809 private boolean startCapture(Surface surface, TvStreamConfig config) { 810 synchronized (mImplLock) { 811 if (mReleased) { 812 return false; 813 } 814 if (surface == null || config == null) { 815 return false; 816 } 817 if (config.getType() != TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) { 818 return false; 819 } 820 821 int result = mHal.addStream(mInfo.getDeviceId(), surface, config); 822 return result == TvInputHal.SUCCESS; 823 } 824 } 825 826 private boolean stopCapture(TvStreamConfig config) { 827 synchronized (mImplLock) { 828 if (mReleased) { 829 return false; 830 } 831 if (config == null) { 832 return false; 833 } 834 835 int result = mHal.removeStream(mInfo.getDeviceId(), config); 836 return result == TvInputHal.SUCCESS; 837 } 838 } 839 840 private boolean updateAudioSourceLocked() { 841 if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) { 842 return false; 843 } 844 AudioDevicePort previousSource = mAudioSource; 845 mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress()); 846 return mAudioSource == null ? (previousSource != null) 847 : !mAudioSource.equals(previousSource); 848 } 849 850 private boolean updateAudioSinkLocked() { 851 if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) { 852 return false; 853 } 854 AudioDevicePort previousSink = mAudioSink; 855 if (mOverrideAudioType == AudioManager.DEVICE_NONE) { 856 mAudioSink = findAudioSinkFromAudioPolicy(); 857 } else { 858 AudioDevicePort audioSink = 859 findAudioDevicePort(mOverrideAudioType, mOverrideAudioAddress); 860 if (audioSink != null) { 861 mAudioSink = audioSink; 862 } 863 } 864 return mAudioSink == null ? (previousSink != null) : !mAudioSink.equals(previousSink); 865 } 866 867 private void handleAudioSinkUpdated() { 868 synchronized (mImplLock) { 869 updateAudioConfigLocked(); 870 } 871 } 872 873 @Override 874 public void overrideAudioSink(int audioType, String audioAddress, int samplingRate, 875 int channelMask, int format) { 876 synchronized (mImplLock) { 877 mOverrideAudioType = audioType; 878 mOverrideAudioAddress = audioAddress; 879 880 mDesiredSamplingRate = samplingRate; 881 mDesiredChannelMask = channelMask; 882 mDesiredFormat = format; 883 884 updateAudioConfigLocked(); 885 } 886 } 887 } 888 889 interface Listener { 890 public void onStateChanged(String inputId, int state); 891 public void onHardwareDeviceAdded(TvInputHardwareInfo info); 892 public void onHardwareDeviceRemoved(TvInputHardwareInfo info); 893 public void onHdmiDeviceAdded(HdmiDeviceInfo device); 894 public void onHdmiDeviceRemoved(HdmiDeviceInfo device); 895 public void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo device); 896 } 897 898 private class ListenerHandler extends Handler { 899 private static final int STATE_CHANGED = 1; 900 private static final int HARDWARE_DEVICE_ADDED = 2; 901 private static final int HARDWARE_DEVICE_REMOVED = 3; 902 private static final int HDMI_DEVICE_ADDED = 4; 903 private static final int HDMI_DEVICE_REMOVED = 5; 904 private static final int HDMI_DEVICE_UPDATED = 6; 905 906 @Override 907 public final void handleMessage(Message msg) { 908 switch (msg.what) { 909 case STATE_CHANGED: { 910 String inputId = (String) msg.obj; 911 int state = msg.arg1; 912 mListener.onStateChanged(inputId, state); 913 break; 914 } 915 case HARDWARE_DEVICE_ADDED: { 916 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj; 917 mListener.onHardwareDeviceAdded(info); 918 break; 919 } 920 case HARDWARE_DEVICE_REMOVED: { 921 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj; 922 mListener.onHardwareDeviceRemoved(info); 923 break; 924 } 925 case HDMI_DEVICE_ADDED: { 926 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj; 927 mListener.onHdmiDeviceAdded(info); 928 break; 929 } 930 case HDMI_DEVICE_REMOVED: { 931 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj; 932 mListener.onHdmiDeviceRemoved(info); 933 break; 934 } 935 case HDMI_DEVICE_UPDATED: { 936 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj; 937 String inputId = null; 938 synchronized (mLock) { 939 inputId = mHdmiInputIdMap.get(info.getId()); 940 } 941 if (inputId != null) { 942 mListener.onHdmiDeviceUpdated(inputId, info); 943 } else { 944 Slog.w(TAG, "Could not resolve input ID matching the device info; " 945 + "ignoring."); 946 } 947 break; 948 } 949 default: { 950 Slog.w(TAG, "Unhandled message: " + msg); 951 break; 952 } 953 } 954 } 955 } 956 957 // Listener implementations for HdmiControlService 958 959 private final class HdmiHotplugEventListener extends IHdmiHotplugEventListener.Stub { 960 @Override 961 public void onReceived(HdmiHotplugEvent event) { 962 synchronized (mLock) { 963 mHdmiStateMap.put(event.getPort(), event.isConnected()); 964 TvInputHardwareInfo hardwareInfo = 965 findHardwareInfoForHdmiPortLocked(event.getPort()); 966 if (hardwareInfo == null) { 967 return; 968 } 969 String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId()); 970 if (inputId == null) { 971 return; 972 } 973 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, 974 convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget(); 975 } 976 } 977 } 978 979 private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub { 980 @Override 981 public void onStatusChanged(HdmiDeviceInfo deviceInfo, int status) { 982 synchronized (mLock) { 983 int messageType = 0; 984 Object obj = null; 985 switch (status) { 986 case HdmiControlManager.DEVICE_EVENT_ADD_DEVICE: { 987 if (findHdmiDeviceInfo(deviceInfo.getId()) == null) { 988 mHdmiDeviceList.add(deviceInfo); 989 } else { 990 Slog.w(TAG, "The list already contains " + deviceInfo + "; ignoring."); 991 return; 992 } 993 messageType = ListenerHandler.HDMI_DEVICE_ADDED; 994 obj = deviceInfo; 995 break; 996 } 997 case HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE: { 998 HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId()); 999 if (!mHdmiDeviceList.remove(originalDeviceInfo)) { 1000 Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring."); 1001 return; 1002 } 1003 messageType = ListenerHandler.HDMI_DEVICE_REMOVED; 1004 obj = deviceInfo; 1005 break; 1006 } 1007 case HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE: { 1008 HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId()); 1009 if (!mHdmiDeviceList.remove(originalDeviceInfo)) { 1010 Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring."); 1011 return; 1012 } 1013 mHdmiDeviceList.add(deviceInfo); 1014 messageType = ListenerHandler.HDMI_DEVICE_UPDATED; 1015 obj = deviceInfo; 1016 break; 1017 } 1018 } 1019 1020 Message msg = mHandler.obtainMessage(messageType, 0, 0, obj); 1021 if (findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId()) != null) { 1022 msg.sendToTarget(); 1023 } else { 1024 mPendingHdmiDeviceEvents.add(msg); 1025 } 1026 } 1027 } 1028 1029 private HdmiDeviceInfo findHdmiDeviceInfo(int id) { 1030 for (HdmiDeviceInfo info : mHdmiDeviceList) { 1031 if (info.getId() == id) { 1032 return info; 1033 } 1034 } 1035 return null; 1036 } 1037 } 1038 1039 private final class HdmiSystemAudioModeChangeListener extends 1040 IHdmiSystemAudioModeChangeListener.Stub { 1041 @Override 1042 public void onStatusChanged(boolean enabled) throws RemoteException { 1043 synchronized (mLock) { 1044 for (int i = 0; i < mConnections.size(); ++i) { 1045 TvInputHardwareImpl impl = mConnections.valueAt(i).getHardwareImplLocked(); 1046 if (impl != null) { 1047 impl.handleAudioSinkUpdated(); 1048 } 1049 } 1050 } 1051 } 1052 } 1053} 1054