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.BroadcastReceiver; 23import android.content.Context; 24import android.content.Intent; 25import android.content.IntentFilter; 26import android.hardware.hdmi.HdmiControlManager; 27import android.hardware.hdmi.HdmiDeviceInfo; 28import android.hardware.hdmi.HdmiHotplugEvent; 29import android.hardware.hdmi.IHdmiControlService; 30import android.hardware.hdmi.IHdmiDeviceEventListener; 31import android.hardware.hdmi.IHdmiHotplugEventListener; 32import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener; 33import android.media.AudioDevicePort; 34import android.media.AudioFormat; 35import android.media.AudioGain; 36import android.media.AudioGainConfig; 37import android.media.AudioManager; 38import android.media.AudioPatch; 39import android.media.AudioPort; 40import android.media.AudioPortConfig; 41import android.media.tv.ITvInputHardware; 42import android.media.tv.ITvInputHardwareCallback; 43import android.media.tv.TvInputHardwareInfo; 44import android.media.tv.TvInputInfo; 45import android.media.tv.TvStreamConfig; 46import android.os.Handler; 47import android.os.IBinder; 48import android.os.Message; 49import android.os.RemoteException; 50import android.os.ServiceManager; 51import android.util.ArrayMap; 52import android.util.Slog; 53import android.util.SparseArray; 54import android.util.SparseBooleanArray; 55import android.view.KeyEvent; 56import android.view.Surface; 57 58import com.android.server.SystemService; 59 60import java.util.ArrayList; 61import java.util.Collections; 62import java.util.Iterator; 63import java.util.LinkedList; 64import java.util.List; 65import java.util.Map; 66 67/** 68 * A helper class for TvInputManagerService to handle TV input hardware. 69 * 70 * This class does a basic connection management and forwarding calls to TvInputHal which eventually 71 * calls to tv_input HAL module. 72 * 73 * @hide 74 */ 75class TvInputHardwareManager implements TvInputHal.Callback { 76 private static final String TAG = TvInputHardwareManager.class.getSimpleName(); 77 78 private final Context mContext; 79 private final Listener mListener; 80 private final TvInputHal mHal = new TvInputHal(this); 81 private final SparseArray<Connection> mConnections = new SparseArray<>(); 82 private final List<TvInputHardwareInfo> mHardwareList = new ArrayList<>(); 83 private final List<HdmiDeviceInfo> mHdmiDeviceList = new LinkedList<>(); 84 /* A map from a device ID to the matching TV input ID. */ 85 private final SparseArray<String> mHardwareInputIdMap = new SparseArray<>(); 86 /* A map from a HDMI logical address to the matching TV input ID. */ 87 private final SparseArray<String> mHdmiInputIdMap = new SparseArray<>(); 88 private final Map<String, TvInputInfo> mInputMap = new ArrayMap<>(); 89 90 private final AudioManager mAudioManager; 91 private final IHdmiHotplugEventListener mHdmiHotplugEventListener = 92 new HdmiHotplugEventListener(); 93 private final IHdmiDeviceEventListener mHdmiDeviceEventListener = new HdmiDeviceEventListener(); 94 private final IHdmiSystemAudioModeChangeListener mHdmiSystemAudioModeChangeListener = 95 new HdmiSystemAudioModeChangeListener(); 96 private final BroadcastReceiver mVolumeReceiver = new BroadcastReceiver() { 97 @Override 98 public void onReceive(Context context, Intent intent) { 99 handleVolumeChange(context, intent); 100 } 101 }; 102 private int mCurrentIndex = 0; 103 private int mCurrentMaxIndex = 0; 104 105 // TODO: Should handle STANDBY case. 106 private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray(); 107 private final List<Message> mPendingHdmiDeviceEvents = new LinkedList<>(); 108 109 // Calls to mListener should happen here. 110 private final Handler mHandler = new ListenerHandler(); 111 112 private final Object mLock = new Object(); 113 114 public TvInputHardwareManager(Context context, Listener listener) { 115 mContext = context; 116 mListener = listener; 117 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 118 mHal.init(); 119 } 120 121 public void onBootPhase(int phase) { 122 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { 123 IHdmiControlService hdmiControlService = IHdmiControlService.Stub.asInterface( 124 ServiceManager.getService(Context.HDMI_CONTROL_SERVICE)); 125 if (hdmiControlService != null) { 126 try { 127 hdmiControlService.addHotplugEventListener(mHdmiHotplugEventListener); 128 hdmiControlService.addDeviceEventListener(mHdmiDeviceEventListener); 129 hdmiControlService.addSystemAudioModeChangeListener( 130 mHdmiSystemAudioModeChangeListener); 131 mHdmiDeviceList.addAll(hdmiControlService.getInputDevices()); 132 } catch (RemoteException e) { 133 Slog.w(TAG, "Error registering listeners to HdmiControlService:", e); 134 } 135 } else { 136 Slog.w(TAG, "HdmiControlService is not available"); 137 } 138 final IntentFilter filter = new IntentFilter(); 139 filter.addAction(AudioManager.VOLUME_CHANGED_ACTION); 140 filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION); 141 mContext.registerReceiver(mVolumeReceiver, filter); 142 updateVolume(); 143 } 144 } 145 146 @Override 147 public void onDeviceAvailable(TvInputHardwareInfo info, TvStreamConfig[] configs) { 148 synchronized (mLock) { 149 Connection connection = new Connection(info); 150 connection.updateConfigsLocked(configs); 151 mConnections.put(info.getDeviceId(), connection); 152 buildHardwareListLocked(); 153 mHandler.obtainMessage( 154 ListenerHandler.HARDWARE_DEVICE_ADDED, 0, 0, info).sendToTarget(); 155 if (info.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) { 156 processPendingHdmiDeviceEventsLocked(); 157 } 158 } 159 } 160 161 private void buildHardwareListLocked() { 162 mHardwareList.clear(); 163 for (int i = 0; i < mConnections.size(); ++i) { 164 mHardwareList.add(mConnections.valueAt(i).getHardwareInfoLocked()); 165 } 166 } 167 168 @Override 169 public void onDeviceUnavailable(int deviceId) { 170 synchronized (mLock) { 171 Connection connection = mConnections.get(deviceId); 172 if (connection == null) { 173 Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId); 174 return; 175 } 176 connection.resetLocked(null, null, null, null, null); 177 mConnections.remove(deviceId); 178 buildHardwareListLocked(); 179 TvInputHardwareInfo info = connection.getHardwareInfoLocked(); 180 if (info.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) { 181 // Remove HDMI devices linked with this hardware. 182 for (Iterator<HdmiDeviceInfo> it = mHdmiDeviceList.iterator(); it.hasNext();) { 183 HdmiDeviceInfo deviceInfo = it.next(); 184 if (deviceInfo.getPortId() == info.getHdmiPortId()) { 185 mHandler.obtainMessage(ListenerHandler.HDMI_DEVICE_REMOVED, 0, 0, 186 deviceInfo).sendToTarget(); 187 it.remove(); 188 } 189 } 190 } 191 mHandler.obtainMessage( 192 ListenerHandler.HARDWARE_DEVICE_REMOVED, 0, 0, info).sendToTarget(); 193 } 194 } 195 196 @Override 197 public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs) { 198 synchronized (mLock) { 199 Connection connection = mConnections.get(deviceId); 200 if (connection == null) { 201 Slog.e(TAG, "StreamConfigurationChanged: Cannot find a connection with " 202 + deviceId); 203 return; 204 } 205 connection.updateConfigsLocked(configs); 206 String inputId = mHardwareInputIdMap.get(deviceId); 207 if (inputId != null) { 208 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, 209 convertConnectedToState(configs.length > 0), 0, inputId).sendToTarget(); 210 } 211 ITvInputHardwareCallback callback = connection.getCallbackLocked(); 212 if (callback != null) { 213 try { 214 callback.onStreamConfigChanged(configs); 215 } catch (RemoteException e) { 216 Slog.e(TAG, "error in onStreamConfigurationChanged", e); 217 } 218 } 219 } 220 } 221 222 @Override 223 public void onFirstFrameCaptured(int deviceId, int streamId) { 224 synchronized (mLock) { 225 Connection connection = mConnections.get(deviceId); 226 if (connection == null) { 227 Slog.e(TAG, "FirstFrameCaptured: Cannot find a connection with " 228 + deviceId); 229 return; 230 } 231 Runnable runnable = connection.getOnFirstFrameCapturedLocked(); 232 if (runnable != null) { 233 runnable.run(); 234 connection.setOnFirstFrameCapturedLocked(null); 235 } 236 } 237 } 238 239 public List<TvInputHardwareInfo> getHardwareList() { 240 synchronized (mLock) { 241 return Collections.unmodifiableList(mHardwareList); 242 } 243 } 244 245 public List<HdmiDeviceInfo> getHdmiDeviceList() { 246 synchronized (mLock) { 247 return Collections.unmodifiableList(mHdmiDeviceList); 248 } 249 } 250 251 private boolean checkUidChangedLocked( 252 Connection connection, int callingUid, int resolvedUserId) { 253 Integer connectionCallingUid = connection.getCallingUidLocked(); 254 Integer connectionResolvedUserId = connection.getResolvedUserIdLocked(); 255 return connectionCallingUid == null || connectionResolvedUserId == null 256 || connectionCallingUid != callingUid || connectionResolvedUserId != resolvedUserId; 257 } 258 259 private int convertConnectedToState(boolean connected) { 260 if (connected) { 261 return INPUT_STATE_CONNECTED; 262 } else { 263 return INPUT_STATE_DISCONNECTED; 264 } 265 } 266 267 public void addHardwareTvInput(int deviceId, TvInputInfo info) { 268 synchronized (mLock) { 269 String oldInputId = mHardwareInputIdMap.get(deviceId); 270 if (oldInputId != null) { 271 Slog.w(TAG, "Trying to override previous registration: old = " 272 + mInputMap.get(oldInputId) + ":" + deviceId + ", new = " 273 + info + ":" + deviceId); 274 } 275 mHardwareInputIdMap.put(deviceId, info.getId()); 276 mInputMap.put(info.getId(), info); 277 278 // Process pending state changes 279 280 // For logical HDMI devices, they have information from HDMI CEC signals. 281 for (int i = 0; i < mHdmiStateMap.size(); ++i) { 282 TvInputHardwareInfo hardwareInfo = 283 findHardwareInfoForHdmiPortLocked(mHdmiStateMap.keyAt(i)); 284 if (hardwareInfo == null) { 285 continue; 286 } 287 String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId()); 288 if (inputId != null && inputId.equals(info.getId())) { 289 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, 290 convertConnectedToState(mHdmiStateMap.valueAt(i)), 0, 291 inputId).sendToTarget(); 292 return; 293 } 294 } 295 // For the rest of the devices, we can tell by the number of available streams. 296 Connection connection = mConnections.get(deviceId); 297 if (connection != null) { 298 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, 299 convertConnectedToState(connection.getConfigsLocked().length > 0), 0, 300 info.getId()).sendToTarget(); 301 } 302 } 303 } 304 305 private static <T> int indexOfEqualValue(SparseArray<T> map, T value) { 306 for (int i = 0; i < map.size(); ++i) { 307 if (map.valueAt(i).equals(value)) { 308 return i; 309 } 310 } 311 return -1; 312 } 313 314 private static boolean intArrayContains(int[] array, int value) { 315 for (int element : array) { 316 if (element == value) return true; 317 } 318 return false; 319 } 320 321 public void addHdmiTvInput(int id, TvInputInfo info) { 322 if (info.getType() != TvInputInfo.TYPE_HDMI) { 323 throw new IllegalArgumentException("info (" + info + ") has non-HDMI type."); 324 } 325 synchronized (mLock) { 326 String parentId = info.getParentId(); 327 int parentIndex = indexOfEqualValue(mHardwareInputIdMap, parentId); 328 if (parentIndex < 0) { 329 throw new IllegalArgumentException("info (" + info + ") has invalid parentId."); 330 } 331 String oldInputId = mHdmiInputIdMap.get(id); 332 if (oldInputId != null) { 333 Slog.w(TAG, "Trying to override previous registration: old = " 334 + mInputMap.get(oldInputId) + ":" + id + ", new = " 335 + info + ":" + id); 336 } 337 mHdmiInputIdMap.put(id, info.getId()); 338 mInputMap.put(info.getId(), info); 339 } 340 } 341 342 public void removeTvInput(String inputId) { 343 synchronized (mLock) { 344 mInputMap.remove(inputId); 345 int hardwareIndex = indexOfEqualValue(mHardwareInputIdMap, inputId); 346 if (hardwareIndex >= 0) { 347 mHardwareInputIdMap.removeAt(hardwareIndex); 348 } 349 int deviceIndex = indexOfEqualValue(mHdmiInputIdMap, inputId); 350 if (deviceIndex >= 0) { 351 mHdmiInputIdMap.removeAt(deviceIndex); 352 } 353 } 354 } 355 356 /** 357 * Create a TvInputHardware object with a specific deviceId. One service at a time can access 358 * the object, and if more than one process attempts to create hardware with the same deviceId, 359 * the latest service will get the object and all the other hardware are released. The 360 * release is notified via ITvInputHardwareCallback.onReleased(). 361 */ 362 public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback, 363 TvInputInfo info, int callingUid, int resolvedUserId) { 364 if (callback == null) { 365 throw new NullPointerException(); 366 } 367 synchronized (mLock) { 368 Connection connection = mConnections.get(deviceId); 369 if (connection == null) { 370 Slog.e(TAG, "Invalid deviceId : " + deviceId); 371 return null; 372 } 373 if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) { 374 TvInputHardwareImpl hardware = 375 new TvInputHardwareImpl(connection.getHardwareInfoLocked()); 376 try { 377 callback.asBinder().linkToDeath(connection, 0); 378 } catch (RemoteException e) { 379 hardware.release(); 380 return null; 381 } 382 connection.resetLocked(hardware, callback, info, callingUid, resolvedUserId); 383 } 384 return connection.getHardwareLocked(); 385 } 386 } 387 388 /** 389 * Release the specified hardware. 390 */ 391 public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid, 392 int resolvedUserId) { 393 synchronized (mLock) { 394 Connection connection = mConnections.get(deviceId); 395 if (connection == null) { 396 Slog.e(TAG, "Invalid deviceId : " + deviceId); 397 return; 398 } 399 if (connection.getHardwareLocked() != hardware 400 || checkUidChangedLocked(connection, callingUid, resolvedUserId)) { 401 return; 402 } 403 connection.resetLocked(null, null, null, null, null); 404 } 405 } 406 407 private TvInputHardwareInfo findHardwareInfoForHdmiPortLocked(int port) { 408 for (TvInputHardwareInfo hardwareInfo : mHardwareList) { 409 if (hardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI 410 && hardwareInfo.getHdmiPortId() == port) { 411 return hardwareInfo; 412 } 413 } 414 return null; 415 } 416 417 private int findDeviceIdForInputIdLocked(String inputId) { 418 for (int i = 0; i < mConnections.size(); ++i) { 419 Connection connection = mConnections.get(i); 420 if (connection.getInfoLocked().getId().equals(inputId)) { 421 return i; 422 } 423 } 424 return -1; 425 } 426 427 /** 428 * Get the list of TvStreamConfig which is buffered mode. 429 */ 430 public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int callingUid, 431 int resolvedUserId) { 432 List<TvStreamConfig> configsList = new ArrayList<>(); 433 synchronized (mLock) { 434 int deviceId = findDeviceIdForInputIdLocked(inputId); 435 if (deviceId < 0) { 436 Slog.e(TAG, "Invalid inputId : " + inputId); 437 return configsList; 438 } 439 Connection connection = mConnections.get(deviceId); 440 for (TvStreamConfig config : connection.getConfigsLocked()) { 441 if (config.getType() == TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) { 442 configsList.add(config); 443 } 444 } 445 } 446 return configsList; 447 } 448 449 /** 450 * Take a snapshot of the given TV input into the provided Surface. 451 */ 452 public boolean captureFrame(String inputId, Surface surface, final TvStreamConfig config, 453 int callingUid, int resolvedUserId) { 454 synchronized (mLock) { 455 int deviceId = findDeviceIdForInputIdLocked(inputId); 456 if (deviceId < 0) { 457 Slog.e(TAG, "Invalid inputId : " + inputId); 458 return false; 459 } 460 Connection connection = mConnections.get(deviceId); 461 final TvInputHardwareImpl hardwareImpl = connection.getHardwareImplLocked(); 462 if (hardwareImpl != null) { 463 // Stop previous capture. 464 Runnable runnable = connection.getOnFirstFrameCapturedLocked(); 465 if (runnable != null) { 466 runnable.run(); 467 connection.setOnFirstFrameCapturedLocked(null); 468 } 469 470 boolean result = hardwareImpl.startCapture(surface, config); 471 if (result) { 472 connection.setOnFirstFrameCapturedLocked(new Runnable() { 473 @Override 474 public void run() { 475 hardwareImpl.stopCapture(config); 476 } 477 }); 478 } 479 return result; 480 } 481 } 482 return false; 483 } 484 485 private void processPendingHdmiDeviceEventsLocked() { 486 for (Iterator<Message> it = mPendingHdmiDeviceEvents.iterator(); it.hasNext(); ) { 487 Message msg = it.next(); 488 HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj; 489 TvInputHardwareInfo hardwareInfo = 490 findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId()); 491 if (hardwareInfo != null) { 492 msg.sendToTarget(); 493 it.remove(); 494 } 495 } 496 } 497 498 private void updateVolume() { 499 mCurrentMaxIndex = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 500 mCurrentIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); 501 } 502 503 private void handleVolumeChange(Context context, Intent intent) { 504 String action = intent.getAction(); 505 switch (action) { 506 case AudioManager.VOLUME_CHANGED_ACTION: { 507 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); 508 if (streamType != AudioManager.STREAM_MUSIC) { 509 return; 510 } 511 int index = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0); 512 if (index == mCurrentIndex) { 513 return; 514 } 515 mCurrentIndex = index; 516 break; 517 } 518 case AudioManager.STREAM_MUTE_CHANGED_ACTION: { 519 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); 520 if (streamType != AudioManager.STREAM_MUSIC) { 521 return; 522 } 523 // volume index will be updated at onMediaStreamVolumeChanged() through 524 // updateVolume(). 525 break; 526 } 527 default: 528 Slog.w(TAG, "Unrecognized intent: " + intent); 529 return; 530 } 531 synchronized (mLock) { 532 for (int i = 0; i < mConnections.size(); ++i) { 533 TvInputHardwareImpl hardwareImpl = mConnections.valueAt(i).getHardwareImplLocked(); 534 if (hardwareImpl != null) { 535 hardwareImpl.onMediaStreamVolumeChanged(); 536 } 537 } 538 } 539 } 540 541 private float getMediaStreamVolume() { 542 return (float) mCurrentIndex / (float) mCurrentMaxIndex; 543 } 544 545 private class Connection implements IBinder.DeathRecipient { 546 private final TvInputHardwareInfo mHardwareInfo; 547 private TvInputInfo mInfo; 548 private TvInputHardwareImpl mHardware = null; 549 private ITvInputHardwareCallback mCallback; 550 private TvStreamConfig[] mConfigs = null; 551 private Integer mCallingUid = null; 552 private Integer mResolvedUserId = null; 553 private Runnable mOnFirstFrameCaptured; 554 555 public Connection(TvInputHardwareInfo hardwareInfo) { 556 mHardwareInfo = hardwareInfo; 557 } 558 559 // *Locked methods assume TvInputHardwareManager.mLock is held. 560 561 public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback, 562 TvInputInfo info, Integer callingUid, Integer resolvedUserId) { 563 if (mHardware != null) { 564 try { 565 mCallback.onReleased(); 566 } catch (RemoteException e) { 567 Slog.e(TAG, "error in Connection::resetLocked", e); 568 } 569 mHardware.release(); 570 } 571 mHardware = hardware; 572 mCallback = callback; 573 mInfo = info; 574 mCallingUid = callingUid; 575 mResolvedUserId = resolvedUserId; 576 mOnFirstFrameCaptured = null; 577 578 if (mHardware != null && mCallback != null) { 579 try { 580 mCallback.onStreamConfigChanged(getConfigsLocked()); 581 } catch (RemoteException e) { 582 Slog.e(TAG, "error in Connection::resetLocked", e); 583 } 584 } 585 } 586 587 public void updateConfigsLocked(TvStreamConfig[] configs) { 588 mConfigs = configs; 589 } 590 591 public TvInputHardwareInfo getHardwareInfoLocked() { 592 return mHardwareInfo; 593 } 594 595 public TvInputInfo getInfoLocked() { 596 return mInfo; 597 } 598 599 public ITvInputHardware getHardwareLocked() { 600 return mHardware; 601 } 602 603 public TvInputHardwareImpl getHardwareImplLocked() { 604 return mHardware; 605 } 606 607 public ITvInputHardwareCallback getCallbackLocked() { 608 return mCallback; 609 } 610 611 public TvStreamConfig[] getConfigsLocked() { 612 return mConfigs; 613 } 614 615 public Integer getCallingUidLocked() { 616 return mCallingUid; 617 } 618 619 public Integer getResolvedUserIdLocked() { 620 return mResolvedUserId; 621 } 622 623 public void setOnFirstFrameCapturedLocked(Runnable runnable) { 624 mOnFirstFrameCaptured = runnable; 625 } 626 627 public Runnable getOnFirstFrameCapturedLocked() { 628 return mOnFirstFrameCaptured; 629 } 630 631 @Override 632 public void binderDied() { 633 synchronized (mLock) { 634 resetLocked(null, null, null, null, null); 635 } 636 } 637 } 638 639 private class TvInputHardwareImpl extends ITvInputHardware.Stub { 640 private final TvInputHardwareInfo mInfo; 641 private boolean mReleased = false; 642 private final Object mImplLock = new Object(); 643 644 private final AudioManager.OnAudioPortUpdateListener mAudioListener = 645 new AudioManager.OnAudioPortUpdateListener() { 646 @Override 647 public void onAudioPortListUpdate(AudioPort[] portList) { 648 synchronized (mImplLock) { 649 updateAudioConfigLocked(); 650 } 651 } 652 653 @Override 654 public void onAudioPatchListUpdate(AudioPatch[] patchList) { 655 // No-op 656 } 657 658 @Override 659 public void onServiceDied() { 660 synchronized (mImplLock) { 661 mAudioSource = null; 662 mAudioSink.clear(); 663 mAudioPatch = null; 664 } 665 } 666 }; 667 private int mOverrideAudioType = AudioManager.DEVICE_NONE; 668 private String mOverrideAudioAddress = ""; 669 private AudioDevicePort mAudioSource; 670 private List<AudioDevicePort> mAudioSink = new ArrayList<>(); 671 private AudioPatch mAudioPatch = null; 672 // Set to an invalid value for a volume, so that current volume can be applied at the 673 // first call to updateAudioConfigLocked(). 674 private float mCommittedVolume = -1f; 675 private float mSourceVolume = 0.0f; 676 677 private TvStreamConfig mActiveConfig = null; 678 679 private int mDesiredSamplingRate = 0; 680 private int mDesiredChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT; 681 private int mDesiredFormat = AudioFormat.ENCODING_DEFAULT; 682 683 public TvInputHardwareImpl(TvInputHardwareInfo info) { 684 mInfo = info; 685 mAudioManager.registerAudioPortUpdateListener(mAudioListener); 686 if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) { 687 mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress()); 688 findAudioSinkFromAudioPolicy(mAudioSink); 689 } 690 } 691 692 private void findAudioSinkFromAudioPolicy(List<AudioDevicePort> sinks) { 693 sinks.clear(); 694 ArrayList<AudioDevicePort> devicePorts = new ArrayList<>(); 695 if (mAudioManager.listAudioDevicePorts(devicePorts) != AudioManager.SUCCESS) { 696 return; 697 } 698 int sinkDevice = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC); 699 for (AudioDevicePort port : devicePorts) { 700 if ((port.type() & sinkDevice) != 0) { 701 sinks.add(port); 702 } 703 } 704 } 705 706 private AudioDevicePort findAudioDevicePort(int type, String address) { 707 if (type == AudioManager.DEVICE_NONE) { 708 return null; 709 } 710 ArrayList<AudioDevicePort> devicePorts = new ArrayList<>(); 711 if (mAudioManager.listAudioDevicePorts(devicePorts) != AudioManager.SUCCESS) { 712 return null; 713 } 714 for (AudioDevicePort port : devicePorts) { 715 if (port.type() == type && port.address().equals(address)) { 716 return port; 717 } 718 } 719 return null; 720 } 721 722 public void release() { 723 synchronized (mImplLock) { 724 mAudioManager.unregisterAudioPortUpdateListener(mAudioListener); 725 if (mAudioPatch != null) { 726 mAudioManager.releaseAudioPatch(mAudioPatch); 727 mAudioPatch = null; 728 } 729 mReleased = true; 730 } 731 } 732 733 // A TvInputHardwareImpl object holds only one active session. Therefore, if a client 734 // attempts to call setSurface with different TvStreamConfig objects, the last call will 735 // prevail. 736 @Override 737 public boolean setSurface(Surface surface, TvStreamConfig config) 738 throws RemoteException { 739 synchronized (mImplLock) { 740 if (mReleased) { 741 throw new IllegalStateException("Device already released."); 742 } 743 744 int result = TvInputHal.SUCCESS; 745 if (surface == null) { 746 // The value of config is ignored when surface == null. 747 if (mActiveConfig != null) { 748 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig); 749 mActiveConfig = null; 750 } else { 751 // We already have no active stream. 752 return true; 753 } 754 } else { 755 // It's impossible to set a non-null surface with a null config. 756 if (config == null) { 757 return false; 758 } 759 // Remove stream only if we have an existing active configuration. 760 if (mActiveConfig != null && !config.equals(mActiveConfig)) { 761 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig); 762 if (result != TvInputHal.SUCCESS) { 763 mActiveConfig = null; 764 } 765 } 766 // Proceed only if all previous operations succeeded. 767 if (result == TvInputHal.SUCCESS) { 768 result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config); 769 if (result == TvInputHal.SUCCESS) { 770 mActiveConfig = config; 771 } 772 } 773 } 774 updateAudioConfigLocked(); 775 return result == TvInputHal.SUCCESS; 776 } 777 } 778 779 /** 780 * Update audio configuration (source, sink, patch) all up to current state. 781 */ 782 private void updateAudioConfigLocked() { 783 boolean sinkUpdated = updateAudioSinkLocked(); 784 boolean sourceUpdated = updateAudioSourceLocked(); 785 // We can't do updated = updateAudioSinkLocked() || updateAudioSourceLocked() here 786 // because Java won't evaluate the latter if the former is true. 787 788 if (mAudioSource == null || mAudioSink.isEmpty() || mActiveConfig == null) { 789 if (mAudioPatch != null) { 790 mAudioManager.releaseAudioPatch(mAudioPatch); 791 mAudioPatch = null; 792 } 793 return; 794 } 795 796 updateVolume(); 797 float volume = mSourceVolume * getMediaStreamVolume(); 798 AudioGainConfig sourceGainConfig = null; 799 if (mAudioSource.gains().length > 0 && volume != mCommittedVolume) { 800 AudioGain sourceGain = null; 801 for (AudioGain gain : mAudioSource.gains()) { 802 if ((gain.mode() & AudioGain.MODE_JOINT) != 0) { 803 sourceGain = gain; 804 break; 805 } 806 } 807 // NOTE: we only change the source gain in MODE_JOINT here. 808 if (sourceGain != null) { 809 int steps = (sourceGain.maxValue() - sourceGain.minValue()) 810 / sourceGain.stepValue(); 811 int gainValue = sourceGain.minValue(); 812 if (volume < 1.0f) { 813 gainValue += sourceGain.stepValue() * (int) (volume * steps + 0.5); 814 } else { 815 gainValue = sourceGain.maxValue(); 816 } 817 // size of gain values is 1 in MODE_JOINT 818 int[] gainValues = new int[] { gainValue }; 819 sourceGainConfig = sourceGain.buildConfig(AudioGain.MODE_JOINT, 820 sourceGain.channelMask(), gainValues, 0); 821 } else { 822 Slog.w(TAG, "No audio source gain with MODE_JOINT support exists."); 823 } 824 } 825 826 AudioPortConfig sourceConfig = mAudioSource.activeConfig(); 827 List<AudioPortConfig> sinkConfigs = new ArrayList<>(); 828 AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch }; 829 boolean shouldRecreateAudioPatch = sourceUpdated || sinkUpdated; 830 831 for (AudioDevicePort audioSink : mAudioSink) { 832 AudioPortConfig sinkConfig = audioSink.activeConfig(); 833 int sinkSamplingRate = mDesiredSamplingRate; 834 int sinkChannelMask = mDesiredChannelMask; 835 int sinkFormat = mDesiredFormat; 836 // If sinkConfig != null and values are set to default, 837 // fill in the sinkConfig values. 838 if (sinkConfig != null) { 839 if (sinkSamplingRate == 0) { 840 sinkSamplingRate = sinkConfig.samplingRate(); 841 } 842 if (sinkChannelMask == AudioFormat.CHANNEL_OUT_DEFAULT) { 843 sinkChannelMask = sinkConfig.channelMask(); 844 } 845 if (sinkFormat == AudioFormat.ENCODING_DEFAULT) { 846 sinkChannelMask = sinkConfig.format(); 847 } 848 } 849 850 if (sinkConfig == null 851 || sinkConfig.samplingRate() != sinkSamplingRate 852 || sinkConfig.channelMask() != sinkChannelMask 853 || sinkConfig.format() != sinkFormat) { 854 // Check for compatibility and reset to default if necessary. 855 if (!intArrayContains(audioSink.samplingRates(), sinkSamplingRate) 856 && audioSink.samplingRates().length > 0) { 857 sinkSamplingRate = audioSink.samplingRates()[0]; 858 } 859 if (!intArrayContains(audioSink.channelMasks(), sinkChannelMask)) { 860 sinkChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT; 861 } 862 if (!intArrayContains(audioSink.formats(), sinkFormat)) { 863 sinkFormat = AudioFormat.ENCODING_DEFAULT; 864 } 865 sinkConfig = audioSink.buildConfig(sinkSamplingRate, sinkChannelMask, 866 sinkFormat, null); 867 shouldRecreateAudioPatch = true; 868 } 869 sinkConfigs.add(sinkConfig); 870 } 871 // sinkConfigs.size() == mAudioSink.size(), and mAudioSink is guaranteed to be 872 // non-empty at the beginning of this method. 873 AudioPortConfig sinkConfig = sinkConfigs.get(0); 874 if (sourceConfig == null || sourceGainConfig != null) { 875 int sourceSamplingRate = 0; 876 if (intArrayContains(mAudioSource.samplingRates(), sinkConfig.samplingRate())) { 877 sourceSamplingRate = sinkConfig.samplingRate(); 878 } else if (mAudioSource.samplingRates().length > 0) { 879 // Use any sampling rate and hope audio patch can handle resampling... 880 sourceSamplingRate = mAudioSource.samplingRates()[0]; 881 } 882 int sourceChannelMask = AudioFormat.CHANNEL_IN_DEFAULT; 883 for (int inChannelMask : mAudioSource.channelMasks()) { 884 if (AudioFormat.channelCountFromOutChannelMask(sinkConfig.channelMask()) 885 == AudioFormat.channelCountFromInChannelMask(inChannelMask)) { 886 sourceChannelMask = inChannelMask; 887 break; 888 } 889 } 890 int sourceFormat = AudioFormat.ENCODING_DEFAULT; 891 if (intArrayContains(mAudioSource.formats(), sinkConfig.format())) { 892 sourceFormat = sinkConfig.format(); 893 } 894 sourceConfig = mAudioSource.buildConfig(sourceSamplingRate, sourceChannelMask, 895 sourceFormat, sourceGainConfig); 896 shouldRecreateAudioPatch = true; 897 } 898 if (shouldRecreateAudioPatch) { 899 mCommittedVolume = volume; 900 mAudioManager.createAudioPatch( 901 audioPatchArray, 902 new AudioPortConfig[] { sourceConfig }, 903 sinkConfigs.toArray(new AudioPortConfig[sinkConfigs.size()])); 904 mAudioPatch = audioPatchArray[0]; 905 if (sourceGainConfig != null) { 906 mAudioManager.setAudioPortGain(mAudioSource, sourceGainConfig); 907 } 908 } 909 } 910 911 @Override 912 public void setStreamVolume(float volume) throws RemoteException { 913 synchronized (mImplLock) { 914 if (mReleased) { 915 throw new IllegalStateException("Device already released."); 916 } 917 mSourceVolume = volume; 918 updateAudioConfigLocked(); 919 } 920 } 921 922 @Override 923 public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException { 924 synchronized (mImplLock) { 925 if (mReleased) { 926 throw new IllegalStateException("Device already released."); 927 } 928 } 929 if (mInfo.getType() != TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) { 930 return false; 931 } 932 // TODO(hdmi): mHdmiClient.sendKeyEvent(event); 933 return false; 934 } 935 936 private boolean startCapture(Surface surface, TvStreamConfig config) { 937 synchronized (mImplLock) { 938 if (mReleased) { 939 return false; 940 } 941 if (surface == null || config == null) { 942 return false; 943 } 944 if (config.getType() != TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) { 945 return false; 946 } 947 948 int result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config); 949 return result == TvInputHal.SUCCESS; 950 } 951 } 952 953 private boolean stopCapture(TvStreamConfig config) { 954 synchronized (mImplLock) { 955 if (mReleased) { 956 return false; 957 } 958 if (config == null) { 959 return false; 960 } 961 962 int result = mHal.removeStream(mInfo.getDeviceId(), config); 963 return result == TvInputHal.SUCCESS; 964 } 965 } 966 967 private boolean updateAudioSourceLocked() { 968 if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) { 969 return false; 970 } 971 AudioDevicePort previousSource = mAudioSource; 972 mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress()); 973 return mAudioSource == null ? (previousSource != null) 974 : !mAudioSource.equals(previousSource); 975 } 976 977 private boolean updateAudioSinkLocked() { 978 if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) { 979 return false; 980 } 981 List<AudioDevicePort> previousSink = mAudioSink; 982 mAudioSink = new ArrayList<>(); 983 if (mOverrideAudioType == AudioManager.DEVICE_NONE) { 984 findAudioSinkFromAudioPolicy(mAudioSink); 985 } else { 986 AudioDevicePort audioSink = 987 findAudioDevicePort(mOverrideAudioType, mOverrideAudioAddress); 988 if (audioSink != null) { 989 mAudioSink.add(audioSink); 990 } 991 } 992 993 // Returns true if mAudioSink and previousSink differs. 994 if (mAudioSink.size() != previousSink.size()) { 995 return true; 996 } 997 previousSink.removeAll(mAudioSink); 998 return !previousSink.isEmpty(); 999 } 1000 1001 private void handleAudioSinkUpdated() { 1002 synchronized (mImplLock) { 1003 updateAudioConfigLocked(); 1004 } 1005 } 1006 1007 @Override 1008 public void overrideAudioSink(int audioType, String audioAddress, int samplingRate, 1009 int channelMask, int format) { 1010 synchronized (mImplLock) { 1011 mOverrideAudioType = audioType; 1012 mOverrideAudioAddress = audioAddress; 1013 1014 mDesiredSamplingRate = samplingRate; 1015 mDesiredChannelMask = channelMask; 1016 mDesiredFormat = format; 1017 1018 updateAudioConfigLocked(); 1019 } 1020 } 1021 1022 public void onMediaStreamVolumeChanged() { 1023 synchronized (mImplLock) { 1024 updateAudioConfigLocked(); 1025 } 1026 } 1027 } 1028 1029 interface Listener { 1030 void onStateChanged(String inputId, int state); 1031 void onHardwareDeviceAdded(TvInputHardwareInfo info); 1032 void onHardwareDeviceRemoved(TvInputHardwareInfo info); 1033 void onHdmiDeviceAdded(HdmiDeviceInfo device); 1034 void onHdmiDeviceRemoved(HdmiDeviceInfo device); 1035 void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo device); 1036 } 1037 1038 private class ListenerHandler extends Handler { 1039 private static final int STATE_CHANGED = 1; 1040 private static final int HARDWARE_DEVICE_ADDED = 2; 1041 private static final int HARDWARE_DEVICE_REMOVED = 3; 1042 private static final int HDMI_DEVICE_ADDED = 4; 1043 private static final int HDMI_DEVICE_REMOVED = 5; 1044 private static final int HDMI_DEVICE_UPDATED = 6; 1045 1046 @Override 1047 public final void handleMessage(Message msg) { 1048 switch (msg.what) { 1049 case STATE_CHANGED: { 1050 String inputId = (String) msg.obj; 1051 int state = msg.arg1; 1052 mListener.onStateChanged(inputId, state); 1053 break; 1054 } 1055 case HARDWARE_DEVICE_ADDED: { 1056 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj; 1057 mListener.onHardwareDeviceAdded(info); 1058 break; 1059 } 1060 case HARDWARE_DEVICE_REMOVED: { 1061 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj; 1062 mListener.onHardwareDeviceRemoved(info); 1063 break; 1064 } 1065 case HDMI_DEVICE_ADDED: { 1066 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj; 1067 mListener.onHdmiDeviceAdded(info); 1068 break; 1069 } 1070 case HDMI_DEVICE_REMOVED: { 1071 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj; 1072 mListener.onHdmiDeviceRemoved(info); 1073 break; 1074 } 1075 case HDMI_DEVICE_UPDATED: { 1076 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj; 1077 String inputId; 1078 synchronized (mLock) { 1079 inputId = mHdmiInputIdMap.get(info.getId()); 1080 } 1081 if (inputId != null) { 1082 mListener.onHdmiDeviceUpdated(inputId, info); 1083 } else { 1084 Slog.w(TAG, "Could not resolve input ID matching the device info; " 1085 + "ignoring."); 1086 } 1087 break; 1088 } 1089 default: { 1090 Slog.w(TAG, "Unhandled message: " + msg); 1091 break; 1092 } 1093 } 1094 } 1095 } 1096 1097 // Listener implementations for HdmiControlService 1098 1099 private final class HdmiHotplugEventListener extends IHdmiHotplugEventListener.Stub { 1100 @Override 1101 public void onReceived(HdmiHotplugEvent event) { 1102 synchronized (mLock) { 1103 mHdmiStateMap.put(event.getPort(), event.isConnected()); 1104 TvInputHardwareInfo hardwareInfo = 1105 findHardwareInfoForHdmiPortLocked(event.getPort()); 1106 if (hardwareInfo == null) { 1107 return; 1108 } 1109 String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId()); 1110 if (inputId == null) { 1111 return; 1112 } 1113 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, 1114 convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget(); 1115 } 1116 } 1117 } 1118 1119 private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub { 1120 @Override 1121 public void onStatusChanged(HdmiDeviceInfo deviceInfo, int status) { 1122 if (!deviceInfo.isSourceType()) return; 1123 synchronized (mLock) { 1124 int messageType = 0; 1125 Object obj = null; 1126 switch (status) { 1127 case HdmiControlManager.DEVICE_EVENT_ADD_DEVICE: { 1128 if (findHdmiDeviceInfo(deviceInfo.getId()) == null) { 1129 mHdmiDeviceList.add(deviceInfo); 1130 } else { 1131 Slog.w(TAG, "The list already contains " + deviceInfo + "; ignoring."); 1132 return; 1133 } 1134 messageType = ListenerHandler.HDMI_DEVICE_ADDED; 1135 obj = deviceInfo; 1136 break; 1137 } 1138 case HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE: { 1139 HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId()); 1140 if (!mHdmiDeviceList.remove(originalDeviceInfo)) { 1141 Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring."); 1142 return; 1143 } 1144 messageType = ListenerHandler.HDMI_DEVICE_REMOVED; 1145 obj = deviceInfo; 1146 break; 1147 } 1148 case HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE: { 1149 HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId()); 1150 if (!mHdmiDeviceList.remove(originalDeviceInfo)) { 1151 Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring."); 1152 return; 1153 } 1154 mHdmiDeviceList.add(deviceInfo); 1155 messageType = ListenerHandler.HDMI_DEVICE_UPDATED; 1156 obj = deviceInfo; 1157 break; 1158 } 1159 } 1160 1161 Message msg = mHandler.obtainMessage(messageType, 0, 0, obj); 1162 if (findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId()) != null) { 1163 msg.sendToTarget(); 1164 } else { 1165 mPendingHdmiDeviceEvents.add(msg); 1166 } 1167 } 1168 } 1169 1170 private HdmiDeviceInfo findHdmiDeviceInfo(int id) { 1171 for (HdmiDeviceInfo info : mHdmiDeviceList) { 1172 if (info.getId() == id) { 1173 return info; 1174 } 1175 } 1176 return null; 1177 } 1178 } 1179 1180 private final class HdmiSystemAudioModeChangeListener extends 1181 IHdmiSystemAudioModeChangeListener.Stub { 1182 @Override 1183 public void onStatusChanged(boolean enabled) throws RemoteException { 1184 synchronized (mLock) { 1185 for (int i = 0; i < mConnections.size(); ++i) { 1186 TvInputHardwareImpl impl = mConnections.valueAt(i).getHardwareImplLocked(); 1187 if (impl != null) { 1188 impl.handleAudioSinkUpdated(); 1189 } 1190 } 1191 } 1192 } 1193 } 1194} 1195