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