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