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