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