TvInputHardwareManager.java revision 0f68d62a4b10aff3c99162bf3a8322d38e643088
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 private final Set<Integer> mActiveHdmiSources = new HashSet<Integer>(); 92 // TODO: Should handle INACTIVE case. 93 private final SparseBooleanArray mHdmiStateMap = new SparseBooleanArray(); 94 95 // Calls to mListener should happen here. 96 private final Handler mHandler = new ListenerHandler(); 97 98 private final Object mLock = new Object(); 99 100 public TvInputHardwareManager(Context context, Listener listener) { 101 mContext = context; 102 mListener = listener; 103 mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 104 mHal.init(); 105 } 106 107 public void onBootPhase(int phase) { 108 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { 109 mHdmiControlService = IHdmiControlService.Stub.asInterface(ServiceManager.getService( 110 Context.HDMI_CONTROL_SERVICE)); 111 if (mHdmiControlService != null) { 112 try { 113 mHdmiControlService.addHotplugEventListener(mHdmiHotplugEventListener); 114 mHdmiControlService.addDeviceEventListener(mHdmiDeviceEventListener); 115 mHdmiControlService.setInputChangeListener(mHdmiInputChangeListener); 116 } catch (RemoteException e) { 117 Slog.w(TAG, "Error registering listeners to HdmiControlService:", e); 118 } 119 } 120 } 121 } 122 123 @Override 124 public void onDeviceAvailable(TvInputHardwareInfo info, TvStreamConfig[] configs) { 125 synchronized (mLock) { 126 Connection connection = new Connection(info); 127 connection.updateConfigsLocked(configs); 128 mConnections.put(info.getDeviceId(), connection); 129 buildInfoListLocked(); 130 mHandler.obtainMessage( 131 ListenerHandler.HARDWARE_DEVICE_ADDED, 0, 0, info).sendToTarget(); 132 } 133 } 134 135 private void buildInfoListLocked() { 136 mInfoList.clear(); 137 for (int i = 0; i < mConnections.size(); ++i) { 138 mInfoList.add(mConnections.valueAt(i).getHardwareInfoLocked()); 139 } 140 } 141 142 @Override 143 public void onDeviceUnavailable(int deviceId) { 144 synchronized (mLock) { 145 Connection connection = mConnections.get(deviceId); 146 if (connection == null) { 147 Slog.e(TAG, "onDeviceUnavailable: Cannot find a connection with " + deviceId); 148 return; 149 } 150 connection.resetLocked(null, null, null, null, null); 151 mConnections.remove(deviceId); 152 buildInfoListLocked(); 153 TvInputHardwareInfo info = connection.getHardwareInfoLocked(); 154 mHandler.obtainMessage( 155 ListenerHandler.HARDWARE_DEVICE_REMOVED, 0, 0, info).sendToTarget(); 156 } 157 } 158 159 @Override 160 public void onStreamConfigurationChanged(int deviceId, TvStreamConfig[] configs) { 161 synchronized (mLock) { 162 Connection connection = mConnections.get(deviceId); 163 if (connection == null) { 164 Slog.e(TAG, "StreamConfigurationChanged: Cannot find a connection with " 165 + deviceId); 166 return; 167 } 168 connection.updateConfigsLocked(configs); 169 try { 170 connection.getCallbackLocked().onStreamConfigChanged(configs); 171 } catch (RemoteException e) { 172 Slog.e(TAG, "error in onStreamConfigurationChanged", e); 173 } 174 } 175 } 176 177 public List<TvInputHardwareInfo> getHardwareList() { 178 synchronized (mLock) { 179 return mInfoList; 180 } 181 } 182 183 public List<HdmiCecDeviceInfo> getHdmiCecInputDeviceList() { 184 if (mHdmiControlService != null) { 185 try { 186 return mHdmiControlService.getInputDevices(); 187 } catch (RemoteException e) { 188 Slog.e(TAG, "error in getHdmiCecInputDeviceList", e); 189 } 190 } 191 return Collections.emptyList(); 192 } 193 194 private boolean checkUidChangedLocked( 195 Connection connection, int callingUid, int resolvedUserId) { 196 Integer connectionCallingUid = connection.getCallingUidLocked(); 197 Integer connectionResolvedUserId = connection.getResolvedUserIdLocked(); 198 if (connectionCallingUid == null || connectionResolvedUserId == null) { 199 return true; 200 } 201 if (connectionCallingUid != callingUid || connectionResolvedUserId != resolvedUserId) { 202 return true; 203 } 204 return false; 205 } 206 207 private int convertConnectedToState(boolean connected) { 208 if (connected) { 209 return INPUT_STATE_CONNECTED; 210 } else { 211 return INPUT_STATE_DISCONNECTED; 212 } 213 } 214 215 public void addHardwareTvInput(int deviceId, TvInputInfo info) { 216 if (info.getType() == TvInputInfo.TYPE_VIRTUAL) { 217 throw new IllegalArgumentException("info (" + info + ") has virtual type."); 218 } 219 synchronized (mLock) { 220 String oldInputId = mHardwareInputIdMap.get(deviceId); 221 if (oldInputId != null) { 222 Slog.w(TAG, "Trying to override previous registration: old = " 223 + mInputMap.get(oldInputId) + ":" + deviceId + ", new = " 224 + info + ":" + deviceId); 225 } 226 mHardwareInputIdMap.put(deviceId, info.getId()); 227 mInputMap.put(info.getId(), info); 228 229 for (int i = 0; i < mHdmiStateMap.size(); ++i) { 230 String inputId = findInputIdForHdmiPortLocked(mHdmiStateMap.keyAt(i)); 231 if (inputId != null && inputId.equals(info.getId())) { 232 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, 233 convertConnectedToState(mHdmiStateMap.valueAt(i)), 0, 234 inputId).sendToTarget(); 235 } 236 } 237 } 238 } 239 240 private static <T> int indexOfEqualValue(SparseArray<T> map, T value) { 241 for (int i = 0; i < map.size(); ++i) { 242 if (map.valueAt(i).equals(value)) { 243 return i; 244 } 245 } 246 return -1; 247 } 248 249 public void addHdmiCecTvInput(int logicalAddress, TvInputInfo info) { 250 if (info.getType() != TvInputInfo.TYPE_HDMI) { 251 throw new IllegalArgumentException("info (" + info + ") has non-HDMI type."); 252 } 253 synchronized (mLock) { 254 String parentId = info.getParentId(); 255 int parentIndex = indexOfEqualValue(mHardwareInputIdMap, parentId); 256 if (parentIndex < 0) { 257 throw new IllegalArgumentException("info (" + info + ") has invalid parentId."); 258 } 259 String oldInputId = mHdmiCecInputIdMap.get(logicalAddress); 260 if (oldInputId != null) { 261 Slog.w(TAG, "Trying to override previous registration: old = " 262 + mInputMap.get(oldInputId) + ":" + logicalAddress + ", new = " 263 + info + ":" + logicalAddress); 264 } 265 mHdmiCecInputIdMap.put(logicalAddress, info.getId()); 266 mInputMap.put(info.getId(), info); 267 } 268 } 269 270 public void removeTvInput(String inputId) { 271 synchronized (mLock) { 272 mInputMap.remove(inputId); 273 int hardwareIndex = indexOfEqualValue(mHardwareInputIdMap, inputId); 274 if (hardwareIndex >= 0) { 275 mHardwareInputIdMap.removeAt(hardwareIndex); 276 } 277 int cecIndex = indexOfEqualValue(mHdmiCecInputIdMap, inputId); 278 if (cecIndex >= 0) { 279 mHdmiCecInputIdMap.removeAt(cecIndex); 280 } 281 } 282 } 283 284 /** 285 * Create a TvInputHardware object with a specific deviceId. One service at a time can access 286 * the object, and if more than one process attempts to create hardware with the same deviceId, 287 * the latest service will get the object and all the other hardware are released. The 288 * release is notified via ITvInputHardwareCallback.onReleased(). 289 */ 290 public ITvInputHardware acquireHardware(int deviceId, ITvInputHardwareCallback callback, 291 TvInputInfo info, int callingUid, int resolvedUserId) { 292 if (callback == null) { 293 throw new NullPointerException(); 294 } 295 synchronized (mLock) { 296 Connection connection = mConnections.get(deviceId); 297 if (connection == null) { 298 Slog.e(TAG, "Invalid deviceId : " + deviceId); 299 return null; 300 } 301 if (checkUidChangedLocked(connection, callingUid, resolvedUserId)) { 302 TvInputHardwareImpl hardware = 303 new TvInputHardwareImpl(connection.getHardwareInfoLocked()); 304 try { 305 callback.asBinder().linkToDeath(connection, 0); 306 } catch (RemoteException e) { 307 hardware.release(); 308 return null; 309 } 310 connection.resetLocked(hardware, callback, info, callingUid, resolvedUserId); 311 } 312 return connection.getHardwareLocked(); 313 } 314 } 315 316 /** 317 * Release the specified hardware. 318 */ 319 public void releaseHardware(int deviceId, ITvInputHardware hardware, int callingUid, 320 int resolvedUserId) { 321 synchronized (mLock) { 322 Connection connection = mConnections.get(deviceId); 323 if (connection == null) { 324 Slog.e(TAG, "Invalid deviceId : " + deviceId); 325 return; 326 } 327 if (connection.getHardwareLocked() != hardware 328 || checkUidChangedLocked(connection, callingUid, resolvedUserId)) { 329 return; 330 } 331 connection.resetLocked(null, null, null, null, null); 332 } 333 } 334 335 private String findInputIdForHdmiPortLocked(int port) { 336 for (TvInputHardwareInfo hardwareInfo : mInfoList) { 337 if (hardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI 338 && hardwareInfo.getHdmiPortId() == port) { 339 return mHardwareInputIdMap.get(hardwareInfo.getDeviceId()); 340 } 341 } 342 return null; 343 } 344 345 private class Connection implements IBinder.DeathRecipient { 346 private final TvInputHardwareInfo mHardwareInfo; 347 private TvInputInfo mInfo; 348 private TvInputHardwareImpl mHardware = null; 349 private ITvInputHardwareCallback mCallback; 350 private TvStreamConfig[] mConfigs = null; 351 private Integer mCallingUid = null; 352 private Integer mResolvedUserId = null; 353 354 public Connection(TvInputHardwareInfo hardwareInfo) { 355 mHardwareInfo = hardwareInfo; 356 } 357 358 // *Locked methods assume TvInputHardwareManager.mLock is held. 359 360 public void resetLocked(TvInputHardwareImpl hardware, ITvInputHardwareCallback callback, 361 TvInputInfo info, Integer callingUid, Integer resolvedUserId) { 362 if (mHardware != null) { 363 try { 364 mCallback.onReleased(); 365 } catch (RemoteException e) { 366 Slog.e(TAG, "error in Connection::resetLocked", e); 367 } 368 mHardware.release(); 369 } 370 mHardware = hardware; 371 mCallback = callback; 372 mInfo = info; 373 mCallingUid = callingUid; 374 mResolvedUserId = resolvedUserId; 375 376 if (mHardware != null && mCallback != null) { 377 try { 378 mCallback.onStreamConfigChanged(getConfigsLocked()); 379 } catch (RemoteException e) { 380 Slog.e(TAG, "error in Connection::resetLocked", e); 381 } 382 } 383 } 384 385 public void updateConfigsLocked(TvStreamConfig[] configs) { 386 mConfigs = configs; 387 } 388 389 public TvInputHardwareInfo getHardwareInfoLocked() { 390 return mHardwareInfo; 391 } 392 393 public TvInputInfo getInfoLocked() { 394 return mInfo; 395 } 396 397 public ITvInputHardware getHardwareLocked() { 398 return mHardware; 399 } 400 401 public ITvInputHardwareCallback getCallbackLocked() { 402 return mCallback; 403 } 404 405 public TvStreamConfig[] getConfigsLocked() { 406 return mConfigs; 407 } 408 409 public Integer getCallingUidLocked() { 410 return mCallingUid; 411 } 412 413 public Integer getResolvedUserIdLocked() { 414 return mResolvedUserId; 415 } 416 417 @Override 418 public void binderDied() { 419 synchronized (mLock) { 420 resetLocked(null, null, null, null, null); 421 } 422 } 423 } 424 425 private class TvInputHardwareImpl extends ITvInputHardware.Stub { 426 private final TvInputHardwareInfo mInfo; 427 private boolean mReleased = false; 428 private final Object mImplLock = new Object(); 429 430 private final AudioDevicePort mAudioSource; 431 private final AudioDevicePort mAudioSink; 432 private AudioPatch mAudioPatch = null; 433 434 private TvStreamConfig mActiveConfig = null; 435 436 public TvInputHardwareImpl(TvInputHardwareInfo info) { 437 mInfo = info; 438 AudioDevicePort audioSource = null; 439 AudioDevicePort audioSink = null; 440 if (mInfo.getAudioType() != AudioManager.DEVICE_NONE) { 441 ArrayList<AudioPort> devicePorts = new ArrayList<AudioPort>(); 442 if (mAudioManager.listAudioDevicePorts(devicePorts) == AudioManager.SUCCESS) { 443 // Find source 444 for (AudioPort port : devicePorts) { 445 AudioDevicePort devicePort = (AudioDevicePort) port; 446 if (devicePort.type() == mInfo.getAudioType() && 447 devicePort.address().equals(mInfo.getAudioAddress())) { 448 audioSource = devicePort; 449 break; 450 } 451 } 452 // Find sink 453 // TODO: App may want to specify sink device? 454 int sinkDevices = mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC); 455 for (AudioPort port : devicePorts) { 456 AudioDevicePort devicePort = (AudioDevicePort) port; 457 if (devicePort.type() == sinkDevices) { 458 audioSink = devicePort; 459 break; 460 } 461 } 462 } 463 } 464 mAudioSource = audioSource; 465 mAudioSink = audioSink; 466 } 467 468 public void release() { 469 synchronized (mImplLock) { 470 if (mAudioPatch != null) { 471 mAudioManager.releaseAudioPatch(mAudioPatch); 472 mAudioPatch = null; 473 } 474 mReleased = true; 475 } 476 } 477 478 // A TvInputHardwareImpl object holds only one active session. Therefore, if a client 479 // attempts to call setSurface with different TvStreamConfig objects, the last call will 480 // prevail. 481 @Override 482 public boolean setSurface(Surface surface, TvStreamConfig config) 483 throws RemoteException { 484 synchronized (mImplLock) { 485 if (mReleased) { 486 throw new IllegalStateException("Device already released."); 487 } 488 if (surface != null && config == null) { 489 return false; 490 } 491 if (surface == null && mActiveConfig == null) { 492 return false; 493 } 494 if (mInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) { 495 if (surface != null) { 496 // Set "Active Source" for HDMI. 497 // TODO(hdmi): mHdmiClient.deviceSelect(...); 498 mActiveHdmiSources.add(mInfo.getDeviceId()); 499 } else { 500 mActiveHdmiSources.remove(mInfo.getDeviceId()); 501 if (mActiveHdmiSources.size() == 0) { 502 // Tell HDMI that no HDMI source is active 503 // TODO(hdmi): mHdmiClient.portSelect(null); 504 } 505 } 506 } 507 if (mAudioSource != null && mAudioSink != null) { 508 if (surface != null) { 509 AudioPortConfig sourceConfig = mAudioSource.activeConfig(); 510 AudioPortConfig sinkConfig = mAudioSink.activeConfig(); 511 AudioPatch[] audioPatchArray = new AudioPatch[] { mAudioPatch }; 512 // TODO: build config if activeConfig() == null 513 mAudioManager.createAudioPatch( 514 audioPatchArray, 515 new AudioPortConfig[] { sourceConfig }, 516 new AudioPortConfig[] { sinkConfig }); 517 mAudioPatch = audioPatchArray[0]; 518 } else { 519 mAudioManager.releaseAudioPatch(mAudioPatch); 520 mAudioPatch = null; 521 } 522 } 523 int result = TvInputHal.ERROR_UNKNOWN; 524 if (surface == null) { 525 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig); 526 mActiveConfig = null; 527 } else { 528 if (config != mActiveConfig && mActiveConfig != null) { 529 result = mHal.removeStream(mInfo.getDeviceId(), mActiveConfig); 530 if (result != TvInputHal.SUCCESS) { 531 mActiveConfig = null; 532 return false; 533 } 534 } 535 result = mHal.addStream(mInfo.getDeviceId(), surface, config); 536 if (result == TvInputHal.SUCCESS) { 537 mActiveConfig = config; 538 } 539 } 540 return result == TvInputHal.SUCCESS; 541 } 542 } 543 544 @Override 545 public void setVolume(float volume) throws RemoteException { 546 synchronized (mImplLock) { 547 if (mReleased) { 548 throw new IllegalStateException("Device already released."); 549 } 550 } 551 // TODO: Use AudioGain? 552 } 553 554 @Override 555 public boolean dispatchKeyEventToHdmi(KeyEvent event) throws RemoteException { 556 synchronized (mImplLock) { 557 if (mReleased) { 558 throw new IllegalStateException("Device already released."); 559 } 560 } 561 if (mInfo.getType() != TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) { 562 return false; 563 } 564 // TODO(hdmi): mHdmiClient.sendKeyEvent(event); 565 return false; 566 } 567 } 568 569 interface Listener { 570 public void onStateChanged(String inputId, int state); 571 public void onHardwareDeviceAdded(TvInputHardwareInfo info); 572 public void onHardwareDeviceRemoved(TvInputHardwareInfo info); 573 public void onHdmiCecDeviceAdded(HdmiCecDeviceInfo cecDevice); 574 public void onHdmiCecDeviceRemoved(HdmiCecDeviceInfo cecDevice); 575 } 576 577 private class ListenerHandler extends Handler { 578 private static final int STATE_CHANGED = 1; 579 private static final int HARDWARE_DEVICE_ADDED = 2; 580 private static final int HARDWARE_DEVICE_REMOVED = 3; 581 private static final int HDMI_CEC_DEVICE_ADDED = 4; 582 private static final int HDMI_CEC_DEVICE_REMOVED = 5; 583 584 @Override 585 public final void handleMessage(Message msg) { 586 switch (msg.what) { 587 case STATE_CHANGED: { 588 String inputId = (String) msg.obj; 589 int state = msg.arg1; 590 mListener.onStateChanged(inputId, state); 591 break; 592 } 593 case HARDWARE_DEVICE_ADDED: { 594 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj; 595 mListener.onHardwareDeviceAdded(info); 596 break; 597 } 598 case HARDWARE_DEVICE_REMOVED: { 599 TvInputHardwareInfo info = (TvInputHardwareInfo) msg.obj; 600 mListener.onHardwareDeviceRemoved(info); 601 break; 602 } 603 case HDMI_CEC_DEVICE_ADDED: { 604 HdmiCecDeviceInfo info = (HdmiCecDeviceInfo) msg.obj; 605 mListener.onHdmiCecDeviceAdded(info); 606 break; 607 } 608 case HDMI_CEC_DEVICE_REMOVED: { 609 HdmiCecDeviceInfo info = (HdmiCecDeviceInfo) msg.obj; 610 mListener.onHdmiCecDeviceRemoved(info); 611 break; 612 } 613 default: { 614 Slog.w(TAG, "Unhandled message: " + msg); 615 break; 616 } 617 } 618 } 619 } 620 621 // Listener implementations for HdmiControlService 622 623 private final class HdmiHotplugEventListener extends IHdmiHotplugEventListener.Stub { 624 @Override 625 public void onReceived(HdmiHotplugEvent event) { 626 synchronized (mLock) { 627 mHdmiStateMap.put(event.getPort(), event.isConnected()); 628 String inputId = findInputIdForHdmiPortLocked(event.getPort()); 629 if (inputId == null) { 630 return; 631 } 632 mHandler.obtainMessage(ListenerHandler.STATE_CHANGED, 633 convertConnectedToState(event.isConnected()), 0, inputId).sendToTarget(); 634 } 635 } 636 } 637 638 private final class HdmiDeviceEventListener extends IHdmiDeviceEventListener.Stub { 639 @Override 640 public void onStatusChanged(HdmiCecDeviceInfo deviceInfo, boolean activated) { 641 mHandler.obtainMessage( 642 activated ? ListenerHandler.HDMI_CEC_DEVICE_ADDED 643 : ListenerHandler.HDMI_CEC_DEVICE_REMOVED, 644 0, 0, deviceInfo).sendToTarget(); 645 } 646 } 647 648 private final class HdmiInputChangeListener extends IHdmiInputChangeListener.Stub { 649 @Override 650 public void onChanged(HdmiCecDeviceInfo device) throws RemoteException { 651 String inputId = mHdmiCecInputMap.get(device.getLogicalAddress()); 652 if (inputId != null) { 653 Intent intent = new Intent(Intent.ACTION_VIEW); 654 intent.setData(TvContract.buildChannelUriForPassthroughTvInput(inputId)); 655 mContext.startActivity(intent); 656 } 657 } 658 } 659} 660