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