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