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