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