TvInputHardwareManager.java revision 58500f43ecbed3f92d7c077fb6ce396252cd00ea
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 AudioDevicePort mAudioSource; 563 private AudioDevicePort mAudioSink; 564 private AudioPatch mAudioPatch = null; 565 private float mCommittedVolume = 0.0f; 566 private float mVolume = 0.0f; 567 568 private TvStreamConfig mActiveConfig = null; 569 570 private int mDesiredSamplingRate = 0; 571 private int mDesiredChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT; 572 private int mDesiredFormat = AudioFormat.ENCODING_DEFAULT; 573 574 public TvInputHardwareImpl(TvInputHardwareInfo info) { 575 mInfo = info; 576 AudioDevicePort audioSource = null; 577 if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) { 578 audioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress()); 579 mAudioSink = findAudioSinkFromAudioPolicy(); 580 } 581 mAudioSource = audioSource; 582 } 583 584 private AudioDevicePort findAudioSinkFromAudioPolicy() { 585 ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>(); 586 if (mAudioManager.listAudioDevicePorts(devicePorts) == AudioManager.SUCCESS) { 587 int sinkDevice = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC); 588 for (AudioPort port : devicePorts) { 589 AudioDevicePort devicePort = (AudioDevicePort) port; 590 if (devicePort.type() == sinkDevice) { 591 return devicePort; 592 } 593 } 594 } 595 return null; 596 } 597 598 private AudioDevicePort findAudioDevicePort(int type, String address) { 599 if (type == AudioManager.DEVICE_NONE) { 600 return null; 601 } 602 ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>(); 603 if (mAudioManager.listAudioDevicePorts(devicePorts) != AudioManager.SUCCESS) { 604 return null; 605 } 606 for (AudioPort port : devicePorts) { 607 AudioDevicePort devicePort = (AudioDevicePort) port; 608 if (devicePort.type() == type && devicePort.address().equals(address)) { 609 return devicePort; 610 } 611 } 612 return null; 613 } 614 615 public void release() { 616 synchronized (mImplLock) { 617 if (mAudioPatch != null) { 618 mAudioManager.releaseAudioPatch(mAudioPatch); 619 mAudioPatch = null; 620 } 621 mReleased = true; 622 } 623 } 624 625 // A TvInputHardwareImpl object holds only one active session. Therefore, if a client 626 // attempts to call setSurface with different TvStreamConfig objects, the last call will 627 // prevail. 628 @Override 629 public boolean setSurface(Surface surface, TvStreamConfig config) 630 throws RemoteException { 631 synchronized (mImplLock) { 632 if (mReleased) { 633 throw new IllegalStateException("Device already released."); 634 } 635 if (surface != null && config == null) { 636 return false; 637 } 638 if (surface == null && mActiveConfig == null) { 639 return false; 640 } 641 if (mAudioSource != null && mAudioSink != null) { 642 if (surface != null) { 643 updateAudioPatchLocked(); 644 } else { 645 mAudioManager.releaseAudioPatch(mAudioPatch); 646 mAudioPatch = null; 647 } 648 } 649 int result = TvInputHal.ERROR_UNKNOWN; 650 if (surface == null) { 651 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig); 652 mActiveConfig = null; 653 } else { 654 if (config != mActiveConfig && mActiveConfig != null) { 655 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig); 656 if (result != TvInputHal.SUCCESS) { 657 mActiveConfig = null; 658 return false; 659 } 660 } 661 result = mHal.addStream(mInfo.getDeviceId(), surface, config); 662 if (result == TvInputHal.SUCCESS) { 663 mActiveConfig = config; 664 } 665 } 666 return result == TvInputHal.SUCCESS; 667 } 668 } 669 670 private void updateAudioPatchLocked() { 671 AudioGainConfig sourceGainConfig = null; 672 if (mAudioSource.gains().length > 0 && mVolume != mCommittedVolume) { 673 AudioGain sourceGain = null; 674 for (AudioGain gain : mAudioSource.gains()) { 675 if ((gain.mode() & AudioGain.MODE_JOINT) != 0) { 676 sourceGain = gain; 677 break; 678 } 679 } 680 // NOTE: we only change the source gain in MODE_JOINT here. 681 if (sourceGain != null) { 682 int steps = (sourceGain.maxValue() - sourceGain.minValue()) 683 / sourceGain.stepValue(); 684 int gainValue = sourceGain.minValue(); 685 if (mVolume < 1.0f) { 686 gainValue += sourceGain.stepValue() * (int) (mVolume * steps + 0.5); 687 } else { 688 gainValue = sourceGain.maxValue(); 689 } 690 int numChannels = 0; 691 for (int mask = sourceGain.channelMask(); mask > 0; mask >>= 1) { 692 numChannels += (mask & 1); 693 } 694 int[] gainValues = new int[numChannels]; 695 Arrays.fill(gainValues, gainValue); 696 sourceGainConfig = sourceGain.buildConfig(AudioGain.MODE_JOINT, 697 sourceGain.channelMask(), gainValues, 0); 698 } else { 699 Slog.w(TAG, "No audio source gain with MODE_JOINT support exists."); 700 } 701 } 702 703 AudioPortConfig sourceConfig = mAudioSource.activeConfig(); 704 AudioPortConfig sinkConfig = mAudioSink.activeConfig(); 705 AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch }; 706 boolean shouldRecreateAudioPatch = false; 707 if (sinkConfig == null 708 || (mDesiredSamplingRate != 0 709 && sinkConfig.samplingRate() != mDesiredSamplingRate) 710 || (mDesiredChannelMask != AudioFormat.CHANNEL_OUT_DEFAULT 711 && sinkConfig.channelMask() != mDesiredChannelMask) 712 || (mDesiredFormat != AudioFormat.ENCODING_DEFAULT 713 && sinkConfig.format() != mDesiredFormat)) { 714 sinkConfig = mAudioSource.buildConfig(mDesiredSamplingRate, mDesiredChannelMask, 715 mDesiredFormat, null); 716 shouldRecreateAudioPatch = true; 717 } 718 if (sourceConfig == null || sourceGainConfig != null) { 719 sourceConfig = mAudioSource.buildConfig(sinkConfig.samplingRate(), 720 sinkConfig.channelMask(), sinkConfig.format(), sourceGainConfig); 721 shouldRecreateAudioPatch = true; 722 } 723 if (shouldRecreateAudioPatch) { 724 mCommittedVolume = mVolume; 725 mAudioManager.createAudioPatch( 726 audioPatchArray, 727 new AudioPortConfig[] { sourceConfig }, 728 new AudioPortConfig[] { sinkConfig }); 729 mAudioPatch = audioPatchArray[0]; 730 } 731 } 732 733 @Override 734 public void setStreamVolume(float volume) throws RemoteException { 735 synchronized (mImplLock) { 736 if (mReleased) { 737 throw new IllegalStateException("Device already released."); 738 } 739 mVolume = volume; 740 updateAudioPatchLocked(); 741 } 742 } 743 744 @Override 745 public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException { 746 synchronized (mImplLock) { 747 if (mReleased) { 748 throw new IllegalStateException("Device already released."); 749 } 750 } 751 if (mInfo.getType() != TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) { 752 return false; 753 } 754 // TODO(hdmi): mHdmiClient.sendKeyEvent(event); 755 return false; 756 } 757 758 private boolean startCapture(Surface surface, TvStreamConfig config) { 759 synchronized (mImplLock) { 760 if (mReleased) { 761 return false; 762 } 763 if (surface == null || config == null) { 764 return false; 765 } 766 if (config.getType() != TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) { 767 return false; 768 } 769 770 int result = mHal.addStream(mInfo.getDeviceId(), surface, config); 771 return result == TvInputHal.SUCCESS; 772 } 773 } 774 775 private boolean stopCapture(TvStreamConfig config) { 776 synchronized (mImplLock) { 777 if (mReleased) { 778 return false; 779 } 780 if (config == null) { 781 return false; 782 } 783 784 int result = mHal.removeStream(mInfo.getDeviceId(), config); 785 return result == TvInputHal.SUCCESS; 786 } 787 } 788 789 @Override 790 public void overrideAudioSink(int audioType, String audioAddress, int samplingRate, 791 int channelMask, int format) { 792 synchronized (mImplLock) { 793 if (audioType == AudioManager.DEVICE_NONE) { 794 mAudioSink = findAudioSinkFromAudioPolicy(); 795 } else { 796 AudioDevicePort audioSink = findAudioDevicePort(audioType, audioAddress); 797 if (audioSink != null) { 798 mAudioSink = audioSink; 799 } 800 } 801 mDesiredSamplingRate = samplingRate; 802 mDesiredChannelMask = channelMask; 803 mDesiredFormat = format; 804 805 if (mAudioPatch != null) { 806 updateAudioPatchLocked(); 807 } 808 } 809 } 810 } 811 812 interface Listener { 813 public void onStateChanged(String inputId, int state); 814 public void onHardwareDeviceAdded(TvInputHardwareInfo info); 815 public void onHardwareDeviceRemoved(TvInputHardwareInfo info); 816 public void onHdmiCecDeviceAdded(HdmiCecDeviceInfo cecDevice); 817 public void onHdmiCecDeviceRemoved(HdmiCecDeviceInfo cecDevice); 818 } 819 820 private class ListenerHandler extends Handler { 821 private static final int STATE_CHANGED = 1; 822 private static final int HARDWARE_DEVICE_ADDED = 2; 823 private static final int HARDWARE_DEVICE_REMOVED = 3; 824 private static final int HDMI_CEC_DEVICE_ADDED = 4; 825 private static final int HDMI_CEC_DEVICE_REMOVED = 5; 826 827 @Override 828 public final void handleMessage(Message msg) { 829 switch (msg.what) { 830 case STATE_CHANGED: { 831 String inputId = (String) msg.obj; 832 int state = msg.arg1; 833 mListener.onStateChanged(inputId, state); 834 break; 835 } 836 case HARDWARE_DEVICE_ADDED: { 837 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj; 838 mListener.onHardwareDeviceAdded(info); 839 break; 840 } 841 case HARDWARE_DEVICE_REMOVED: { 842 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj; 843 mListener.onHardwareDeviceRemoved(info); 844 break; 845 } 846 case HDMI_CEC_DEVICE_ADDED: { 847 HdmiCecDeviceInfo info = (HdmiCecDeviceInfo) msg.obj; 848 mListener.onHdmiCecDeviceAdded(info); 849 break; 850 } 851 case HDMI_CEC_DEVICE_REMOVED: { 852 HdmiCecDeviceInfo info = (HdmiCecDeviceInfo) msg.obj; 853 mListener.onHdmiCecDeviceRemoved(info); 854 break; 855 } 856 default: { 857 Slog.w(TAG, "Unhandled message: " + msg); 858 break; 859 } 860 } 861 } 862 } 863 864 // Listener implementations for HdmiControlService 865 866 private final class HdmiHotplugEventListener extends IHdmiHotplugEventListener.Stub { 867 @Override 868 public void onReceived(HdmiHotplugEvent event) { 869 synchronized (mLock) { 870 mHdmiStateMap.put(event.getPort(), event.isConnected()); 871 TvInputHardwareInfo hardwareInfo = 872 findHardwareInfoForHdmiPortLocked(event.getPort()); 873 if (hardwareInfo == null) { 874 return; 875 } 876 String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId()); 877 if (inputId == null) { 878 return; 879 } 880 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, 881 convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget(); 882 } 883 } 884 } 885 886 private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub { 887 @Override 888 public void onStatusChanged(HdmiCecDeviceInfo deviceInfo, boolean activated) { 889 synchronized (mLock) { 890 if (activated) { 891 if (!mHdmiCecDeviceList.contains(deviceInfo)) { 892 mHdmiCecDeviceList.add(deviceInfo); 893 } else { 894 Slog.w(TAG, "The list already contains " + deviceInfo + "; ignoring."); 895 return; 896 } 897 } else { 898 if (!mHdmiCecDeviceList.remove(deviceInfo)) { 899 Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring."); 900 return; 901 } 902 } 903 Message msg = mHandler.obtainMessage( 904 activated ? ListenerHandler.HDMI_CEC_DEVICE_ADDED 905 : ListenerHandler.HDMI_CEC_DEVICE_REMOVED, 906 0, 0, deviceInfo); 907 if (findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId()) != null) { 908 msg.sendToTarget(); 909 } else { 910 mPendingHdmiDeviceEvents.add(msg); 911 } 912 } 913 } 914 } 915 916 private final class HdmiInputChangeListener extends IHdmiInputChangeListener.Stub { 917 @Override 918 public void onChanged(HdmiCecDeviceInfo device) throws RemoteException { 919 String inputId; 920 synchronized (mLock) { 921 if (device.isCecDevice()) { 922 inputId = mHdmiCecInputIdMap.get(device.getLogicalAddress()); 923 } else { 924 TvInputHardwareInfo hardwareInfo = 925 findHardwareInfoForHdmiPortLocked(device.getPortId()); 926 inputId = (hardwareInfo == null) ? null 927 : mHardwareInputIdMap.get(hardwareInfo.getDeviceId()); 928 } 929 } 930 if (inputId != null) { 931 Intent intent = new Intent(Intent.ACTION_VIEW); 932 intent.setData(TvContract.buildChannelUriForPassthroughTvInput(inputId)); 933 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 934 mContext.startActivity(intent); 935 } else { 936 Slog.w(TAG, "onChanged: InputId cannot be found for :" + device); 937 } 938 } 939 } 940} 941