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 setPictureProfile(String inputId, long profileHandle)599 public boolean setPictureProfile(String inputId, long profileHandle) { 600 synchronized (mLock) { 601 int deviceId = findDeviceIdForInputIdLocked(inputId); 602 if (deviceId < 0) { 603 Slog.e(TAG, "Invalid inputId : " + inputId); 604 return false; 605 } 606 607 Connection connection = mConnections.get(deviceId); 608 boolean success = true; 609 for (TvStreamConfig config : connection.getConfigsLocked()) { 610 success = success 611 && mHal.setPictureProfile(deviceId, config, profileHandle) 612 == TvInputHal.SUCCESS; 613 } 614 615 return success; 616 } 617 } 618 619 /** 620 * Take a snapshot of the given TV input into the provided Surface. 621 */ captureFrame(String inputId, Surface surface, final TvStreamConfig config, int callingUid, int resolvedUserId)622 public boolean captureFrame(String inputId, Surface surface, final TvStreamConfig config, 623 int callingUid, int resolvedUserId) { 624 synchronized (mLock) { 625 int deviceId = findDeviceIdForInputIdLocked(inputId); 626 if (deviceId < 0) { 627 Slog.e(TAG, "Invalid inputId : " + inputId); 628 return false; 629 } 630 Connection connection = mConnections.get(deviceId); 631 final TvInputHardwareImpl hardwareImpl = connection.getHardwareImplLocked(); 632 if (hardwareImpl != null) { 633 // Stop previous capture. 634 Runnable runnable = connection.getOnFirstFrameCapturedLocked(); 635 if (runnable != null) { 636 runnable.run(); 637 connection.setOnFirstFrameCapturedLocked(null); 638 } 639 640 boolean result = hardwareImpl.startCapture(surface, config); 641 if (result) { 642 connection.setOnFirstFrameCapturedLocked(new Runnable() { 643 @Override 644 public void run() { 645 hardwareImpl.stopCapture(config); 646 } 647 }); 648 } 649 return result; 650 } 651 } 652 return false; 653 } 654 655 @GuardedBy("mLock") processPendingHdmiDeviceEventsLocked()656 private void processPendingHdmiDeviceEventsLocked() { 657 for (Iterator<Message> it = mPendingHdmiDeviceEvents.iterator(); it.hasNext(); ) { 658 Message msg = it.next(); 659 HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj; 660 TvInputHardwareInfo hardwareInfo = 661 findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId()); 662 if (hardwareInfo != null) { 663 msg.sendToTarget(); 664 it.remove(); 665 } 666 } 667 } 668 669 670 @GuardedBy("mLock") processPendingTvInputInfoEventsLocked()671 private void processPendingTvInputInfoEventsLocked() { 672 for (Iterator<Message> it = mPendingTvinputInfoEvents.iterator(); it.hasNext(); ) { 673 Message msg = it.next(); 674 int deviceId = msg.arg1; 675 String inputId = mHardwareInputIdMap.get(deviceId); 676 if (inputId != null) { 677 msg.sendToTarget(); 678 it.remove(); 679 } 680 } 681 } 682 683 updateVolume()684 private void updateVolume() { 685 mCurrentMaxIndex = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 686 mCurrentIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); 687 } 688 handleVolumeChange(Context context, Intent intent)689 private void handleVolumeChange(Context context, Intent intent) { 690 String action = intent.getAction(); 691 switch (action) { 692 case AudioManager.VOLUME_CHANGED_ACTION: { 693 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); 694 if (streamType != AudioManager.STREAM_MUSIC) { 695 return; 696 } 697 int index = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0); 698 if (index == mCurrentIndex) { 699 return; 700 } 701 mCurrentIndex = index; 702 break; 703 } 704 case AudioManager.STREAM_MUTE_CHANGED_ACTION: { 705 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); 706 if (streamType != AudioManager.STREAM_MUSIC) { 707 return; 708 } 709 // volume index will be updated at onMediaStreamVolumeChanged() through 710 // updateVolume(). 711 break; 712 } 713 default: 714 Slog.w(TAG, "Unrecognized intent: " + intent); 715 return; 716 } 717 synchronized (mLock) { 718 for (int i = 0; i < mConnections.size(); ++i) { 719 TvInputHardwareImpl hardwareImpl = mConnections.valueAt(i).getHardwareImplLocked(); 720 if (hardwareImpl != null) { 721 hardwareImpl.onMediaStreamVolumeChanged(); 722 } 723 } 724 } 725 } 726 getMediaStreamVolume()727 private float getMediaStreamVolume() { 728 return (float) mCurrentIndex / (float) mCurrentMaxIndex; 729 } 730 dump(FileDescriptor fd, final PrintWriter writer, String[] args)731 public void dump(FileDescriptor fd, final PrintWriter writer, String[] args) { 732 final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); 733 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; 734 735 synchronized (mLock) { 736 pw.println("TvInputHardwareManager Info:"); 737 pw.increaseIndent(); 738 pw.println("mConnections: deviceId -> Connection"); 739 pw.increaseIndent(); 740 for (int i = 0; i < mConnections.size(); i++) { 741 int deviceId = mConnections.keyAt(i); 742 Connection mConnection = mConnections.valueAt(i); 743 pw.println(deviceId + ": " + mConnection); 744 745 } 746 pw.decreaseIndent(); 747 748 pw.println("mHardwareList:"); 749 pw.increaseIndent(); 750 for (TvInputHardwareInfo tvInputHardwareInfo : mHardwareList) { 751 pw.println(tvInputHardwareInfo); 752 } 753 pw.decreaseIndent(); 754 755 pw.println("mHdmiDeviceList:"); 756 pw.increaseIndent(); 757 for (HdmiDeviceInfo hdmiDeviceInfo : mHdmiDeviceList) { 758 pw.println(hdmiDeviceInfo); 759 } 760 pw.decreaseIndent(); 761 762 pw.println("mHardwareInputIdMap: deviceId -> inputId"); 763 pw.increaseIndent(); 764 for (int i = 0 ; i < mHardwareInputIdMap.size(); i++) { 765 int deviceId = mHardwareInputIdMap.keyAt(i); 766 String inputId = mHardwareInputIdMap.valueAt(i); 767 pw.println(deviceId + ": " + inputId); 768 } 769 pw.decreaseIndent(); 770 771 pw.println("mHdmiInputIdMap: id -> inputId"); 772 pw.increaseIndent(); 773 for (int i = 0; i < mHdmiInputIdMap.size(); i++) { 774 int id = mHdmiInputIdMap.keyAt(i); 775 String inputId = mHdmiInputIdMap.valueAt(i); 776 pw.println(id + ": " + inputId); 777 } 778 pw.decreaseIndent(); 779 780 pw.println("mInputMap: inputId -> inputInfo"); 781 pw.increaseIndent(); 782 for(Map.Entry<String, TvInputInfo> entry : mInputMap.entrySet()) { 783 pw.println(entry.getKey() + ": " + entry.getValue()); 784 } 785 pw.decreaseIndent(); 786 pw.decreaseIndent(); 787 } 788 } 789 790 private class Connection implements IBinder.DeathRecipient { 791 private TvInputHardwareInfo mHardwareInfo; 792 private TvInputInfo mInfo; 793 private TvInputHardwareImpl mHardware = null; 794 private ITvInputHardwareCallback mCallback; 795 private TvStreamConfig[] mConfigs = null; 796 private Integer mCallingUid = null; 797 private Integer mResolvedUserId = null; 798 private Runnable mOnFirstFrameCaptured; 799 private ResourceClientProfile mResourceClientProfile = null; 800 private boolean mIsCableConnectionStatusUpdated = false; 801 Connection(TvInputHardwareInfo hardwareInfo)802 public Connection(TvInputHardwareInfo hardwareInfo) { 803 mHardwareInfo = hardwareInfo; 804 } 805 806 // *Locked methods assume TvInputHardwareManager.mLock is held. 807 808 @GuardedBy("mLock") resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback, TvInputInfo info, Integer callingUid, Integer resolvedUserId, ResourceClientProfile profile)809 public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback, 810 TvInputInfo info, Integer callingUid, Integer resolvedUserId, 811 ResourceClientProfile profile) { 812 if (mHardware != null) { 813 try { 814 mCallback.onReleased(); 815 } catch (RemoteException e) { 816 Slog.e(TAG, "error in Connection::resetLocked", e); 817 } 818 mHardware.release(); 819 } 820 mHardware = hardware; 821 mCallback = callback; 822 mInfo = info; 823 mCallingUid = callingUid; 824 mResolvedUserId = resolvedUserId; 825 mOnFirstFrameCaptured = null; 826 mResourceClientProfile = profile; 827 828 if (mHardware != null && mCallback != null) { 829 try { 830 mCallback.onStreamConfigChanged(getConfigsLocked()); 831 } catch (RemoteException e) { 832 Slog.e(TAG, "error in Connection::resetLocked", e); 833 } 834 } 835 } 836 837 @GuardedBy("mLock") updateConfigsLocked(TvStreamConfig[] configs)838 public void updateConfigsLocked(TvStreamConfig[] configs) { 839 mConfigs = configs; 840 } 841 842 @GuardedBy("mLock") getHardwareInfoLocked()843 public TvInputHardwareInfo getHardwareInfoLocked() { 844 return mHardwareInfo; 845 } 846 847 @GuardedBy("mLock") getInfoLocked()848 public TvInputInfo getInfoLocked() { 849 return mInfo; 850 } 851 852 @GuardedBy("mLock") getHardwareLocked()853 public ITvInputHardware getHardwareLocked() { 854 return mHardware; 855 } 856 857 @GuardedBy("mLock") getHardwareImplLocked()858 public TvInputHardwareImpl getHardwareImplLocked() { 859 return mHardware; 860 } 861 862 @GuardedBy("mLock") getCallbackLocked()863 public ITvInputHardwareCallback getCallbackLocked() { 864 return mCallback; 865 } 866 getConfigsLocked()867 public TvStreamConfig[] getConfigsLocked() { 868 return mConfigs; 869 } 870 871 @GuardedBy("mLock") getCallingUidLocked()872 public Integer getCallingUidLocked() { 873 return mCallingUid; 874 } 875 876 @GuardedBy("mLock") getResolvedUserIdLocked()877 public Integer getResolvedUserIdLocked() { 878 return mResolvedUserId; 879 } 880 881 @GuardedBy("mLock") setOnFirstFrameCapturedLocked(Runnable runnable)882 public void setOnFirstFrameCapturedLocked(Runnable runnable) { 883 mOnFirstFrameCaptured = runnable; 884 } 885 886 @GuardedBy("mLock") getOnFirstFrameCapturedLocked()887 public Runnable getOnFirstFrameCapturedLocked() { 888 return mOnFirstFrameCaptured; 889 } 890 891 @GuardedBy("mLock") getResourceClientProfileLocked()892 public ResourceClientProfile getResourceClientProfileLocked() { 893 return mResourceClientProfile; 894 } 895 896 @Override binderDied()897 public void binderDied() { 898 synchronized (mLock) { 899 resetLocked(null, null, null, null, null, null); 900 } 901 } 902 toString()903 public String toString() { 904 return "Connection{" 905 + " mHardwareInfo: " + mHardwareInfo 906 + ", mInfo: " + mInfo 907 + ", mCallback: " + mCallback 908 + ", mHardware: " + mHardware 909 + ", mConfigs: " + Arrays.toString(mConfigs) 910 + ", mCallingUid: " + mCallingUid 911 + ", mResolvedUserId: " + mResolvedUserId 912 + ", mResourceClientProfile: " + mResourceClientProfile 913 + " }"; 914 } 915 916 @GuardedBy("mLock") updateCableConnectionStatusLocked(int cableConnectionStatus)917 public boolean updateCableConnectionStatusLocked(int cableConnectionStatus) { 918 // Update connection status only if it's not default value 919 if (cableConnectionStatus != TvInputHardwareInfo.CABLE_CONNECTION_STATUS_UNKNOWN 920 || mIsCableConnectionStatusUpdated) { 921 mIsCableConnectionStatusUpdated = true; 922 mHardwareInfo = mHardwareInfo.toBuilder() 923 .cableConnectionStatus(cableConnectionStatus).build(); 924 } 925 return mIsCableConnectionStatusUpdated; 926 } 927 928 @GuardedBy("mLock") getConfigsLengthLocked()929 private int getConfigsLengthLocked() { 930 return mConfigs == null ? 0 : mConfigs.length; 931 } 932 933 @GuardedBy("mLock") getInputStateLocked()934 private int getInputStateLocked() { 935 int configsLength = getConfigsLengthLocked(); 936 if (configsLength > 0) { 937 if (!mIsCableConnectionStatusUpdated) { 938 return INPUT_STATE_CONNECTED; 939 } 940 } 941 switch (mHardwareInfo.getCableConnectionStatus()) { 942 case TvInputHardwareInfo.CABLE_CONNECTION_STATUS_CONNECTED: 943 return INPUT_STATE_CONNECTED; 944 case TvInputHardwareInfo.CABLE_CONNECTION_STATUS_DISCONNECTED: 945 return INPUT_STATE_DISCONNECTED; 946 case TvInputHardwareInfo.CABLE_CONNECTION_STATUS_UNKNOWN: 947 default: 948 return INPUT_STATE_CONNECTED_STANDBY; 949 } 950 } 951 } 952 953 private class TvInputHardwareImpl extends ITvInputHardware.Stub { 954 private final TvInputHardwareInfo mInfo; 955 private final Object mImplLock = new Object(); 956 957 private final AudioManager.OnAudioPortUpdateListener mAudioListener = 958 new AudioManager.OnAudioPortUpdateListener() { 959 @Override 960 public void onAudioPortListUpdate(AudioPort[] portList) { 961 synchronized (mImplLock) { 962 updateAudioConfigLocked(); 963 } 964 } 965 966 @Override 967 public void onAudioPatchListUpdate(AudioPatch[] patchList) { 968 // No-op 969 } 970 971 @Override 972 public void onServiceDied() { 973 synchronized (mImplLock) { 974 mAudioSource = null; 975 mAudioSink.clear(); 976 if (mAudioPatch != null) { 977 mAudioManager.releaseAudioPatch(mAudioPatch); 978 mAudioPatch = null; 979 } 980 } 981 } 982 }; 983 @GuardedBy("mImplLock") 984 private boolean mReleased = false; 985 @GuardedBy("mImplLock") 986 private int mOverrideAudioType = AudioManager.DEVICE_NONE; 987 @GuardedBy("mImplLock") 988 private String mOverrideAudioAddress = ""; 989 @GuardedBy("mImplLock") 990 private AudioDevicePort mAudioSource; 991 @GuardedBy("mImplLock") 992 private List<AudioDevicePort> mAudioSink = new ArrayList<>(); 993 @GuardedBy("mImplLock") 994 private AudioPatch mAudioPatch = null; 995 // Set to an invalid value for a volume, so that current volume can be applied at the 996 // first call to updateAudioConfigLocked(). 997 @GuardedBy("mImplLock") 998 private float mCommittedVolume = -1f; 999 @GuardedBy("mImplLock") 1000 private float mSourceVolume = 0.0f; 1001 1002 @GuardedBy("mImplLock") 1003 private TvStreamConfig mActiveConfig = null; 1004 1005 @GuardedBy("mImplLock") 1006 private int mDesiredSamplingRate = 0; 1007 @GuardedBy("mImplLock") 1008 private int mDesiredChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT; 1009 @GuardedBy("mImplLock") 1010 private int mDesiredFormat = AudioFormat.ENCODING_DEFAULT; 1011 TvInputHardwareImpl(TvInputHardwareInfo info)1012 public TvInputHardwareImpl(TvInputHardwareInfo info) { 1013 mInfo = info; 1014 mAudioManager.registerAudioPortUpdateListener(mAudioListener); 1015 if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) { 1016 synchronized (mImplLock) { 1017 mAudioSource = 1018 findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress()); 1019 findAudioSinkFromAudioPolicy(mAudioSink); 1020 } 1021 } 1022 } 1023 findAudioSinkFromAudioPolicy(List<AudioDevicePort> sinks)1024 private void findAudioSinkFromAudioPolicy(List<AudioDevicePort> sinks) { 1025 sinks.clear(); 1026 ArrayList<AudioDevicePort> devicePorts = new ArrayList<>(); 1027 if (mAudioManager.listAudioDevicePorts(devicePorts) != AudioManager.SUCCESS) { 1028 return; 1029 } 1030 int sinkDevice = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC); 1031 for (AudioDevicePort port : devicePorts) { 1032 if ((port.type() & sinkDevice) != 0 && 1033 !AudioSystem.isInputDevice(port.type())) { 1034 sinks.add(port); 1035 } 1036 } 1037 } 1038 findAudioDevicePort(int type, String address)1039 private AudioDevicePort findAudioDevicePort(int type, String address) { 1040 if (type == AudioManager.DEVICE_NONE) { 1041 return null; 1042 } 1043 ArrayList<AudioDevicePort> devicePorts = new ArrayList<>(); 1044 if (mAudioManager.listAudioDevicePorts(devicePorts) != AudioManager.SUCCESS) { 1045 return null; 1046 } 1047 for (AudioDevicePort port : devicePorts) { 1048 if (port.type() == type && port.address().equals(address)) { 1049 return port; 1050 } 1051 } 1052 return null; 1053 } 1054 release()1055 public void release() { 1056 synchronized (mImplLock) { 1057 mAudioManager.unregisterAudioPortUpdateListener(mAudioListener); 1058 if (mAudioPatch != null) { 1059 mAudioManager.releaseAudioPatch(mAudioPatch); 1060 mAudioPatch = null; 1061 } 1062 mReleased = true; 1063 } 1064 } 1065 1066 // A TvInputHardwareImpl object holds only one active session. Therefore, if a client 1067 // attempts to call setSurface with different TvStreamConfig objects, the last call will 1068 // prevail. 1069 @Override setSurface(Surface surface, TvStreamConfig config)1070 public boolean setSurface(Surface surface, TvStreamConfig config) 1071 throws RemoteException { 1072 synchronized (mImplLock) { 1073 if (mReleased) { 1074 throw new IllegalStateException("Device already released."); 1075 } 1076 1077 int result = TvInputHal.SUCCESS; 1078 if (surface == null) { 1079 // The value of config is ignored when surface == null. 1080 if (mActiveConfig != null) { 1081 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig); 1082 mActiveConfig = null; 1083 } else { 1084 // We already have no active stream. 1085 return true; 1086 } 1087 } else { 1088 // It's impossible to set a non-null surface with a null config. 1089 if (config == null) { 1090 return false; 1091 } 1092 // Remove stream only if we have an existing active configuration. 1093 if (mActiveConfig != null && !config.equals(mActiveConfig)) { 1094 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig); 1095 if (result != TvInputHal.SUCCESS) { 1096 mActiveConfig = null; 1097 } 1098 } 1099 // Proceed only if all previous operations succeeded. 1100 if (result == TvInputHal.SUCCESS) { 1101 result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config); 1102 if (result == TvInputHal.SUCCESS) { 1103 mActiveConfig = config; 1104 } 1105 } 1106 } 1107 updateAudioConfigLocked(); 1108 return result == TvInputHal.SUCCESS; 1109 } 1110 } 1111 1112 /** 1113 * Update audio configuration (source, sink, patch) all up to current state. 1114 */ 1115 @GuardedBy("mImplLock") updateAudioConfigLocked()1116 private void updateAudioConfigLocked() { 1117 boolean sinkUpdated = updateAudioSinkLocked(); 1118 boolean sourceUpdated = updateAudioSourceLocked(); 1119 // We can't do updated = updateAudioSinkLocked() || updateAudioSourceLocked() here 1120 // because Java won't evaluate the latter if the former is true. 1121 1122 if (mAudioSource == null || mAudioSink.isEmpty() || mActiveConfig == null) { 1123 if (mAudioPatch != null) { 1124 mAudioManager.releaseAudioPatch(mAudioPatch); 1125 mAudioPatch = null; 1126 } 1127 return; 1128 } 1129 1130 updateVolume(); 1131 float volume = mSourceVolume * getMediaStreamVolume(); 1132 AudioGainConfig sourceGainConfig = null; 1133 if (mAudioSource.gains().length > 0 && volume != mCommittedVolume) { 1134 AudioGain sourceGain = null; 1135 for (AudioGain gain : mAudioSource.gains()) { 1136 if ((gain.mode() & AudioGain.MODE_JOINT) != 0) { 1137 sourceGain = gain; 1138 break; 1139 } 1140 } 1141 // NOTE: we only change the source gain in MODE_JOINT here. 1142 if (sourceGain != null) { 1143 int steps = (sourceGain.maxValue() - sourceGain.minValue()) 1144 / sourceGain.stepValue(); 1145 int gainValue = sourceGain.minValue(); 1146 if (volume < 1.0f) { 1147 gainValue += sourceGain.stepValue() * (int) (volume * steps + 0.5); 1148 } else { 1149 gainValue = sourceGain.maxValue(); 1150 } 1151 // size of gain values is 1 in MODE_JOINT 1152 int[] gainValues = new int[] { gainValue }; 1153 sourceGainConfig = sourceGain.buildConfig(AudioGain.MODE_JOINT, 1154 sourceGain.channelMask(), gainValues, 0); 1155 } else { 1156 Slog.w(TAG, "No audio source gain with MODE_JOINT support exists."); 1157 } 1158 } 1159 1160 AudioPortConfig sourceConfig = mAudioSource.activeConfig(); 1161 List<AudioPortConfig> sinkConfigs = new ArrayList<>(); 1162 AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch }; 1163 boolean shouldRecreateAudioPatch = sourceUpdated || sinkUpdated || mAudioPatch == null; 1164 1165 for (AudioDevicePort audioSink : mAudioSink) { 1166 AudioPortConfig sinkConfig = audioSink.activeConfig(); 1167 int sinkSamplingRate = mDesiredSamplingRate; 1168 int sinkChannelMask = mDesiredChannelMask; 1169 int sinkFormat = mDesiredFormat; 1170 // If sinkConfig != null and values are set to default, 1171 // fill in the sinkConfig values. 1172 if (sinkConfig != null) { 1173 if (sinkSamplingRate == 0) { 1174 sinkSamplingRate = sinkConfig.samplingRate(); 1175 } 1176 if (sinkChannelMask == AudioFormat.CHANNEL_OUT_DEFAULT) { 1177 sinkChannelMask = sinkConfig.channelMask(); 1178 } 1179 if (sinkFormat == AudioFormat.ENCODING_DEFAULT) { 1180 sinkFormat = sinkConfig.format(); 1181 } 1182 } 1183 1184 if (sinkConfig == null 1185 || sinkConfig.samplingRate() != sinkSamplingRate 1186 || sinkConfig.channelMask() != sinkChannelMask 1187 || sinkConfig.format() != sinkFormat) { 1188 // Check for compatibility and reset to default if necessary. 1189 if (!intArrayContains(audioSink.samplingRates(), sinkSamplingRate) 1190 && audioSink.samplingRates().length > 0) { 1191 sinkSamplingRate = audioSink.samplingRates()[0]; 1192 } 1193 if (!intArrayContains(audioSink.channelMasks(), sinkChannelMask)) { 1194 sinkChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT; 1195 } 1196 if (!intArrayContains(audioSink.formats(), sinkFormat)) { 1197 sinkFormat = AudioFormat.ENCODING_DEFAULT; 1198 } 1199 sinkConfig = audioSink.buildConfig(sinkSamplingRate, sinkChannelMask, 1200 sinkFormat, null); 1201 shouldRecreateAudioPatch = true; 1202 } 1203 sinkConfigs.add(sinkConfig); 1204 } 1205 // sinkConfigs.size() == mAudioSink.size(), and mAudioSink is guaranteed to be 1206 // non-empty at the beginning of this method. 1207 AudioPortConfig sinkConfig = sinkConfigs.get(0); 1208 if (sourceConfig == null || sourceGainConfig != null) { 1209 int sourceSamplingRate = 0; 1210 if (intArrayContains(mAudioSource.samplingRates(), sinkConfig.samplingRate())) { 1211 sourceSamplingRate = sinkConfig.samplingRate(); 1212 } else if (mAudioSource.samplingRates().length > 0) { 1213 // Use any sampling rate and hope audio patch can handle resampling... 1214 sourceSamplingRate = mAudioSource.samplingRates()[0]; 1215 } 1216 int sourceChannelMask = AudioFormat.CHANNEL_IN_DEFAULT; 1217 for (int inChannelMask : mAudioSource.channelMasks()) { 1218 if (AudioFormat.channelCountFromOutChannelMask(sinkConfig.channelMask()) 1219 == AudioFormat.channelCountFromInChannelMask(inChannelMask)) { 1220 sourceChannelMask = inChannelMask; 1221 break; 1222 } 1223 } 1224 int sourceFormat = AudioFormat.ENCODING_DEFAULT; 1225 if (intArrayContains(mAudioSource.formats(), sinkConfig.format())) { 1226 sourceFormat = sinkConfig.format(); 1227 } 1228 sourceConfig = mAudioSource.buildConfig(sourceSamplingRate, sourceChannelMask, 1229 sourceFormat, sourceGainConfig); 1230 shouldRecreateAudioPatch = true; 1231 } 1232 if (shouldRecreateAudioPatch) { 1233 mCommittedVolume = volume; 1234 // only recreate if something was updated or audioPath is null 1235 if (mAudioPatch == null || sinkUpdated ||sourceUpdated ) { 1236 if (mAudioPatch != null) { 1237 mAudioManager.releaseAudioPatch(mAudioPatch); 1238 audioPatchArray[0] = null; 1239 } 1240 mAudioManager.createAudioPatch( 1241 audioPatchArray, 1242 new AudioPortConfig[] { sourceConfig }, 1243 sinkConfigs.toArray(new AudioPortConfig[sinkConfigs.size()])); 1244 mAudioPatch = audioPatchArray[0]; 1245 } 1246 } 1247 1248 if (sourceGainConfig != null) { 1249 mAudioManager.setAudioPortGain(mAudioSource, sourceGainConfig); 1250 } 1251 } 1252 1253 @Override setStreamVolume(float volume)1254 public void setStreamVolume(float volume) throws RemoteException { 1255 synchronized (mImplLock) { 1256 if (mReleased) { 1257 throw new IllegalStateException("Device already released."); 1258 } 1259 mSourceVolume = volume; 1260 updateAudioConfigLocked(); 1261 } 1262 } 1263 startCapture(Surface surface, TvStreamConfig config)1264 private boolean startCapture(Surface surface, TvStreamConfig config) { 1265 synchronized (mImplLock) { 1266 if (mReleased) { 1267 return false; 1268 } 1269 if (surface == null || config == null) { 1270 return false; 1271 } 1272 if (config.getType() != TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) { 1273 return false; 1274 } 1275 1276 int result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config); 1277 return result == TvInputHal.SUCCESS; 1278 } 1279 } 1280 stopCapture(TvStreamConfig config)1281 private boolean stopCapture(TvStreamConfig config) { 1282 synchronized (mImplLock) { 1283 if (mReleased) { 1284 return false; 1285 } 1286 if (config == null) { 1287 return false; 1288 } 1289 1290 int result = mHal.removeStream(mInfo.getDeviceId(), config); 1291 return result == TvInputHal.SUCCESS; 1292 } 1293 } 1294 1295 @GuardedBy("mImplLock") updateAudioSourceLocked()1296 private boolean updateAudioSourceLocked() { 1297 if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) { 1298 return false; 1299 } 1300 AudioDevicePort previousSource = mAudioSource; 1301 mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress()); 1302 return mAudioSource == null ? (previousSource != null) 1303 : !mAudioSource.equals(previousSource); 1304 } 1305 1306 @GuardedBy("mImplLock") updateAudioSinkLocked()1307 private boolean updateAudioSinkLocked() { 1308 if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) { 1309 return false; 1310 } 1311 List<AudioDevicePort> previousSink = mAudioSink; 1312 mAudioSink = new ArrayList<>(); 1313 if (mOverrideAudioType == AudioManager.DEVICE_NONE) { 1314 findAudioSinkFromAudioPolicy(mAudioSink); 1315 } else { 1316 AudioDevicePort audioSink = 1317 findAudioDevicePort(mOverrideAudioType, mOverrideAudioAddress); 1318 if (audioSink != null) { 1319 mAudioSink.add(audioSink); 1320 } 1321 } 1322 1323 // Returns true if mAudioSink and previousSink differs. 1324 if (mAudioSink.size() != previousSink.size()) { 1325 return true; 1326 } 1327 previousSink.removeAll(mAudioSink); 1328 return !previousSink.isEmpty(); 1329 } 1330 handleAudioSinkUpdated()1331 private void handleAudioSinkUpdated() { 1332 synchronized (mImplLock) { 1333 updateAudioConfigLocked(); 1334 } 1335 } 1336 1337 @Override overrideAudioSink(int audioType, String audioAddress, int samplingRate, int channelMask, int format)1338 public void overrideAudioSink(int audioType, String audioAddress, int samplingRate, 1339 int channelMask, int format) { 1340 synchronized (mImplLock) { 1341 mOverrideAudioType = audioType; 1342 mOverrideAudioAddress = audioAddress; 1343 1344 mDesiredSamplingRate = samplingRate; 1345 mDesiredChannelMask = channelMask; 1346 mDesiredFormat = format; 1347 1348 updateAudioConfigLocked(); 1349 } 1350 } 1351 onMediaStreamVolumeChanged()1352 public void onMediaStreamVolumeChanged() { 1353 synchronized (mImplLock) { 1354 updateAudioConfigLocked(); 1355 } 1356 } 1357 } 1358 1359 interface Listener { onStateChanged(String inputId, int state)1360 void onStateChanged(String inputId, int state); onHardwareDeviceAdded(TvInputHardwareInfo info)1361 void onHardwareDeviceAdded(TvInputHardwareInfo info); onHardwareDeviceRemoved(TvInputHardwareInfo info)1362 void onHardwareDeviceRemoved(TvInputHardwareInfo info); onHdmiDeviceAdded(HdmiDeviceInfo device)1363 void onHdmiDeviceAdded(HdmiDeviceInfo device); onHdmiDeviceRemoved(HdmiDeviceInfo device)1364 void onHdmiDeviceRemoved(HdmiDeviceInfo device); onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo device)1365 void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo device); onTvMessage(String inputId, int type, Bundle data)1366 void onTvMessage(String inputId, int type, Bundle data); 1367 } 1368 1369 private class ListenerHandler extends Handler { 1370 private static final int STATE_CHANGED = 1; 1371 private static final int HARDWARE_DEVICE_ADDED = 2; 1372 private static final int HARDWARE_DEVICE_REMOVED = 3; 1373 private static final int HDMI_DEVICE_ADDED = 4; 1374 private static final int HDMI_DEVICE_REMOVED = 5; 1375 private static final int HDMI_DEVICE_UPDATED = 6; 1376 private static final int TVINPUT_INFO_ADDED = 7; 1377 private static final int TV_MESSAGE_RECEIVED = 8; 1378 1379 @Override handleMessage(Message msg)1380 public final void handleMessage(Message msg) { 1381 switch (msg.what) { 1382 case STATE_CHANGED: { 1383 String inputId = (String) msg.obj; 1384 int state = msg.arg1; 1385 mListener.onStateChanged(inputId, state); 1386 break; 1387 } 1388 case HARDWARE_DEVICE_ADDED: { 1389 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj; 1390 mListener.onHardwareDeviceAdded(info); 1391 break; 1392 } 1393 case HARDWARE_DEVICE_REMOVED: { 1394 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj; 1395 mListener.onHardwareDeviceRemoved(info); 1396 break; 1397 } 1398 case HDMI_DEVICE_ADDED: { 1399 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj; 1400 mListener.onHdmiDeviceAdded(info); 1401 break; 1402 } 1403 case HDMI_DEVICE_REMOVED: { 1404 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj; 1405 mListener.onHdmiDeviceRemoved(info); 1406 break; 1407 } 1408 case HDMI_DEVICE_UPDATED: { 1409 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj; 1410 String inputId; 1411 synchronized (mLock) { 1412 inputId = mHdmiInputIdMap.get(info.getId()); 1413 } 1414 if (inputId != null) { 1415 mListener.onHdmiDeviceUpdated(inputId, info); 1416 } else { 1417 Slog.w(TAG, "Could not resolve input ID matching the device info; " 1418 + "ignoring."); 1419 } 1420 break; 1421 } 1422 case TVINPUT_INFO_ADDED: { 1423 int deviceId = msg.arg1; 1424 int cableConnectionStatus = msg.arg2; 1425 Connection connection =(Connection)msg.obj; 1426 1427 int previousConfigsLength = connection.getConfigsLengthLocked(); 1428 int previousCableConnectionStatus = connection.getInputStateLocked(); 1429 String inputId = mHardwareInputIdMap.get(deviceId); 1430 1431 if (inputId != null) { 1432 synchronized (mLock) { 1433 if (connection.updateCableConnectionStatusLocked( 1434 cableConnectionStatus)) { 1435 if (previousCableConnectionStatus 1436 != connection.getInputStateLocked()) { 1437 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, 1438 connection.getInputStateLocked(), 0, inputId) 1439 .sendToTarget(); 1440 } 1441 } else { 1442 if ((previousConfigsLength == 0) 1443 != (connection.getConfigsLengthLocked() == 0)) { 1444 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, 1445 connection.getInputStateLocked(), 0, inputId) 1446 .sendToTarget(); 1447 } 1448 } 1449 } 1450 } 1451 break; 1452 } 1453 case TV_MESSAGE_RECEIVED: { 1454 SomeArgs args = (SomeArgs) msg.obj; 1455 String inputId = (String) args.arg1; 1456 Bundle data = (Bundle) args.arg2; 1457 int type = msg.arg1; 1458 mListener.onTvMessage(inputId, type, data); 1459 args.recycle(); 1460 break; 1461 } 1462 default: { 1463 Slog.w(TAG, "Unhandled message: " + msg); 1464 break; 1465 } 1466 } 1467 } 1468 } 1469 1470 // Listener implementations for HdmiControlService 1471 1472 private final class HdmiHotplugEventListener extends IHdmiHotplugEventListener.Stub { 1473 @Override onReceived(HdmiHotplugEvent event)1474 public void onReceived(HdmiHotplugEvent event) { 1475 synchronized (mLock) { 1476 mHdmiStateMap.put(event.getPort(), event.isConnected()); 1477 TvInputHardwareInfo hardwareInfo = 1478 findHardwareInfoForHdmiPortLocked(event.getPort()); 1479 if (hardwareInfo == null) { 1480 return; 1481 } 1482 String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId()); 1483 if (inputId == null) { 1484 return; 1485 } 1486 // No HDMI hotplug does not necessarily mean disconnected, as old devices may 1487 // not report hotplug state correctly. Using INPUT_STATE_CONNECTED_STANDBY to 1488 // denote unknown state. 1489 int state = event.isConnected() 1490 ? INPUT_STATE_CONNECTED 1491 : INPUT_STATE_CONNECTED_STANDBY; 1492 mHandler.obtainMessage( 1493 ListenerHandler.STATE_CHANGED, state, 0, inputId).sendToTarget(); 1494 } 1495 } 1496 } 1497 1498 private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub { 1499 @Override onStatusChanged(HdmiDeviceInfo deviceInfo, int status)1500 public void onStatusChanged(HdmiDeviceInfo deviceInfo, int status) { 1501 if (!deviceInfo.isSourceType()) return; 1502 synchronized (mLock) { 1503 int messageType = 0; 1504 Object obj = null; 1505 switch (status) { 1506 case HdmiControlManager.DEVICE_EVENT_ADD_DEVICE: { 1507 if (findHdmiDeviceInfo(deviceInfo.getId()) == null) { 1508 mHdmiDeviceList.add(deviceInfo); 1509 } else { 1510 Slog.w(TAG, "The list already contains " + deviceInfo + "; ignoring."); 1511 return; 1512 } 1513 messageType = ListenerHandler.HDMI_DEVICE_ADDED; 1514 obj = deviceInfo; 1515 break; 1516 } 1517 case HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE: { 1518 HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId()); 1519 if (!mHdmiDeviceList.remove(originalDeviceInfo)) { 1520 Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring."); 1521 return; 1522 } 1523 messageType = ListenerHandler.HDMI_DEVICE_REMOVED; 1524 obj = deviceInfo; 1525 break; 1526 } 1527 case HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE: { 1528 HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId()); 1529 if (!mHdmiDeviceList.remove(originalDeviceInfo)) { 1530 Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring."); 1531 return; 1532 } 1533 mHdmiDeviceList.add(deviceInfo); 1534 messageType = ListenerHandler.HDMI_DEVICE_UPDATED; 1535 obj = deviceInfo; 1536 break; 1537 } 1538 } 1539 1540 Message msg = mHandler.obtainMessage(messageType, 0, 0, obj); 1541 if (findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId()) != null) { 1542 msg.sendToTarget(); 1543 } else { 1544 mPendingHdmiDeviceEvents.add(msg); 1545 } 1546 } 1547 } 1548 findHdmiDeviceInfo(int id)1549 private HdmiDeviceInfo findHdmiDeviceInfo(int id) { 1550 for (HdmiDeviceInfo info : mHdmiDeviceList) { 1551 if (info.getId() == id) { 1552 return info; 1553 } 1554 } 1555 return null; 1556 } 1557 } 1558 1559 private final class HdmiSystemAudioModeChangeListener extends 1560 IHdmiSystemAudioModeChangeListener.Stub { 1561 @Override onStatusChanged(boolean enabled)1562 public void onStatusChanged(boolean enabled) throws RemoteException { 1563 synchronized (mLock) { 1564 for (int i = 0; i < mConnections.size(); ++i) { 1565 TvInputHardwareImpl impl = mConnections.valueAt(i).getHardwareImplLocked(); 1566 if (impl != null) { 1567 impl.handleAudioSinkUpdated(); 1568 } 1569 } 1570 } 1571 } 1572 } 1573 } 1574