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