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