TvInputHardwareManager.java revision 98d760e1c3123e6db8459f605d59a5689d56268c
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 17package com.android.server.tv; 18 19import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED; 20import static android.media.tv.TvInputManager.INPUT_STATE_DISCONNECTED; 21 22import android.content.BroadcastReceiver; 23import android.content.Context; 24import android.content.Intent; 25import android.content.IntentFilter; 26import android.hardware.hdmi.HdmiControlManager; 27import android.hardware.hdmi.HdmiDeviceInfo; 28import android.hardware.hdmi.HdmiHotplugEvent; 29import android.hardware.hdmi.IHdmiControlService; 30import android.hardware.hdmi.IHdmiDeviceEventListener; 31import android.hardware.hdmi.IHdmiHotplugEventListener; 32import android.hardware.hdmi.IHdmiSystemAudioModeChangeListener; 33import android.media.AudioDevicePort; 34import android.media.AudioFormat; 35import android.media.AudioGain; 36import android.media.AudioGainConfig; 37import android.media.AudioManager; 38import android.media.AudioPatch; 39import android.media.AudioPort; 40import android.media.AudioPortConfig; 41import android.media.tv.ITvInputHardware; 42import android.media.tv.ITvInputHardwareCallback; 43import android.media.tv.TvInputHardwareInfo; 44import android.media.tv.TvInputInfo; 45import android.media.tv.TvStreamConfig; 46import android.os.Handler; 47import android.os.IBinder; 48import android.os.Message; 49import android.os.RemoteException; 50import android.os.ServiceManager; 51import android.util.ArrayMap; 52import android.util.Slog; 53import android.util.SparseArray; 54import android.util.SparseBooleanArray; 55import android.view.KeyEvent; 56import android.view.Surface; 57 58import com.android.internal.os.SomeArgs; 59import com.android.server.SystemService; 60 61import java.util.ArrayList; 62import java.util.Arrays; 63import java.util.Collections; 64import java.util.Iterator; 65import java.util.LinkedList; 66import java.util.List; 67import java.util.Map; 68 69/** 70 * A helper class for TvInputManagerService to handle TV input hardware. 71 * 72 * This class does a basic connection management and forwarding calls to TvInputHal which eventually 73 * calls to tv_input HAL module. 74 * 75 * @hide 76 */ 77class TvInputHardwareManager implements TvInputHal.Callback { 78 private static final String TAG = TvInputHardwareManager.class.getSimpleName(); 79 80 private final Context mContext; 81 private final Listener mListener; 82 private final TvInputHal mHal = new TvInputHal(this); 83 private final SparseArray<Connection> mConnections = new SparseArray<>(); 84 private final List<TvInputHardwareInfo> mHardwareList = new ArrayList<>(); 85 private final List<HdmiDeviceInfo> mHdmiDeviceList = new LinkedList<>(); 86 /* A map from a device ID to the matching TV input ID. */ 87 private final SparseArray<String> mHardwareInputIdMap = new SparseArray<>(); 88 /* A map from a HDMI logical address to the matching TV input ID. */ 89 private final SparseArray<String> mHdmiInputIdMap = new SparseArray<>(); 90 private final Map<String, TvInputInfo> mInputMap = new ArrayMap<>(); 91 92 private final AudioManager mAudioManager; 93 private IHdmiControlService mHdmiControlService; 94 private final IHdmiHotplugEventListener mHdmiHotplugEventListener = 95 new HdmiHotplugEventListener(); 96 private final IHdmiDeviceEventListener mHdmiDeviceEventListener = new HdmiDeviceEventListener(); 97 private final IHdmiSystemAudioModeChangeListener mHdmiSystemAudioModeChangeListener = 98 new HdmiSystemAudioModeChangeListener(); 99 private final BroadcastReceiver mVolumeReceiver = new BroadcastReceiver() { 100 @Override 101 public void onReceive(Context context, Intent intent) { 102 handleVolumeChange(context, intent); 103 } 104 }; 105 private int mCurrentIndex = 0; 106 private int mCurrentMaxIndex = 0; 107 private final boolean mUseMasterVolume; 108 109 // TODO: Should handle STANDBY case. 110 private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray(); 111 private final List<Message> mPendingHdmiDeviceEvents = new LinkedList<>(); 112 113 // Calls to mListener should happen here. 114 private final Handler mHandler = new ListenerHandler(); 115 116 private final Object mLock = new Object(); 117 118 public TvInputHardwareManager(Context context, Listener listener) { 119 mContext = context; 120 mListener = listener; 121 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 122 mUseMasterVolume = mContext.getResources().getBoolean( 123 com.android.internal.R.bool.config_useMasterVolume); 124 mHal.init(); 125 } 126 127 public void onBootPhase(int phase) { 128 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { 129 mHdmiControlService = IHdmiControlService.Stub.asInterface(ServiceManager.getService( 130 Context.HDMI_CONTROL_SERVICE)); 131 if (mHdmiControlService != null) { 132 try { 133 mHdmiControlService.addHotplugEventListener(mHdmiHotplugEventListener); 134 mHdmiControlService.addDeviceEventListener(mHdmiDeviceEventListener); 135 mHdmiControlService.addSystemAudioModeChangeListener( 136 mHdmiSystemAudioModeChangeListener); 137 mHdmiDeviceList.addAll(mHdmiControlService.getInputDevices()); 138 } catch (RemoteException e) { 139 Slog.w(TAG, "Error registering listeners to HdmiControlService:", e); 140 } 141 } else { 142 Slog.w(TAG, "HdmiControlService is not available"); 143 } 144 if (!mUseMasterVolume) { 145 final IntentFilter filter = new IntentFilter(); 146 filter.addAction(AudioManager.VOLUME_CHANGED_ACTION); 147 filter.addAction(AudioManager.STREAM_MUTE_CHANGED_ACTION); 148 mContext.registerReceiver(mVolumeReceiver, filter); 149 } 150 updateVolume(); 151 } 152 } 153 154 @Override 155 public void onDeviceAvailable(TvInputHardwareInfo info, TvStreamConfig[] configs) { 156 synchronized (mLock) { 157 Connection connection = new Connection(info); 158 connection.updateConfigsLocked(configs); 159 mConnections.put(info.getDeviceId(), connection); 160 buildHardwareListLocked(); 161 mHandler.obtainMessage( 162 ListenerHandler.HARDWARE_DEVICE_ADDED, 0, 0, info).sendToTarget(); 163 if (info.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) { 164 processPendingHdmiDeviceEventsLocked(); 165 } 166 } 167 } 168 169 private void buildHardwareListLocked() { 170 mHardwareList.clear(); 171 for (int i = 0; i < mConnections.size(); ++i) { 172 mHardwareList.add(mConnections.valueAt(i).getHardwareInfoLocked()); 173 } 174 } 175 176 @Override 177 public void onDeviceUnavailable(int deviceId) { 178 synchronized (mLock) { 179 Connection connection = mConnections.get(deviceId); 180 if (connection == null) { 181 Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId); 182 return; 183 } 184 connection.resetLocked(null, null, null, null, null); 185 mConnections.remove(deviceId); 186 buildHardwareListLocked(); 187 TvInputHardwareInfo info = connection.getHardwareInfoLocked(); 188 if (info.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) { 189 // Remove HDMI devices linked with this hardware. 190 for (Iterator<HdmiDeviceInfo> it = mHdmiDeviceList.iterator(); it.hasNext();) { 191 HdmiDeviceInfo deviceInfo = it.next(); 192 if (deviceInfo.getPortId() == info.getHdmiPortId()) { 193 mHandler.obtainMessage(ListenerHandler.HDMI_DEVICE_REMOVED, 0, 0, 194 deviceInfo).sendToTarget(); 195 it.remove(); 196 } 197 } 198 } 199 mHandler.obtainMessage( 200 ListenerHandler.HARDWARE_DEVICE_REMOVED, 0, 0, info).sendToTarget(); 201 } 202 } 203 204 @Override 205 public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs) { 206 synchronized (mLock) { 207 Connection connection = mConnections.get(deviceId); 208 if (connection == null) { 209 Slog.e(TAG, "StreamConfigurationChanged: Cannot find a connection with " 210 + deviceId); 211 return; 212 } 213 connection.updateConfigsLocked(configs); 214 String inputId = mHardwareInputIdMap.get(deviceId); 215 if (inputId != null) { 216 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, 217 convertConnectedToState(configs.length > 0), 0, inputId).sendToTarget(); 218 } 219 try { 220 connection.getCallbackLocked().onStreamConfigChanged(configs); 221 } catch (RemoteException e) { 222 Slog.e(TAG, "error in onStreamConfigurationChanged", e); 223 } 224 } 225 } 226 227 @Override 228 public void onFirstFrameCaptured(int deviceId, int streamId) { 229 synchronized (mLock) { 230 Connection connection = mConnections.get(deviceId); 231 if (connection == null) { 232 Slog.e(TAG, "FirstFrameCaptured: Cannot find a connection with " 233 + deviceId); 234 return; 235 } 236 Runnable runnable = connection.getOnFirstFrameCapturedLocked(); 237 if (runnable != null) { 238 runnable.run(); 239 connection.setOnFirstFrameCapturedLocked(null); 240 } 241 } 242 } 243 244 public List<TvInputHardwareInfo> getHardwareList() { 245 synchronized (mLock) { 246 return Collections.unmodifiableList(mHardwareList); 247 } 248 } 249 250 public List<HdmiDeviceInfo> getHdmiDeviceList() { 251 synchronized (mLock) { 252 return Collections.unmodifiableList(mHdmiDeviceList); 253 } 254 } 255 256 private boolean checkUidChangedLocked( 257 Connection connection, int callingUid, int resolvedUserId) { 258 Integer connectionCallingUid = connection.getCallingUidLocked(); 259 Integer connectionResolvedUserId = connection.getResolvedUserIdLocked(); 260 if (connectionCallingUid == null || connectionResolvedUserId == null) { 261 return true; 262 } 263 if (connectionCallingUid != callingUid || connectionResolvedUserId != resolvedUserId) { 264 return true; 265 } 266 return false; 267 } 268 269 private int convertConnectedToState(boolean connected) { 270 if (connected) { 271 return INPUT_STATE_CONNECTED; 272 } else { 273 return INPUT_STATE_DISCONNECTED; 274 } 275 } 276 277 public void addHardwareTvInput(int deviceId, TvInputInfo info) { 278 synchronized (mLock) { 279 String oldInputId = mHardwareInputIdMap.get(deviceId); 280 if (oldInputId != null) { 281 Slog.w(TAG, "Trying to override previous registration: old = " 282 + mInputMap.get(oldInputId) + ":" + deviceId + ", new = " 283 + info + ":" + deviceId); 284 } 285 mHardwareInputIdMap.put(deviceId, info.getId()); 286 mInputMap.put(info.getId(), info); 287 288 // Process pending state changes 289 290 // For logical HDMI devices, they have information from HDMI CEC signals. 291 for (int i = 0; i < mHdmiStateMap.size(); ++i) { 292 TvInputHardwareInfo hardwareInfo = 293 findHardwareInfoForHdmiPortLocked(mHdmiStateMap.keyAt(i)); 294 if (hardwareInfo == null) { 295 continue; 296 } 297 String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId()); 298 if (inputId != null && inputId.equals(info.getId())) { 299 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, 300 convertConnectedToState(mHdmiStateMap.valueAt(i)), 0, 301 inputId).sendToTarget(); 302 return; 303 } 304 } 305 // For the rest of the devices, we can tell by the number of available streams. 306 Connection connection = mConnections.get(deviceId); 307 if (connection != null) { 308 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, 309 convertConnectedToState(connection.getConfigsLocked().length > 0), 0, 310 info.getId()).sendToTarget(); 311 return; 312 } 313 } 314 } 315 316 private static <T> int indexOfEqualValue(SparseArray<T> map, T value) { 317 for (int i = 0; i < map.size(); ++i) { 318 if (map.valueAt(i).equals(value)) { 319 return i; 320 } 321 } 322 return -1; 323 } 324 325 private static boolean intArrayContains(int[] array, int value) { 326 for (int element : array) { 327 if (element == value) return true; 328 } 329 return false; 330 } 331 332 public void addHdmiTvInput(int id, TvInputInfo info) { 333 if (info.getType() != TvInputInfo.TYPE_HDMI) { 334 throw new IllegalArgumentException("info (" + info + ") has non-HDMI type."); 335 } 336 synchronized (mLock) { 337 String parentId = info.getParentId(); 338 int parentIndex = indexOfEqualValue(mHardwareInputIdMap, parentId); 339 if (parentIndex < 0) { 340 throw new IllegalArgumentException("info (" + info + ") has invalid parentId."); 341 } 342 String oldInputId = mHdmiInputIdMap.get(id); 343 if (oldInputId != null) { 344 Slog.w(TAG, "Trying to override previous registration: old = " 345 + mInputMap.get(oldInputId) + ":" + id + ", new = " 346 + info + ":" + id); 347 } 348 mHdmiInputIdMap.put(id, info.getId()); 349 mInputMap.put(info.getId(), info); 350 } 351 } 352 353 public void removeTvInput(String inputId) { 354 synchronized (mLock) { 355 mInputMap.remove(inputId); 356 int hardwareIndex = indexOfEqualValue(mHardwareInputIdMap, inputId); 357 if (hardwareIndex >= 0) { 358 mHardwareInputIdMap.removeAt(hardwareIndex); 359 } 360 int deviceIndex = indexOfEqualValue(mHdmiInputIdMap, inputId); 361 if (deviceIndex >= 0) { 362 mHdmiInputIdMap.removeAt(deviceIndex); 363 } 364 } 365 } 366 367 /** 368 * Create a TvInputHardware object with a specific deviceId. One service at a time can access 369 * the object, and if more than one process attempts to create hardware with the same deviceId, 370 * the latest service will get the object and all the other hardware are released. The 371 * release is notified via ITvInputHardwareCallback.onReleased(). 372 */ 373 public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback, 374 TvInputInfo info, int callingUid, int resolvedUserId) { 375 if (callback == null) { 376 throw new NullPointerException(); 377 } 378 synchronized (mLock) { 379 Connection connection = mConnections.get(deviceId); 380 if (connection == null) { 381 Slog.e(TAG, "Invalid deviceId : " + deviceId); 382 return null; 383 } 384 if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) { 385 TvInputHardwareImpl hardware = 386 new TvInputHardwareImpl(connection.getHardwareInfoLocked()); 387 try { 388 callback.asBinder().linkToDeath(connection, 0); 389 } catch (RemoteException e) { 390 hardware.release(); 391 return null; 392 } 393 connection.resetLocked(hardware, callback, info, callingUid, resolvedUserId); 394 } 395 return connection.getHardwareLocked(); 396 } 397 } 398 399 /** 400 * Release the specified hardware. 401 */ 402 public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid, 403 int resolvedUserId) { 404 synchronized (mLock) { 405 Connection connection = mConnections.get(deviceId); 406 if (connection == null) { 407 Slog.e(TAG, "Invalid deviceId : " + deviceId); 408 return; 409 } 410 if (connection.getHardwareLocked() != hardware 411 || checkUidChangedLocked(connection, callingUid, resolvedUserId)) { 412 return; 413 } 414 connection.resetLocked(null, null, null, null, null); 415 } 416 } 417 418 private TvInputHardwareInfo findHardwareInfoForHdmiPortLocked(int port) { 419 for (TvInputHardwareInfo hardwareInfo : mHardwareList) { 420 if (hardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI 421 && hardwareInfo.getHdmiPortId() == port) { 422 return hardwareInfo; 423 } 424 } 425 return null; 426 } 427 428 private int findDeviceIdForInputIdLocked(String inputId) { 429 for (int i = 0; i < mConnections.size(); ++i) { 430 Connection connection = mConnections.get(i); 431 if (connection.getInfoLocked().getId().equals(inputId)) { 432 return i; 433 } 434 } 435 return -1; 436 } 437 438 /** 439 * Get the list of TvStreamConfig which is buffered mode. 440 */ 441 public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int callingUid, 442 int resolvedUserId) { 443 List<TvStreamConfig> configsList = new ArrayList<TvStreamConfig>(); 444 synchronized (mLock) { 445 int deviceId = findDeviceIdForInputIdLocked(inputId); 446 if (deviceId < 0) { 447 Slog.e(TAG, "Invalid inputId : " + inputId); 448 return configsList; 449 } 450 Connection connection = mConnections.get(deviceId); 451 for (TvStreamConfig config : connection.getConfigsLocked()) { 452 if (config.getType() == TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) { 453 configsList.add(config); 454 } 455 } 456 } 457 return configsList; 458 } 459 460 /** 461 * Take a snapshot of the given TV input into the provided Surface. 462 */ 463 public boolean captureFrame(String inputId, Surface surface, final TvStreamConfig config, 464 int callingUid, int resolvedUserId) { 465 synchronized (mLock) { 466 int deviceId = findDeviceIdForInputIdLocked(inputId); 467 if (deviceId < 0) { 468 Slog.e(TAG, "Invalid inputId : " + inputId); 469 return false; 470 } 471 Connection connection = mConnections.get(deviceId); 472 final TvInputHardwareImpl hardwareImpl = connection.getHardwareImplLocked(); 473 if (hardwareImpl != null) { 474 // Stop previous capture. 475 Runnable runnable = connection.getOnFirstFrameCapturedLocked(); 476 if (runnable != null) { 477 runnable.run(); 478 connection.setOnFirstFrameCapturedLocked(null); 479 } 480 481 boolean result = hardwareImpl.startCapture(surface, config); 482 if (result) { 483 connection.setOnFirstFrameCapturedLocked(new Runnable() { 484 @Override 485 public void run() { 486 hardwareImpl.stopCapture(config); 487 } 488 }); 489 } 490 return result; 491 } 492 } 493 return false; 494 } 495 496 private void processPendingHdmiDeviceEventsLocked() { 497 for (Iterator<Message> it = mPendingHdmiDeviceEvents.iterator(); it.hasNext(); ) { 498 Message msg = it.next(); 499 HdmiDeviceInfo deviceInfo = (HdmiDeviceInfo) msg.obj; 500 TvInputHardwareInfo hardwareInfo = 501 findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId()); 502 if (hardwareInfo != null) { 503 msg.sendToTarget(); 504 it.remove(); 505 } 506 } 507 } 508 509 private void updateVolume() { 510 mCurrentMaxIndex = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); 511 mCurrentIndex = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); 512 } 513 514 private void handleVolumeChange(Context context, Intent intent) { 515 String action = intent.getAction(); 516 if (action.equals(AudioManager.VOLUME_CHANGED_ACTION)) { 517 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); 518 if (streamType != AudioManager.STREAM_MUSIC) { 519 return; 520 } 521 int index = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_VALUE, 0); 522 if (index == mCurrentIndex) { 523 return; 524 } 525 mCurrentIndex = index; 526 } else if (action.equals(AudioManager.STREAM_MUTE_CHANGED_ACTION)) { 527 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); 528 if (streamType != AudioManager.STREAM_MUSIC) { 529 return; 530 } 531 // volume index will be updated at onMediaStreamVolumeChanged() through updateVolume(). 532 } else { 533 Slog.w(TAG, "Unrecognized intent: " + intent); 534 return; 535 } 536 synchronized (mLock) { 537 for (int i = 0; i < mConnections.size(); ++i) { 538 TvInputHardwareImpl hardwareImpl = mConnections.valueAt(i).getHardwareImplLocked(); 539 if (hardwareImpl != null) { 540 hardwareImpl.onMediaStreamVolumeChanged(); 541 } 542 } 543 } 544 } 545 546 private float getMediaStreamVolume() { 547 return mUseMasterVolume ? 1.0f : ((float) mCurrentIndex / (float) mCurrentMaxIndex); 548 } 549 550 private class Connection implements IBinder.DeathRecipient { 551 private final TvInputHardwareInfo mHardwareInfo; 552 private TvInputInfo mInfo; 553 private TvInputHardwareImpl mHardware = null; 554 private ITvInputHardwareCallback mCallback; 555 private TvStreamConfig[] mConfigs = null; 556 private Integer mCallingUid = null; 557 private Integer mResolvedUserId = null; 558 private Runnable mOnFirstFrameCaptured; 559 560 public Connection(TvInputHardwareInfo hardwareInfo) { 561 mHardwareInfo = hardwareInfo; 562 } 563 564 // *Locked methods assume TvInputHardwareManager.mLock is held. 565 566 public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback, 567 TvInputInfo info, Integer callingUid, Integer resolvedUserId) { 568 if (mHardware != null) { 569 try { 570 mCallback.onReleased(); 571 } catch (RemoteException e) { 572 Slog.e(TAG, "error in Connection::resetLocked", e); 573 } 574 mHardware.release(); 575 } 576 mHardware = hardware; 577 mCallback = callback; 578 mInfo = info; 579 mCallingUid = callingUid; 580 mResolvedUserId = resolvedUserId; 581 mOnFirstFrameCaptured = null; 582 583 if (mHardware != null && mCallback != null) { 584 try { 585 mCallback.onStreamConfigChanged(getConfigsLocked()); 586 } catch (RemoteException e) { 587 Slog.e(TAG, "error in Connection::resetLocked", e); 588 } 589 } 590 } 591 592 public void updateConfigsLocked(TvStreamConfig[] configs) { 593 mConfigs = configs; 594 } 595 596 public TvInputHardwareInfo getHardwareInfoLocked() { 597 return mHardwareInfo; 598 } 599 600 public TvInputInfo getInfoLocked() { 601 return mInfo; 602 } 603 604 public ITvInputHardware getHardwareLocked() { 605 return mHardware; 606 } 607 608 public TvInputHardwareImpl getHardwareImplLocked() { 609 return mHardware; 610 } 611 612 public ITvInputHardwareCallback getCallbackLocked() { 613 return mCallback; 614 } 615 616 public TvStreamConfig[] getConfigsLocked() { 617 return mConfigs; 618 } 619 620 public Integer getCallingUidLocked() { 621 return mCallingUid; 622 } 623 624 public Integer getResolvedUserIdLocked() { 625 return mResolvedUserId; 626 } 627 628 public void setOnFirstFrameCapturedLocked(Runnable runnable) { 629 mOnFirstFrameCaptured = runnable; 630 } 631 632 public Runnable getOnFirstFrameCapturedLocked() { 633 return mOnFirstFrameCaptured; 634 } 635 636 @Override 637 public void binderDied() { 638 synchronized (mLock) { 639 resetLocked(null, null, null, null, null); 640 } 641 } 642 } 643 644 private class TvInputHardwareImpl extends ITvInputHardware.Stub { 645 private final TvInputHardwareInfo mInfo; 646 private boolean mReleased = false; 647 private final Object mImplLock = new Object(); 648 649 private final AudioManager.OnAudioPortUpdateListener mAudioListener = 650 new AudioManager.OnAudioPortUpdateListener() { 651 @Override 652 public void onAudioPortListUpdate(AudioPort[] portList) { 653 synchronized (mImplLock) { 654 updateAudioConfigLocked(); 655 } 656 } 657 658 @Override 659 public void onAudioPatchListUpdate(AudioPatch[] patchList) { 660 // No-op 661 } 662 663 @Override 664 public void onServiceDied() { 665 synchronized (mImplLock) { 666 mAudioSource = null; 667 mAudioSink = null; 668 mAudioPatch = null; 669 } 670 } 671 }; 672 private int mOverrideAudioType = AudioManager.DEVICE_NONE; 673 private String mOverrideAudioAddress = ""; 674 private AudioDevicePort mAudioSource; 675 private AudioDevicePort mAudioSink; 676 private AudioPatch mAudioPatch = null; 677 private float mCommittedVolume = 0.0f; 678 private float mSourceVolume = 0.0f; 679 680 private TvStreamConfig mActiveConfig = null; 681 682 private int mDesiredSamplingRate = 0; 683 private int mDesiredChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT; 684 private int mDesiredFormat = AudioFormat.ENCODING_DEFAULT; 685 686 public TvInputHardwareImpl(TvInputHardwareInfo info) { 687 mInfo = info; 688 mAudioManager.registerAudioPortUpdateListener(mAudioListener); 689 if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) { 690 mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress()); 691 mAudioSink = findAudioSinkFromAudioPolicy(); 692 } 693 } 694 695 private AudioDevicePort findAudioSinkFromAudioPolicy() { 696 ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>(); 697 if (mAudioManager.listAudioDevicePorts(devicePorts) == AudioManager.SUCCESS) { 698 int sinkDevice = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC); 699 for (AudioPort port : devicePorts) { 700 AudioDevicePort devicePort = (AudioDevicePort) port; 701 if ((devicePort.type() & sinkDevice) != 0) { 702 return devicePort; 703 } 704 } 705 } 706 return null; 707 } 708 709 private AudioDevicePort findAudioDevicePort(int type, String address) { 710 if (type == AudioManager.DEVICE_NONE) { 711 return null; 712 } 713 ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>(); 714 if (mAudioManager.listAudioDevicePorts(devicePorts) != AudioManager.SUCCESS) { 715 return null; 716 } 717 for (AudioPort port : devicePorts) { 718 AudioDevicePort devicePort = (AudioDevicePort) port; 719 if (devicePort.type() == type && devicePort.address().equals(address)) { 720 return devicePort; 721 } 722 } 723 return null; 724 } 725 726 public void release() { 727 synchronized (mImplLock) { 728 mAudioManager.unregisterAudioPortUpdateListener(mAudioListener); 729 if (mAudioPatch != null) { 730 mAudioManager.releaseAudioPatch(mAudioPatch); 731 mAudioPatch = null; 732 } 733 mReleased = true; 734 } 735 } 736 737 // A TvInputHardwareImpl object holds only one active session. Therefore, if a client 738 // attempts to call setSurface with different TvStreamConfig objects, the last call will 739 // prevail. 740 @Override 741 public boolean setSurface(Surface surface, TvStreamConfig config) 742 throws RemoteException { 743 synchronized (mImplLock) { 744 if (mReleased) { 745 throw new IllegalStateException("Device already released."); 746 } 747 748 int result = TvInputHal.SUCCESS; 749 if (surface == null) { 750 // The value of config is ignored when surface == null. 751 if (mActiveConfig != null) { 752 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig); 753 mActiveConfig = null; 754 } else { 755 // We already have no active stream. 756 return true; 757 } 758 } else { 759 // It's impossible to set a non-null surface with a null config. 760 if (config == null) { 761 return false; 762 } 763 // Remove stream only if we have an existing active configuration. 764 if (mActiveConfig != null && !config.equals(mActiveConfig)) { 765 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig); 766 if (result != TvInputHal.SUCCESS) { 767 mActiveConfig = null; 768 } 769 } 770 // Proceed only if all previous operations succeeded. 771 if (result == TvInputHal.SUCCESS) { 772 result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config); 773 if (result == TvInputHal.SUCCESS) { 774 mActiveConfig = config; 775 } 776 } 777 } 778 updateAudioConfigLocked(); 779 return result == TvInputHal.SUCCESS; 780 } 781 } 782 783 /** 784 * Update audio configuration (source, sink, patch) all up to current state. 785 */ 786 private void updateAudioConfigLocked() { 787 boolean sinkUpdated = updateAudioSinkLocked(); 788 boolean sourceUpdated = updateAudioSourceLocked(); 789 // We can't do updated = updateAudioSinkLocked() || updateAudioSourceLocked() here 790 // because Java won't evaluate the latter if the former is true. 791 792 if (mAudioSource == null || mAudioSink == null || mActiveConfig == null) { 793 if (mAudioPatch != null) { 794 mAudioManager.releaseAudioPatch(mAudioPatch); 795 mAudioPatch = null; 796 } 797 return; 798 } 799 800 updateVolume(); 801 float volume = mSourceVolume * getMediaStreamVolume(); 802 AudioGainConfig sourceGainConfig = null; 803 if (mAudioSource.gains().length > 0 && volume != mCommittedVolume) { 804 AudioGain sourceGain = null; 805 for (AudioGain gain : mAudioSource.gains()) { 806 if ((gain.mode() & AudioGain.MODE_JOINT) != 0) { 807 sourceGain = gain; 808 break; 809 } 810 } 811 // NOTE: we only change the source gain in MODE_JOINT here. 812 if (sourceGain != null) { 813 int steps = (sourceGain.maxValue() - sourceGain.minValue()) 814 / sourceGain.stepValue(); 815 int gainValue = sourceGain.minValue(); 816 if (volume < 1.0f) { 817 gainValue += sourceGain.stepValue() * (int) (volume * steps + 0.5); 818 } else { 819 gainValue = sourceGain.maxValue(); 820 } 821 // size of gain values is 1 in MODE_JOINT 822 int[] gainValues = new int[] { gainValue }; 823 sourceGainConfig = sourceGain.buildConfig(AudioGain.MODE_JOINT, 824 sourceGain.channelMask(), gainValues, 0); 825 } else { 826 Slog.w(TAG, "No audio source gain with MODE_JOINT support exists."); 827 } 828 } 829 830 AudioPortConfig sourceConfig = mAudioSource.activeConfig(); 831 AudioPortConfig sinkConfig = mAudioSink.activeConfig(); 832 AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch }; 833 boolean shouldRecreateAudioPatch = sourceUpdated || sinkUpdated; 834 835 int sinkSamplingRate = mDesiredSamplingRate; 836 int sinkChannelMask = mDesiredChannelMask; 837 int sinkFormat = mDesiredFormat; 838 // If sinkConfig != null and values are set to default, fill in the sinkConfig values. 839 if (sinkConfig != null) { 840 if (sinkSamplingRate == 0) { 841 sinkSamplingRate = sinkConfig.samplingRate(); 842 } 843 if (sinkChannelMask == AudioFormat.CHANNEL_OUT_DEFAULT) { 844 sinkChannelMask = sinkConfig.channelMask(); 845 } 846 if (sinkFormat == AudioFormat.ENCODING_DEFAULT) { 847 sinkChannelMask = sinkConfig.format(); 848 } 849 } 850 851 if (sinkConfig == null 852 || sinkConfig.samplingRate() != sinkSamplingRate 853 || sinkConfig.channelMask() != sinkChannelMask 854 || sinkConfig.format() != sinkFormat) { 855 // Check for compatibility and reset to default if necessary. 856 if (!intArrayContains(mAudioSink.samplingRates(), sinkSamplingRate) 857 && mAudioSink.samplingRates().length > 0) { 858 sinkSamplingRate = mAudioSink.samplingRates()[0]; 859 } 860 if (!intArrayContains(mAudioSink.channelMasks(), sinkChannelMask)) { 861 sinkChannelMask = AudioFormat.CHANNEL_OUT_DEFAULT; 862 } 863 if (!intArrayContains(mAudioSink.formats(), sinkFormat)) { 864 sinkFormat = AudioFormat.ENCODING_DEFAULT; 865 } 866 sinkConfig = mAudioSink.buildConfig(sinkSamplingRate, sinkChannelMask, 867 sinkFormat, null); 868 shouldRecreateAudioPatch = true; 869 } 870 if (sourceConfig == null || sourceGainConfig != null) { 871 int sourceSamplingRate = 0; 872 if (intArrayContains(mAudioSource.samplingRates(), sinkConfig.samplingRate())) { 873 sourceSamplingRate = sinkConfig.samplingRate(); 874 } else if (mAudioSource.samplingRates().length > 0) { 875 // Use any sampling rate and hope audio patch can handle resampling... 876 sourceSamplingRate = mAudioSource.samplingRates()[0]; 877 } 878 int sourceChannelMask = AudioFormat.CHANNEL_IN_DEFAULT; 879 for (int inChannelMask : mAudioSource.channelMasks()) { 880 if (AudioFormat.channelCountFromOutChannelMask(sinkConfig.channelMask()) 881 == AudioFormat.channelCountFromInChannelMask(inChannelMask)) { 882 sourceChannelMask = inChannelMask; 883 break; 884 } 885 } 886 int sourceFormat = AudioFormat.ENCODING_DEFAULT; 887 if (intArrayContains(mAudioSource.formats(), sinkConfig.format())) { 888 sourceFormat = sinkConfig.format(); 889 } 890 sourceConfig = mAudioSource.buildConfig(sourceSamplingRate, sourceChannelMask, 891 sourceFormat, sourceGainConfig); 892 shouldRecreateAudioPatch = true; 893 } 894 if (shouldRecreateAudioPatch) { 895 mCommittedVolume = volume; 896 mAudioManager.createAudioPatch( 897 audioPatchArray, 898 new AudioPortConfig[] { sourceConfig }, 899 new AudioPortConfig[] { sinkConfig }); 900 mAudioPatch = audioPatchArray[0]; 901 if (sourceGainConfig != null) { 902 mAudioManager.setAudioPortGain(mAudioSource, sourceGainConfig); 903 } 904 } 905 } 906 907 @Override 908 public void setStreamVolume(float volume) throws RemoteException { 909 synchronized (mImplLock) { 910 if (mReleased) { 911 throw new IllegalStateException("Device already released."); 912 } 913 mSourceVolume = volume; 914 updateAudioConfigLocked(); 915 } 916 } 917 918 @Override 919 public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException { 920 synchronized (mImplLock) { 921 if (mReleased) { 922 throw new IllegalStateException("Device already released."); 923 } 924 } 925 if (mInfo.getType() != TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) { 926 return false; 927 } 928 // TODO(hdmi): mHdmiClient.sendKeyEvent(event); 929 return false; 930 } 931 932 private boolean startCapture(Surface surface, TvStreamConfig config) { 933 synchronized (mImplLock) { 934 if (mReleased) { 935 return false; 936 } 937 if (surface == null || config == null) { 938 return false; 939 } 940 if (config.getType() != TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) { 941 return false; 942 } 943 944 int result = mHal.addOrUpdateStream(mInfo.getDeviceId(), surface, config); 945 return result == TvInputHal.SUCCESS; 946 } 947 } 948 949 private boolean stopCapture(TvStreamConfig config) { 950 synchronized (mImplLock) { 951 if (mReleased) { 952 return false; 953 } 954 if (config == null) { 955 return false; 956 } 957 958 int result = mHal.removeStream(mInfo.getDeviceId(), config); 959 return result == TvInputHal.SUCCESS; 960 } 961 } 962 963 private boolean updateAudioSourceLocked() { 964 if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) { 965 return false; 966 } 967 AudioDevicePort previousSource = mAudioSource; 968 mAudioSource = findAudioDevicePort(mInfo.getAudioType(), mInfo.getAudioAddress()); 969 return mAudioSource == null ? (previousSource != null) 970 : !mAudioSource.equals(previousSource); 971 } 972 973 private boolean updateAudioSinkLocked() { 974 if (mInfo.getAudioType() == AudioManager.DEVICE_NONE) { 975 return false; 976 } 977 AudioDevicePort previousSink = mAudioSink; 978 if (mOverrideAudioType == AudioManager.DEVICE_NONE) { 979 mAudioSink = findAudioSinkFromAudioPolicy(); 980 } else { 981 AudioDevicePort audioSink = 982 findAudioDevicePort(mOverrideAudioType, mOverrideAudioAddress); 983 if (audioSink != null) { 984 mAudioSink = audioSink; 985 } 986 } 987 return mAudioSink == null ? (previousSink != null) : !mAudioSink.equals(previousSink); 988 } 989 990 private void handleAudioSinkUpdated() { 991 synchronized (mImplLock) { 992 updateAudioConfigLocked(); 993 } 994 } 995 996 @Override 997 public void overrideAudioSink(int audioType, String audioAddress, int samplingRate, 998 int channelMask, int format) { 999 synchronized (mImplLock) { 1000 mOverrideAudioType = audioType; 1001 mOverrideAudioAddress = audioAddress; 1002 1003 mDesiredSamplingRate = samplingRate; 1004 mDesiredChannelMask = channelMask; 1005 mDesiredFormat = format; 1006 1007 updateAudioConfigLocked(); 1008 } 1009 } 1010 1011 public void onMediaStreamVolumeChanged() { 1012 synchronized (mImplLock) { 1013 updateAudioConfigLocked(); 1014 } 1015 } 1016 } 1017 1018 interface Listener { 1019 public void onStateChanged(String inputId, int state); 1020 public void onHardwareDeviceAdded(TvInputHardwareInfo info); 1021 public void onHardwareDeviceRemoved(TvInputHardwareInfo info); 1022 public void onHdmiDeviceAdded(HdmiDeviceInfo device); 1023 public void onHdmiDeviceRemoved(HdmiDeviceInfo device); 1024 public void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo device); 1025 } 1026 1027 private class ListenerHandler extends Handler { 1028 private static final int STATE_CHANGED = 1; 1029 private static final int HARDWARE_DEVICE_ADDED = 2; 1030 private static final int HARDWARE_DEVICE_REMOVED = 3; 1031 private static final int HDMI_DEVICE_ADDED = 4; 1032 private static final int HDMI_DEVICE_REMOVED = 5; 1033 private static final int HDMI_DEVICE_UPDATED = 6; 1034 1035 @Override 1036 public final void handleMessage(Message msg) { 1037 switch (msg.what) { 1038 case STATE_CHANGED: { 1039 String inputId = (String) msg.obj; 1040 int state = msg.arg1; 1041 mListener.onStateChanged(inputId, state); 1042 break; 1043 } 1044 case HARDWARE_DEVICE_ADDED: { 1045 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj; 1046 mListener.onHardwareDeviceAdded(info); 1047 break; 1048 } 1049 case HARDWARE_DEVICE_REMOVED: { 1050 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj; 1051 mListener.onHardwareDeviceRemoved(info); 1052 break; 1053 } 1054 case HDMI_DEVICE_ADDED: { 1055 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj; 1056 mListener.onHdmiDeviceAdded(info); 1057 break; 1058 } 1059 case HDMI_DEVICE_REMOVED: { 1060 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj; 1061 mListener.onHdmiDeviceRemoved(info); 1062 break; 1063 } 1064 case HDMI_DEVICE_UPDATED: { 1065 HdmiDeviceInfo info = (HdmiDeviceInfo) msg.obj; 1066 String inputId = null; 1067 synchronized (mLock) { 1068 inputId = mHdmiInputIdMap.get(info.getId()); 1069 } 1070 if (inputId != null) { 1071 mListener.onHdmiDeviceUpdated(inputId, info); 1072 } else { 1073 Slog.w(TAG, "Could not resolve input ID matching the device info; " 1074 + "ignoring."); 1075 } 1076 break; 1077 } 1078 default: { 1079 Slog.w(TAG, "Unhandled message: " + msg); 1080 break; 1081 } 1082 } 1083 } 1084 } 1085 1086 // Listener implementations for HdmiControlService 1087 1088 private final class HdmiHotplugEventListener extends IHdmiHotplugEventListener.Stub { 1089 @Override 1090 public void onReceived(HdmiHotplugEvent event) { 1091 synchronized (mLock) { 1092 mHdmiStateMap.put(event.getPort(), event.isConnected()); 1093 TvInputHardwareInfo hardwareInfo = 1094 findHardwareInfoForHdmiPortLocked(event.getPort()); 1095 if (hardwareInfo == null) { 1096 return; 1097 } 1098 String inputId = mHardwareInputIdMap.get(hardwareInfo.getDeviceId()); 1099 if (inputId == null) { 1100 return; 1101 } 1102 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, 1103 convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget(); 1104 } 1105 } 1106 } 1107 1108 private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub { 1109 @Override 1110 public void onStatusChanged(HdmiDeviceInfo deviceInfo, int status) { 1111 if (!deviceInfo.isSourceType()) return; 1112 synchronized (mLock) { 1113 int messageType = 0; 1114 Object obj = null; 1115 switch (status) { 1116 case HdmiControlManager.DEVICE_EVENT_ADD_DEVICE: { 1117 if (findHdmiDeviceInfo(deviceInfo.getId()) == null) { 1118 mHdmiDeviceList.add(deviceInfo); 1119 } else { 1120 Slog.w(TAG, "The list already contains " + deviceInfo + "; ignoring."); 1121 return; 1122 } 1123 messageType = ListenerHandler.HDMI_DEVICE_ADDED; 1124 obj = deviceInfo; 1125 break; 1126 } 1127 case HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE: { 1128 HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId()); 1129 if (!mHdmiDeviceList.remove(originalDeviceInfo)) { 1130 Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring."); 1131 return; 1132 } 1133 messageType = ListenerHandler.HDMI_DEVICE_REMOVED; 1134 obj = deviceInfo; 1135 break; 1136 } 1137 case HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE: { 1138 HdmiDeviceInfo originalDeviceInfo = findHdmiDeviceInfo(deviceInfo.getId()); 1139 if (!mHdmiDeviceList.remove(originalDeviceInfo)) { 1140 Slog.w(TAG, "The list doesn't contain " + deviceInfo + "; ignoring."); 1141 return; 1142 } 1143 mHdmiDeviceList.add(deviceInfo); 1144 messageType = ListenerHandler.HDMI_DEVICE_UPDATED; 1145 obj = deviceInfo; 1146 break; 1147 } 1148 } 1149 1150 Message msg = mHandler.obtainMessage(messageType, 0, 0, obj); 1151 if (findHardwareInfoForHdmiPortLocked(deviceInfo.getPortId()) != null) { 1152 msg.sendToTarget(); 1153 } else { 1154 mPendingHdmiDeviceEvents.add(msg); 1155 } 1156 } 1157 } 1158 1159 private HdmiDeviceInfo findHdmiDeviceInfo(int id) { 1160 for (HdmiDeviceInfo info : mHdmiDeviceList) { 1161 if (info.getId() == id) { 1162 return info; 1163 } 1164 } 1165 return null; 1166 } 1167 } 1168 1169 private final class HdmiSystemAudioModeChangeListener extends 1170 IHdmiSystemAudioModeChangeListener.Stub { 1171 @Override 1172 public void onStatusChanged(boolean enabled) throws RemoteException { 1173 synchronized (mLock) { 1174 for (int i = 0; i < mConnections.size(); ++i) { 1175 TvInputHardwareImpl impl = mConnections.valueAt(i).getHardwareImplLocked(); 1176 if (impl != null) { 1177 impl.handleAudioSinkUpdated(); 1178 } 1179 } 1180 } 1181 } 1182 } 1183} 1184