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