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