TvInputHardwareManager.java revision c086a3df3b28996cd10ebe42c5f59035d054aa0d
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.Context; 23import android.hardware.hdmi.HdmiCecDeviceInfo; 24import android.hardware.hdmi.HdmiHotplugEvent; 25import android.hardware.hdmi.IHdmiControlService; 26import android.hardware.hdmi.IHdmiDeviceEventListener; 27import android.hardware.hdmi.IHdmiHotplugEventListener; 28import android.hardware.hdmi.IHdmiInputChangeListener; 29import android.media.AudioDevicePort; 30import android.media.AudioManager; 31import android.media.AudioPatch; 32import android.media.AudioPort; 33import android.media.AudioPortConfig; 34import android.media.tv.ITvInputHardware; 35import android.media.tv.ITvInputHardwareCallback; 36import android.media.tv.TvInputHardwareInfo; 37import android.media.tv.TvInputInfo; 38import android.media.tv.TvStreamConfig; 39import android.os.Handler; 40import android.os.IBinder; 41import android.os.Looper; 42import android.os.Message; 43import android.os.RemoteException; 44import android.os.ServiceManager; 45import android.util.ArrayMap; 46import android.util.Slog; 47import android.util.SparseArray; 48import android.util.SparseBooleanArray; 49import android.view.KeyEvent; 50import android.view.Surface; 51 52import com.android.server.SystemService; 53 54import java.util.ArrayList; 55import java.util.Collections; 56import java.util.HashSet; 57import java.util.List; 58import java.util.Map; 59import java.util.Set; 60 61/** 62 * A helper class for TvInputManagerService to handle TV input hardware. 63 * 64 * This class does a basic connection management and forwarding calls to TvInputHal which eventually 65 * calls to tv_input HAL module. 66 * 67 * @hide 68 */ 69class TvInputHardwareManager implements TvInputHal.Callback { 70 private static final String TAG = TvInputHardwareManager.class.getSimpleName(); 71 72 private final Context mContext; 73 private final Listener mListener; 74 private final TvInputHal mHal = new TvInputHal(this); 75 private final SparseArray<Connection> mConnections = new SparseArray<Connection>(); 76 private final List<TvInputHardwareInfo> mInfoList = new ArrayList<TvInputHardwareInfo>(); 77 /* A map from a device ID to the matching TV input ID. */ 78 private final SparseArray<String> mHardwareInputIdMap = new SparseArray<String>(); 79 /* A map from a HDMI logical address to the matching TV input ID. */ 80 private final SparseArray<String> mHdmiCecInputIdMap = new SparseArray<String>(); 81 private final Map<String, TvInputInfo> mInputMap = new ArrayMap<String, TvInputInfo>(); 82 83 private final AudioManager mAudioManager; 84 private IHdmiControlService mHdmiControlService; 85 private final IHdmiHotplugEventListener mHdmiHotplugEventListener = 86 new HdmiHotplugEventListener(); 87 private final IHdmiDeviceEventListener mHdmiDeviceEventListener = new HdmiDeviceEventListener(); 88 private final IHdmiInputChangeListener mHdmiInputChangeListener = new HdmiInputChangeListener(); 89 private final Set<Integer> mActiveHdmiSources = new HashSet<Integer>(); 90 // TODO: Should handle INACTIVE case. 91 private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray(); 92 93 // Calls to mListener should happen here. 94 private final Handler mHandler = new ListenerHandler(); 95 96 private final Object mLock = new Object(); 97 98 public TvInputHardwareManager(Context context, Listener listener) { 99 mContext = context; 100 mListener = listener; 101 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 102 mHal.init(); 103 } 104 105 public void onBootPhase(int phase) { 106 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { 107 mHdmiControlService = IHdmiControlService.Stub.asInterface(ServiceManager.getService( 108 Context.HDMI_CONTROL_SERVICE)); 109 if (mHdmiControlService != null) { 110 try { 111 mHdmiControlService.addHotplugEventListener(mHdmiHotplugEventListener); 112 mHdmiControlService.addDeviceEventListener(mHdmiDeviceEventListener); 113 mHdmiControlService.setInputChangeListener(mHdmiInputChangeListener); 114 } catch (RemoteException e) { 115 Slog.w(TAG, "Error registering listeners to HdmiControlService:", e); 116 } 117 } 118 } 119 } 120 121 @Override 122 public void onDeviceAvailable(TvInputHardwareInfo info, TvStreamConfig[] configs) { 123 synchronized (mLock) { 124 Connection connection = new Connection(info); 125 connection.updateConfigsLocked(configs); 126 mConnections.put(info.getDeviceId(), connection); 127 buildInfoListLocked(); 128 mHandler.obtainMessage( 129 ListenerHandler.HARDWARE_DEVICE_ADDED, 0, 0, info).sendToTarget(); 130 } 131 } 132 133 private void buildInfoListLocked() { 134 mInfoList.clear(); 135 for (int i = 0; i < mConnections.size(); ++i) { 136 mInfoList.add(mConnections.valueAt(i).getHardwareInfoLocked()); 137 } 138 } 139 140 @Override 141 public void onDeviceUnavailable(int deviceId) { 142 synchronized (mLock) { 143 Connection connection = mConnections.get(deviceId); 144 if (connection == null) { 145 Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId); 146 return; 147 } 148 connection.resetLocked(null, null, null, null, null); 149 mConnections.remove(deviceId); 150 buildInfoListLocked(); 151 TvInputHardwareInfo info = connection.getHardwareInfoLocked(); 152 mHandler.obtainMessage( 153 ListenerHandler.HARDWARE_DEVICE_REMOVED, 0, 0, info).sendToTarget(); 154 } 155 } 156 157 @Override 158 public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs) { 159 synchronized (mLock) { 160 Connection connection = mConnections.get(deviceId); 161 if (connection == null) { 162 Slog.e(TAG, "StreamConfigurationChanged: Cannot find a connection with " 163 + deviceId); 164 return; 165 } 166 connection.updateConfigsLocked(configs); 167 try { 168 connection.getCallbackLocked().onStreamConfigChanged(configs); 169 } catch (RemoteException e) { 170 Slog.e(TAG, "error in onStreamConfigurationChanged", e); 171 } 172 } 173 } 174 175 @Override 176 public void onFirstFrameCaptured(int deviceId, int streamId) { 177 synchronized (mLock) { 178 Connection connection = mConnections.get(deviceId); 179 if (connection == null) { 180 Slog.e(TAG, "FirstFrameCaptured: Cannot find a connection with " 181 + deviceId); 182 return; 183 } 184 Runnable runnable = connection.getOnFirstFrameCapturedLocked(); 185 if (runnable != null) { 186 runnable.run(); 187 connection.setOnFirstFrameCapturedLocked(null); 188 } 189 } 190 } 191 192 public List<TvInputHardwareInfo> getHardwareList() { 193 synchronized (mLock) { 194 return mInfoList; 195 } 196 } 197 198 public List<HdmiCecDeviceInfo> getHdmiCecInputDeviceList() { 199 if (mHdmiControlService != null) { 200 try { 201 return mHdmiControlService.getInputDevices(); 202 } catch (RemoteException e) { 203 Slog.e(TAG, "error in getHdmiCecInputDeviceList", e); 204 } 205 } 206 return Collections.emptyList(); 207 } 208 209 private boolean checkUidChangedLocked( 210 Connection connection, int callingUid, int resolvedUserId) { 211 Integer connectionCallingUid = connection.getCallingUidLocked(); 212 Integer connectionResolvedUserId = connection.getResolvedUserIdLocked(); 213 if (connectionCallingUid == null || connectionResolvedUserId == null) { 214 return true; 215 } 216 if (connectionCallingUid != callingUid || connectionResolvedUserId != resolvedUserId) { 217 return true; 218 } 219 return false; 220 } 221 222 private int convertConnectedToState(boolean connected) { 223 if (connected) { 224 return INPUT_STATE_CONNECTED; 225 } else { 226 return INPUT_STATE_DISCONNECTED; 227 } 228 } 229 230 public void addHardwareTvInput(int deviceId, TvInputInfo info) { 231 synchronized (mLock) { 232 String oldInputId = mHardwareInputIdMap.get(deviceId); 233 if (oldInputId != null) { 234 Slog.w(TAG, "Trying to override previous registration: old = " 235 + mInputMap.get(oldInputId) + ":" + deviceId + ", new = " 236 + info + ":" + deviceId); 237 } 238 mHardwareInputIdMap.put(deviceId, info.getId()); 239 mInputMap.put(info.getId(), info); 240 241 for (int i = 0; i < mHdmiStateMap.size(); ++i) { 242 String inputId = findInputIdForHdmiPortLocked(mHdmiStateMap.keyAt(i)); 243 if (inputId != null && inputId.equals(info.getId())) { 244 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, 245 convertConnectedToState(mHdmiStateMap.valueAt(i)), 0, 246 inputId).sendToTarget(); 247 } 248 } 249 } 250 } 251 252 private static <T> int indexOfEqualValue(SparseArray<T> map, T value) { 253 for (int i = 0; i < map.size(); ++i) { 254 if (map.valueAt(i).equals(value)) { 255 return i; 256 } 257 } 258 return -1; 259 } 260 261 public void addHdmiCecTvInput(int logicalAddress, TvInputInfo info) { 262 if (info.getType() != TvInputInfo.TYPE_HDMI) { 263 throw new IllegalArgumentException("info (" + info + ") has non-HDMI type."); 264 } 265 synchronized (mLock) { 266 String parentId = info.getParentId(); 267 int parentIndex = indexOfEqualValue(mHardwareInputIdMap, parentId); 268 if (parentIndex < 0) { 269 throw new IllegalArgumentException("info (" + info + ") has invalid parentId."); 270 } 271 String oldInputId = mHdmiCecInputIdMap.get(logicalAddress); 272 if (oldInputId != null) { 273 Slog.w(TAG, "Trying to override previous registration: old = " 274 + mInputMap.get(oldInputId) + ":" + logicalAddress + ", new = " 275 + info + ":" + logicalAddress); 276 } 277 mHdmiCecInputIdMap.put(logicalAddress, info.getId()); 278 mInputMap.put(info.getId(), info); 279 } 280 } 281 282 public void removeTvInput(String inputId) { 283 synchronized (mLock) { 284 mInputMap.remove(inputId); 285 int hardwareIndex = indexOfEqualValue(mHardwareInputIdMap, inputId); 286 if (hardwareIndex >= 0) { 287 mHardwareInputIdMap.removeAt(hardwareIndex); 288 } 289 int cecIndex = indexOfEqualValue(mHdmiCecInputIdMap, inputId); 290 if (cecIndex >= 0) { 291 mHdmiCecInputIdMap.removeAt(cecIndex); 292 } 293 } 294 } 295 296 /** 297 * Create a TvInputHardware object with a specific deviceId. One service at a time can access 298 * the object, and if more than one process attempts to create hardware with the same deviceId, 299 * the latest service will get the object and all the other hardware are released. The 300 * release is notified via ITvInputHardwareCallback.onReleased(). 301 */ 302 public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback, 303 TvInputInfo info, int callingUid, int resolvedUserId) { 304 if (callback == null) { 305 throw new NullPointerException(); 306 } 307 synchronized (mLock) { 308 Connection connection = mConnections.get(deviceId); 309 if (connection == null) { 310 Slog.e(TAG, "Invalid deviceId : " + deviceId); 311 return null; 312 } 313 if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) { 314 TvInputHardwareImpl hardware = 315 new TvInputHardwareImpl(connection.getHardwareInfoLocked()); 316 try { 317 callback.asBinder().linkToDeath(connection, 0); 318 } catch (RemoteException e) { 319 hardware.release(); 320 return null; 321 } 322 connection.resetLocked(hardware, callback, info, callingUid, resolvedUserId); 323 } 324 return connection.getHardwareLocked(); 325 } 326 } 327 328 /** 329 * Release the specified hardware. 330 */ 331 public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid, 332 int resolvedUserId) { 333 synchronized (mLock) { 334 Connection connection = mConnections.get(deviceId); 335 if (connection == null) { 336 Slog.e(TAG, "Invalid deviceId : " + deviceId); 337 return; 338 } 339 if (connection.getHardwareLocked() != hardware 340 || checkUidChangedLocked(connection, callingUid, resolvedUserId)) { 341 return; 342 } 343 connection.resetLocked(null, null, null, null, null); 344 } 345 } 346 347 private String findInputIdForHdmiPortLocked(int port) { 348 for (TvInputHardwareInfo hardwareInfo : mInfoList) { 349 if (hardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI 350 && hardwareInfo.getHdmiPortId() == port) { 351 return mHardwareInputIdMap.get(hardwareInfo.getDeviceId()); 352 } 353 } 354 return null; 355 } 356 357 private int findDeviceIdForInputIdLocked(String inputId) { 358 for (int i = 0; i < mConnections.size(); ++i) { 359 Connection connection = mConnections.get(i); 360 if (connection.getInfoLocked().getId().equals(inputId)) { 361 return i; 362 } 363 } 364 return -1; 365 } 366 367 /** 368 * Get the list of TvStreamConfig which is buffered mode. 369 */ 370 public List<TvStreamConfig> getAvailableTvStreamConfigList(String inputId, int callingUid, 371 int resolvedUserId) { 372 List<TvStreamConfig> configsList = new ArrayList<TvStreamConfig>(); 373 synchronized (mLock) { 374 int deviceId = findDeviceIdForInputIdLocked(inputId); 375 if (deviceId < 0) { 376 Slog.e(TAG, "Invalid inputId : " + inputId); 377 return configsList; 378 } 379 Connection connection = mConnections.get(deviceId); 380 for (TvStreamConfig config : connection.getConfigsLocked()) { 381 if (config.getType() == TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) { 382 configsList.add(config); 383 } 384 } 385 } 386 return configsList; 387 } 388 389 /** 390 * Take a snapshot of the given TV input into the provided Surface. 391 */ 392 public boolean captureFrame(String inputId, Surface surface, final TvStreamConfig config, 393 int callingUid, int resolvedUserId) { 394 synchronized (mLock) { 395 int deviceId = findDeviceIdForInputIdLocked(inputId); 396 if (deviceId < 0) { 397 Slog.e(TAG, "Invalid inputId : " + inputId); 398 return false; 399 } 400 Connection connection = mConnections.get(deviceId); 401 final TvInputHardwareImpl hardwareImpl = connection.getHardwareImplLocked(); 402 if (hardwareImpl != null) { 403 // Stop previous capture. 404 Runnable runnable = connection.getOnFirstFrameCapturedLocked(); 405 if (runnable != null) { 406 runnable.run(); 407 connection.setOnFirstFrameCapturedLocked(null); 408 } 409 410 boolean result = hardwareImpl.startCapture(surface, config); 411 if (result) { 412 connection.setOnFirstFrameCapturedLocked(new Runnable() { 413 @Override 414 public void run() { 415 hardwareImpl.stopCapture(config); 416 } 417 }); 418 } 419 return result; 420 } 421 } 422 return false; 423 } 424 425 private class Connection implements IBinder.DeathRecipient { 426 private final TvInputHardwareInfo mHardwareInfo; 427 private TvInputInfo mInfo; 428 private TvInputHardwareImpl mHardware = null; 429 private ITvInputHardwareCallback mCallback; 430 private TvStreamConfig[] mConfigs = null; 431 private Integer mCallingUid = null; 432 private Integer mResolvedUserId = null; 433 private Runnable mOnFirstFrameCaptured; 434 435 public Connection(TvInputHardwareInfo hardwareInfo) { 436 mHardwareInfo = hardwareInfo; 437 } 438 439 // *Locked methods assume TvInputHardwareManager.mLock is held. 440 441 public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback, 442 TvInputInfo info, Integer callingUid, Integer resolvedUserId) { 443 if (mHardware != null) { 444 try { 445 mCallback.onReleased(); 446 } catch (RemoteException e) { 447 Slog.e(TAG, "error in Connection::resetLocked", e); 448 } 449 mHardware.release(); 450 } 451 mHardware = hardware; 452 mCallback = callback; 453 mInfo = info; 454 mCallingUid = callingUid; 455 mResolvedUserId = resolvedUserId; 456 mOnFirstFrameCaptured = null; 457 458 if (mHardware != null && mCallback != null) { 459 try { 460 mCallback.onStreamConfigChanged(getConfigsLocked()); 461 } catch (RemoteException e) { 462 Slog.e(TAG, "error in Connection::resetLocked", e); 463 } 464 } 465 } 466 467 public void updateConfigsLocked(TvStreamConfig[] configs) { 468 mConfigs = configs; 469 } 470 471 public TvInputHardwareInfo getHardwareInfoLocked() { 472 return mHardwareInfo; 473 } 474 475 public TvInputInfo getInfoLocked() { 476 return mInfo; 477 } 478 479 public ITvInputHardware getHardwareLocked() { 480 return mHardware; 481 } 482 483 public TvInputHardwareImpl getHardwareImplLocked() { 484 return mHardware; 485 } 486 487 public ITvInputHardwareCallback getCallbackLocked() { 488 return mCallback; 489 } 490 491 public TvStreamConfig[] getConfigsLocked() { 492 return mConfigs; 493 } 494 495 public Integer getCallingUidLocked() { 496 return mCallingUid; 497 } 498 499 public Integer getResolvedUserIdLocked() { 500 return mResolvedUserId; 501 } 502 503 public void setOnFirstFrameCapturedLocked(Runnable runnable) { 504 mOnFirstFrameCaptured = runnable; 505 } 506 507 public Runnable getOnFirstFrameCapturedLocked() { 508 return mOnFirstFrameCaptured; 509 } 510 511 @Override 512 public void binderDied() { 513 synchronized (mLock) { 514 resetLocked(null, null, null, null, null); 515 } 516 } 517 } 518 519 private class TvInputHardwareImpl extends ITvInputHardware.Stub { 520 private final TvInputHardwareInfo mInfo; 521 private boolean mReleased = false; 522 private final Object mImplLock = new Object(); 523 524 private final AudioDevicePort mAudioSource; 525 private final AudioDevicePort mAudioSink; 526 private AudioPatch mAudioPatch = null; 527 528 private TvStreamConfig mActiveConfig = null; 529 530 public TvInputHardwareImpl(TvInputHardwareInfo info) { 531 mInfo = info; 532 AudioDevicePort audioSource = null; 533 AudioDevicePort audioSink = null; 534 if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) { 535 ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>(); 536 if (mAudioManager.listAudioDevicePorts(devicePorts) == AudioManager.SUCCESS) { 537 // Find source 538 for (AudioPort port : devicePorts) { 539 AudioDevicePort devicePort = (AudioDevicePort) port; 540 if (devicePort.type() == mInfo.getAudioType() && 541 devicePort.address().equals(mInfo.getAudioAddress())) { 542 audioSource = devicePort; 543 break; 544 } 545 } 546 // Find sink 547 // TODO: App may want to specify sink device? 548 int sinkDevices = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC); 549 for (AudioPort port : devicePorts) { 550 AudioDevicePort devicePort = (AudioDevicePort) port; 551 if (devicePort.type() == sinkDevices) { 552 audioSink = devicePort; 553 break; 554 } 555 } 556 } 557 } 558 mAudioSource = audioSource; 559 mAudioSink = audioSink; 560 } 561 562 public void release() { 563 synchronized (mImplLock) { 564 if (mAudioPatch != null) { 565 mAudioManager.releaseAudioPatch(mAudioPatch); 566 mAudioPatch = null; 567 } 568 mReleased = true; 569 } 570 } 571 572 // A TvInputHardwareImpl object holds only one active session. Therefore, if a client 573 // attempts to call setSurface with different TvStreamConfig objects, the last call will 574 // prevail. 575 @Override 576 public boolean setSurface(Surface surface, TvStreamConfig config) 577 throws RemoteException { 578 synchronized (mImplLock) { 579 if (mReleased) { 580 throw new IllegalStateException("Device already released."); 581 } 582 if (surface != null && config == null) { 583 return false; 584 } 585 if (surface == null && mActiveConfig == null) { 586 return false; 587 } 588 if (mInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) { 589 if (surface != null) { 590 // Set "Active Source" for HDMI. 591 // TODO(hdmi): mHdmiClient.deviceSelect(...); 592 mActiveHdmiSources.add(mInfo.getDeviceId()); 593 } else { 594 mActiveHdmiSources.remove(mInfo.getDeviceId()); 595 if (mActiveHdmiSources.size() == 0) { 596 // Tell HDMI that no HDMI source is active 597 // TODO(hdmi): mHdmiClient.portSelect(null); 598 } 599 } 600 } 601 if (mAudioSource != null && mAudioSink != null) { 602 if (surface != null) { 603 AudioPortConfig sourceConfig = mAudioSource.activeConfig(); 604 AudioPortConfig sinkConfig = mAudioSink.activeConfig(); 605 AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch }; 606 // TODO: build config if activeConfig() == null 607 mAudioManager.createAudioPatch( 608 audioPatchArray, 609 new AudioPortConfig[] { sourceConfig }, 610 new AudioPortConfig[] { sinkConfig }); 611 mAudioPatch = audioPatchArray[0]; 612 } else { 613 mAudioManager.releaseAudioPatch(mAudioPatch); 614 mAudioPatch = null; 615 } 616 } 617 int result = TvInputHal.ERROR_UNKNOWN; 618 if (surface == null) { 619 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig); 620 mActiveConfig = null; 621 } else { 622 if (config != mActiveConfig && mActiveConfig != null) { 623 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig); 624 if (result != TvInputHal.SUCCESS) { 625 mActiveConfig = null; 626 return false; 627 } 628 } 629 result = mHal.addStream(mInfo.getDeviceId(), surface, config); 630 if (result == TvInputHal.SUCCESS) { 631 mActiveConfig = config; 632 } 633 } 634 return result == TvInputHal.SUCCESS; 635 } 636 } 637 638 @Override 639 public void setVolume(float volume) throws RemoteException { 640 synchronized (mImplLock) { 641 if (mReleased) { 642 throw new IllegalStateException("Device already released."); 643 } 644 } 645 // TODO: Use AudioGain? 646 } 647 648 @Override 649 public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException { 650 synchronized (mImplLock) { 651 if (mReleased) { 652 throw new IllegalStateException("Device already released."); 653 } 654 } 655 if (mInfo.getType() != TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) { 656 return false; 657 } 658 // TODO(hdmi): mHdmiClient.sendKeyEvent(event); 659 return false; 660 } 661 662 private boolean startCapture(Surface surface, TvStreamConfig config) { 663 synchronized (mImplLock) { 664 if (mReleased) { 665 return false; 666 } 667 if (surface == null || config == null) { 668 return false; 669 } 670 if (config.getType() != TvStreamConfig.STREAM_TYPE_BUFFER_PRODUCER) { 671 return false; 672 } 673 674 int result = mHal.addStream(mInfo.getDeviceId(), surface, config); 675 return result == TvInputHal.SUCCESS; 676 } 677 } 678 679 private boolean stopCapture(TvStreamConfig config) { 680 synchronized (mImplLock) { 681 if (mReleased) { 682 return false; 683 } 684 if (config == null) { 685 return false; 686 } 687 688 int result = mHal.removeStream(mInfo.getDeviceId(), config); 689 return result == TvInputHal.SUCCESS; 690 } 691 } 692 } 693 694 interface Listener { 695 public void onStateChanged(String inputId, int state); 696 public void onHardwareDeviceAdded(TvInputHardwareInfo info); 697 public void onHardwareDeviceRemoved(TvInputHardwareInfo info); 698 public void onHdmiCecDeviceAdded(HdmiCecDeviceInfo cecDevice); 699 public void onHdmiCecDeviceRemoved(HdmiCecDeviceInfo cecDevice); 700 } 701 702 private class ListenerHandler extends Handler { 703 private static final int STATE_CHANGED = 1; 704 private static final int HARDWARE_DEVICE_ADDED = 2; 705 private static final int HARDWARE_DEVICE_REMOVED = 3; 706 private static final int HDMI_CEC_DEVICE_ADDED = 4; 707 private static final int HDMI_CEC_DEVICE_REMOVED = 5; 708 709 @Override 710 public final void handleMessage(Message msg) { 711 switch (msg.what) { 712 case STATE_CHANGED: { 713 String inputId = (String) msg.obj; 714 int state = msg.arg1; 715 mListener.onStateChanged(inputId, state); 716 break; 717 } 718 case HARDWARE_DEVICE_ADDED: { 719 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj; 720 mListener.onHardwareDeviceAdded(info); 721 break; 722 } 723 case HARDWARE_DEVICE_REMOVED: { 724 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj; 725 mListener.onHardwareDeviceRemoved(info); 726 break; 727 } 728 case HDMI_CEC_DEVICE_ADDED: { 729 HdmiCecDeviceInfo info = (HdmiCecDeviceInfo) msg.obj; 730 mListener.onHdmiCecDeviceAdded(info); 731 break; 732 } 733 case HDMI_CEC_DEVICE_REMOVED: { 734 HdmiCecDeviceInfo info = (HdmiCecDeviceInfo) msg.obj; 735 mListener.onHdmiCecDeviceRemoved(info); 736 break; 737 } 738 default: { 739 Slog.w(TAG, "Unhandled message: " + msg); 740 break; 741 } 742 } 743 } 744 } 745 746 // Listener implementations for HdmiControlService 747 748 private final class HdmiHotplugEventListener extends IHdmiHotplugEventListener.Stub { 749 @Override 750 public void onReceived(HdmiHotplugEvent event) { 751 synchronized (mLock) { 752 mHdmiStateMap.put(event.getPort(), event.isConnected()); 753 String inputId = findInputIdForHdmiPortLocked(event.getPort()); 754 if (inputId == null) { 755 return; 756 } 757 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, 758 convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget(); 759 } 760 } 761 } 762 763 private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub { 764 @Override 765 public void onStatusChanged(HdmiCecDeviceInfo deviceInfo, boolean activated) { 766 mHandler.obtainMessage( 767 activated ? ListenerHandler.HDMI_CEC_DEVICE_ADDED 768 : ListenerHandler.HDMI_CEC_DEVICE_REMOVED, 769 0, 0, deviceInfo).sendToTarget(); 770 } 771 } 772 773 private final class HdmiInputChangeListener extends IHdmiInputChangeListener.Stub { 774 @Override 775 public void onChanged(HdmiCecDeviceInfo device) throws RemoteException { 776 // TODO: Build a channel Uri for the TvInputInfo associated with the logical device 777 // and send an intent to TV app 778 } 779 } 780} 781