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