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