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