HdmiCecLocalDeviceTv.java revision 8333571bd5e0a08773a1679964f8d96227af3356
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.hdmi; 18 19import android.hardware.hdmi.HdmiCec; 20import android.hardware.hdmi.HdmiCecDeviceInfo; 21import android.hardware.hdmi.HdmiCecMessage; 22import android.hardware.hdmi.IHdmiControlCallback; 23import android.media.AudioSystem; 24import android.os.RemoteException; 25import android.util.Slog; 26import android.util.SparseArray; 27 28import com.android.internal.annotations.GuardedBy; 29import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback; 30import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; 31 32import java.util.ArrayList; 33import java.util.Collections; 34import java.util.List; 35import java.util.Locale; 36 37/** 38 * Represent a logical device of type TV residing in Android system. 39 */ 40final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { 41 private static final String TAG = "HdmiCecLocalDeviceTv"; 42 43 // Whether ARC is "enabled" or not. 44 @GuardedBy("mLock") 45 private boolean mArcStatusEnabled = false; 46 47 // Whether SystemAudioMode is "On" or not. 48 @GuardedBy("mLock") 49 private boolean mSystemAudioMode; 50 51 // The previous port id (input) before switching to the new one. This is remembered in order to 52 // be able to switch to it upon receiving <Inactive Source> from currently active source. 53 // This remains valid only when the active source was switched via one touch play operation 54 // (either by TV or source device). Manual port switching invalidates this value to 55 // HdmiConstants.PORT_INVALID, for which case <Inactive Source> does not do anything. 56 @GuardedBy("mLock") 57 private int mPrevPortId; 58 59 // Copy of mDeviceInfos to guarantee thread-safety. 60 @GuardedBy("mLock") 61 private List<HdmiCecDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList(); 62 // All external cec device which excludes local devices. 63 @GuardedBy("mLock") 64 private List<HdmiCecDeviceInfo> mSafeExternalDeviceInfos = Collections.emptyList(); 65 66 // Map-like container of all cec devices including local ones. 67 // A logical address of device is used as key of container. 68 // This is not thread-safe. For external purpose use mSafeDeviceInfos. 69 private final SparseArray<HdmiCecDeviceInfo> mDeviceInfos = new SparseArray<>(); 70 71 HdmiCecLocalDeviceTv(HdmiControlService service) { 72 super(service, HdmiCec.DEVICE_TV); 73 mPrevPortId = HdmiConstants.INVALID_PORT_ID; 74 // TODO: load system audio mode and set it to mSystemAudioMode. 75 } 76 77 @Override 78 @ServiceThreadOnly 79 protected void onAddressAllocated(int logicalAddress) { 80 assertRunOnServiceThread(); 81 // TODO: vendor-specific initialization here. 82 83 mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( 84 mAddress, mService.getPhysicalAddress(), mDeviceType)); 85 mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand( 86 mAddress, mService.getVendorId())); 87 88 launchDeviceDiscovery(); 89 // TODO: Start routing control action 90 } 91 92 /** 93 * Performs the action 'device select', or 'one touch play' initiated by TV. 94 * 95 * @param targetAddress logical address of the device to select 96 * @param callback callback object to report the result with 97 */ 98 @ServiceThreadOnly 99 void deviceSelect(int targetAddress, IHdmiControlCallback callback) { 100 assertRunOnServiceThread(); 101 if (targetAddress == HdmiCec.ADDR_INTERNAL) { 102 handleSelectInternalSource(callback); 103 return; 104 } 105 HdmiCecDeviceInfo targetDevice = getDeviceInfo(targetAddress); 106 if (targetDevice == null) { 107 invokeCallback(callback, HdmiCec.RESULT_TARGET_NOT_AVAILABLE); 108 return; 109 } 110 removeAction(DeviceSelectAction.class); 111 addAndStartAction(new DeviceSelectAction(this, targetDevice, callback)); 112 } 113 114 @ServiceThreadOnly 115 private void handleSelectInternalSource(IHdmiControlCallback callback) { 116 assertRunOnServiceThread(); 117 // Seq #18 118 if (isHdmiControlEnabled() && getActiveSource() != mAddress) { 119 updateActiveSource(mAddress, mService.getPhysicalAddress()); 120 // TODO: Check if this comes from <Text/Image View On> - if true, do nothing. 121 HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource( 122 mAddress, mService.getPhysicalAddress()); 123 mService.sendCecCommand(activeSource); 124 } 125 } 126 127 @ServiceThreadOnly 128 void updateActiveSource(int activeSource, int activePath) { 129 assertRunOnServiceThread(); 130 // Seq #14 131 if (activeSource == getActiveSource() && activePath == getActivePath()) { 132 return; 133 } 134 setActiveSource(activeSource); 135 setActivePath(activePath); 136 if (getDeviceInfo(activeSource) != null && activeSource != mAddress) { 137 if (mService.pathToPortId(activePath) == getActivePortId()) { 138 setPrevPortId(getActivePortId()); 139 } 140 // TODO: Show the OSD banner related to the new active source device. 141 } else { 142 // TODO: If displayed, remove the OSD banner related to the previous 143 // active source device. 144 } 145 } 146 147 /** 148 * Returns the previous port id kept to handle input switching on <Inactive Source>. 149 */ 150 int getPrevPortId() { 151 synchronized (mLock) { 152 return mPrevPortId; 153 } 154 } 155 156 /** 157 * Sets the previous port id. INVALID_PORT_ID invalidates it, hence no actions will be 158 * taken for <Inactive Source>. 159 */ 160 void setPrevPortId(int portId) { 161 synchronized (mLock) { 162 mPrevPortId = portId; 163 } 164 } 165 166 @ServiceThreadOnly 167 void updateActivePortId(int portId) { 168 assertRunOnServiceThread(); 169 // Seq #15 170 if (portId == getActivePortId()) { 171 return; 172 } 173 setPrevPortId(portId); 174 // TODO: Actually switch the physical port here. Handle PAP/PIP as well. 175 // Show OSD port change banner 176 } 177 178 @ServiceThreadOnly 179 void doManualPortSwitching(int portId, IHdmiControlCallback callback) { 180 assertRunOnServiceThread(); 181 // Seq #20 182 if (!isHdmiControlEnabled() || portId == getActivePortId()) { 183 invokeCallback(callback, HdmiCec.RESULT_INCORRECT_MODE); 184 return; 185 } 186 // TODO: Make sure this call does not stem from <Active Source> message reception. 187 188 setActivePortId(portId); 189 // TODO: Return immediately if the operation is triggered by <Text/Image View On> 190 // and this is the first notification about the active input after power-on. 191 // TODO: Handle invalid port id / active input which should be treated as an 192 // internal tuner. 193 194 removeAction(RoutingControlAction.class); 195 196 int oldPath = mService.portIdToPath(mService.portIdToPath(getActivePortId())); 197 int newPath = mService.portIdToPath(portId); 198 HdmiCecMessage routingChange = 199 HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath); 200 mService.sendCecCommand(routingChange); 201 addAndStartAction(new RoutingControlAction(this, newPath, callback)); 202 } 203 204 /** 205 * Sends key to a target CEC device. 206 * 207 * @param keyCode key code to send. Defined in {@link android.view.KeyEvent}. 208 * @param isPressed true if this is keypress event 209 */ 210 @ServiceThreadOnly 211 void sendKeyEvent(int keyCode, boolean isPressed) { 212 assertRunOnServiceThread(); 213 List<SendKeyAction> action = getActions(SendKeyAction.class); 214 if (!action.isEmpty()) { 215 action.get(0).processKeyEvent(keyCode, isPressed); 216 } else { 217 if (isPressed) { 218 addAndStartAction(new SendKeyAction(this, getActiveSource(), keyCode)); 219 } else { 220 Slog.w(TAG, "Discard key release event"); 221 } 222 } 223 } 224 225 private static void invokeCallback(IHdmiControlCallback callback, int result) { 226 if (callback == null) { 227 return; 228 } 229 try { 230 callback.onComplete(result); 231 } catch (RemoteException e) { 232 Slog.e(TAG, "Invoking callback failed:" + e); 233 } 234 } 235 236 @Override 237 @ServiceThreadOnly 238 protected boolean handleActiveSource(HdmiCecMessage message) { 239 assertRunOnServiceThread(); 240 int activePath = HdmiUtils.twoBytesToInt(message.getParams()); 241 ActiveSourceHandler.create(this, null).process(message.getSource(), activePath); 242 return true; 243 } 244 245 @Override 246 @ServiceThreadOnly 247 protected boolean handleInactiveSource(HdmiCecMessage message) { 248 assertRunOnServiceThread(); 249 // Seq #10 250 251 // Ignore <Inactive Source> from non-active source device. 252 if (getActiveSource() != message.getSource()) { 253 return true; 254 } 255 if (isInPresetInstallationMode()) { 256 return true; 257 } 258 int portId = getPrevPortId(); 259 if (portId != HdmiConstants.INVALID_PORT_ID) { 260 // TODO: Do this only if TV is not showing multiview like PIP/PAP. 261 262 HdmiCecDeviceInfo inactiveSource = getDeviceInfo(message.getSource()); 263 if (inactiveSource == null) { 264 return true; 265 } 266 if (mService.pathToPortId(inactiveSource.getPhysicalAddress()) == portId) { 267 return true; 268 } 269 // TODO: Switch the TV freeze mode off 270 271 setActivePortId(portId); 272 doManualPortSwitching(portId, null); 273 setPrevPortId(HdmiConstants.INVALID_PORT_ID); 274 } 275 return true; 276 } 277 278 @Override 279 @ServiceThreadOnly 280 protected boolean handleRequestActiveSource(HdmiCecMessage message) { 281 assertRunOnServiceThread(); 282 // Seq #19 283 int address = getDeviceInfo().getLogicalAddress(); 284 if (address == getActiveSource()) { 285 mService.sendCecCommand( 286 HdmiCecMessageBuilder.buildActiveSource(address, getActivePath())); 287 } 288 return true; 289 } 290 291 @Override 292 @ServiceThreadOnly 293 protected boolean handleGetMenuLanguage(HdmiCecMessage message) { 294 assertRunOnServiceThread(); 295 HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand( 296 mAddress, Locale.getDefault().getISO3Language()); 297 // TODO: figure out how to handle failed to get language code. 298 if (command != null) { 299 mService.sendCecCommand(command); 300 } else { 301 Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString()); 302 } 303 return true; 304 } 305 306 @Override 307 @ServiceThreadOnly 308 protected boolean handleReportPhysicalAddress(HdmiCecMessage message) { 309 assertRunOnServiceThread(); 310 // Ignore if [Device Discovery Action] is going on. 311 if (hasAction(DeviceDiscoveryAction.class)) { 312 Slog.i(TAG, "Ignore unrecognizable <Report Physical Address> " 313 + "because Device Discovery Action is on-going:" + message); 314 return true; 315 } 316 317 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); 318 int logicalAddress = message.getSource(); 319 320 // If it is a new device and connected to the tail of active path, 321 // it's required to change routing path. 322 boolean requireRoutingChange = !isInDeviceList(physicalAddress, logicalAddress) 323 && isTailOfActivePath(physicalAddress); 324 addAndStartAction(new NewDeviceAction(this, message.getSource(), physicalAddress, 325 requireRoutingChange)); 326 return true; 327 } 328 329 @Override 330 @ServiceThreadOnly 331 protected boolean handleVendorSpecificCommand(HdmiCecMessage message) { 332 assertRunOnServiceThread(); 333 List<VendorSpecificAction> actions = Collections.emptyList(); 334 // TODO: Call mService.getActions(VendorSpecificAction.class) to get all the actions. 335 336 // We assume that there can be multiple vendor-specific command actions running 337 // at the same time. Pass the message to each action to see if one of them needs it. 338 for (VendorSpecificAction action : actions) { 339 if (action.processCommand(message)) { 340 return true; 341 } 342 } 343 // Handle the message here if it is not already consumed by one of the running actions. 344 // Respond with a appropriate vendor-specific command or <Feature Abort>, or create another 345 // vendor-specific action: 346 // 347 // mService.addAndStartAction(new VendorSpecificAction(mService, mAddress)); 348 // 349 // For now, simply reply with <Feature Abort> and mark it consumed by returning true. 350 mService.sendCecCommand(HdmiCecMessageBuilder.buildFeatureAbortCommand( 351 message.getDestination(), message.getSource(), message.getOpcode(), 352 HdmiConstants.ABORT_REFUSED)); 353 return true; 354 } 355 356 @ServiceThreadOnly 357 private void launchDeviceDiscovery() { 358 assertRunOnServiceThread(); 359 clearDeviceInfoList(); 360 DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, 361 new DeviceDiscoveryCallback() { 362 @Override 363 public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) { 364 for (HdmiCecDeviceInfo info : deviceInfos) { 365 addCecDevice(info); 366 } 367 368 // Since we removed all devices when it's start and 369 // device discovery action does not poll local devices, 370 // we should put device info of local device manually here 371 for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) { 372 addCecDevice(device.getDeviceInfo()); 373 } 374 375 addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this)); 376 377 // If there is AVR, initiate System Audio Auto initiation action, 378 // which turns on and off system audio according to last system 379 // audio setting. 380 HdmiCecDeviceInfo avrInfo = getAvrDeviceInfo(); 381 if (avrInfo != null) { 382 addAndStartAction(new SystemAudioAutoInitiationAction( 383 HdmiCecLocalDeviceTv.this, avrInfo.getLogicalAddress())); 384 } 385 } 386 }); 387 addAndStartAction(action); 388 } 389 390 // Clear all device info. 391 @ServiceThreadOnly 392 private void clearDeviceInfoList() { 393 assertRunOnServiceThread(); 394 mDeviceInfos.clear(); 395 updateSafeDeviceInfoList(); 396 } 397 398 @ServiceThreadOnly 399 void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) { 400 assertRunOnServiceThread(); 401 HdmiCecDeviceInfo avr = getAvrDeviceInfo(); 402 if (avr == null) { 403 invokeCallback(callback, HdmiCec.RESULT_SOURCE_NOT_AVAILABLE); 404 return; 405 } 406 407 addAndStartAction( 408 new SystemAudioActionFromTv(this, avr.getLogicalAddress(), enabled, callback)); 409 } 410 411 void setSystemAudioMode(boolean on) { 412 synchronized (mLock) { 413 if (on != mSystemAudioMode) { 414 mSystemAudioMode = on; 415 // TODO: Need to set the preference for SystemAudioMode. 416 mService.announceSystemAudioModeChange(on); 417 } 418 } 419 } 420 421 boolean getSystemAudioMode() { 422 synchronized (mLock) { 423 return mSystemAudioMode; 424 } 425 } 426 427 /** 428 * Change ARC status into the given {@code enabled} status. 429 * 430 * @return {@code true} if ARC was in "Enabled" status 431 */ 432 boolean setArcStatus(boolean enabled) { 433 synchronized (mLock) { 434 boolean oldStatus = mArcStatusEnabled; 435 // 1. Enable/disable ARC circuit. 436 mService.setAudioReturnChannel(enabled); 437 // 2. Notify arc status to audio service. 438 notifyArcStatusToAudioService(enabled); 439 // 3. Update arc status; 440 mArcStatusEnabled = enabled; 441 return oldStatus; 442 } 443 } 444 445 private void notifyArcStatusToAudioService(boolean enabled) { 446 // Note that we don't set any name to ARC. 447 mService.getAudioManager().setWiredDeviceConnectionState( 448 AudioSystem.DEVICE_OUT_HDMI_ARC, 449 enabled ? 1 : 0, ""); 450 } 451 452 /** 453 * Returns whether ARC is enabled or not. 454 */ 455 boolean getArcStatus() { 456 synchronized (mLock) { 457 return mArcStatusEnabled; 458 } 459 } 460 461 @ServiceThreadOnly 462 void setAudioStatus(boolean mute, int volume) { 463 mService.setAudioStatus(mute, volume); 464 } 465 466 @Override 467 @ServiceThreadOnly 468 protected boolean handleInitiateArc(HdmiCecMessage message) { 469 assertRunOnServiceThread(); 470 // In case where <Initiate Arc> is started by <Request ARC Initiation> 471 // need to clean up RequestArcInitiationAction. 472 removeAction(RequestArcInitiationAction.class); 473 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 474 message.getSource(), true); 475 addAndStartAction(action); 476 return true; 477 } 478 479 @Override 480 @ServiceThreadOnly 481 protected boolean handleTerminateArc(HdmiCecMessage message) { 482 assertRunOnServiceThread(); 483 // In case where <Terminate Arc> is started by <Request ARC Termination> 484 // need to clean up RequestArcInitiationAction. 485 // TODO: check conditions of power status by calling is_connected api 486 // to be added soon. 487 removeAction(RequestArcTerminationAction.class); 488 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 489 message.getSource(), false); 490 addAndStartAction(action); 491 return true; 492 } 493 494 @Override 495 @ServiceThreadOnly 496 protected boolean handleSetSystemAudioMode(HdmiCecMessage message) { 497 assertRunOnServiceThread(); 498 if (!isMessageForSystemAudio(message)) { 499 return false; 500 } 501 SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this, 502 message.getSource(), HdmiUtils.parseCommandParamSystemAudioStatus(message), null); 503 addAndStartAction(action); 504 return true; 505 } 506 507 @Override 508 @ServiceThreadOnly 509 protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) { 510 assertRunOnServiceThread(); 511 if (!isMessageForSystemAudio(message)) { 512 return false; 513 } 514 setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message)); 515 return true; 516 } 517 518 private boolean isMessageForSystemAudio(HdmiCecMessage message) { 519 if (message.getSource() != HdmiCec.ADDR_AUDIO_SYSTEM 520 || message.getDestination() != HdmiCec.ADDR_TV 521 || getAvrDeviceInfo() == null) { 522 Slog.w(TAG, "Skip abnormal CecMessage: " + message); 523 return false; 524 } 525 return true; 526 } 527 528 /** 529 * Add a new {@link HdmiCecDeviceInfo}. It returns old device info which has the same 530 * logical address as new device info's. 531 * 532 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 533 * 534 * @param deviceInfo a new {@link HdmiCecDeviceInfo} to be added. 535 * @return {@code null} if it is new device. Otherwise, returns old {@HdmiCecDeviceInfo} 536 * that has the same logical address as new one has. 537 */ 538 @ServiceThreadOnly 539 HdmiCecDeviceInfo addDeviceInfo(HdmiCecDeviceInfo deviceInfo) { 540 assertRunOnServiceThread(); 541 HdmiCecDeviceInfo oldDeviceInfo = getDeviceInfo(deviceInfo.getLogicalAddress()); 542 if (oldDeviceInfo != null) { 543 removeDeviceInfo(deviceInfo.getLogicalAddress()); 544 } 545 mDeviceInfos.append(deviceInfo.getLogicalAddress(), deviceInfo); 546 updateSafeDeviceInfoList(); 547 return oldDeviceInfo; 548 } 549 550 /** 551 * Remove a device info corresponding to the given {@code logicalAddress}. 552 * It returns removed {@link HdmiCecDeviceInfo} if exists. 553 * 554 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 555 * 556 * @param logicalAddress logical address of device to be removed 557 * @return removed {@link HdmiCecDeviceInfo} it exists. Otherwise, returns {@code null} 558 */ 559 @ServiceThreadOnly 560 HdmiCecDeviceInfo removeDeviceInfo(int logicalAddress) { 561 assertRunOnServiceThread(); 562 HdmiCecDeviceInfo deviceInfo = mDeviceInfos.get(logicalAddress); 563 if (deviceInfo != null) { 564 mDeviceInfos.remove(logicalAddress); 565 } 566 updateSafeDeviceInfoList(); 567 return deviceInfo; 568 } 569 570 /** 571 * Return a list of all {@link HdmiCecDeviceInfo}. 572 * 573 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 574 * This is not thread-safe. For thread safety, call {@link #getSafeDeviceInfoList(boolean)}. 575 */ 576 @ServiceThreadOnly 577 List<HdmiCecDeviceInfo> getDeviceInfoList(boolean includelLocalDevice) { 578 assertRunOnServiceThread(); 579 if (includelLocalDevice) { 580 return HdmiUtils.sparseArrayToList(mDeviceInfos); 581 } else { 582 ArrayList<HdmiCecDeviceInfo> infoList = new ArrayList<>(); 583 for (int i = 0; i < mDeviceInfos.size(); ++i) { 584 HdmiCecDeviceInfo info = mDeviceInfos.valueAt(i); 585 if (!isLocalDeviceAddress(info.getLogicalAddress())) { 586 infoList.add(info); 587 } 588 } 589 return infoList; 590 } 591 } 592 593 /** 594 * Return a list of {@link HdmiCecDeviceInfo}. 595 * 596 * @param includeLocalDevice whether to include local device in result. 597 */ 598 List<HdmiCecDeviceInfo> getSafeDeviceInfoList(boolean includeLocalDevice) { 599 synchronized (mLock) { 600 if (includeLocalDevice) { 601 return mSafeAllDeviceInfos; 602 } else { 603 return mSafeExternalDeviceInfos; 604 } 605 } 606 } 607 608 @ServiceThreadOnly 609 private void updateSafeDeviceInfoList() { 610 assertRunOnServiceThread(); 611 List<HdmiCecDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos); 612 List<HdmiCecDeviceInfo> externalDeviceInfos = getDeviceInfoList(false); 613 synchronized (mLock) { 614 mSafeAllDeviceInfos = copiedDevices; 615 mSafeExternalDeviceInfos = externalDeviceInfos; 616 } 617 } 618 619 @ServiceThreadOnly 620 private boolean isLocalDeviceAddress(int address) { 621 assertRunOnServiceThread(); 622 for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) { 623 if (device.isAddressOf(address)) { 624 return true; 625 } 626 } 627 return false; 628 } 629 630 @ServiceThreadOnly 631 HdmiCecDeviceInfo getAvrDeviceInfo() { 632 assertRunOnServiceThread(); 633 return getDeviceInfo(HdmiCec.ADDR_AUDIO_SYSTEM); 634 } 635 636 /** 637 * Return a {@link HdmiCecDeviceInfo} corresponding to the given {@code logicalAddress}. 638 * 639 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 640 * This is not thread-safe. For thread safety, call {@link #getSafeDeviceInfo(int)}. 641 * 642 * @param logicalAddress logical address to be retrieved 643 * @return {@link HdmiCecDeviceInfo} matched with the given {@code logicalAddress}. 644 * Returns null if no logical address matched 645 */ 646 @ServiceThreadOnly 647 HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) { 648 assertRunOnServiceThread(); 649 return mDeviceInfos.get(logicalAddress); 650 } 651 652 boolean hasSystemAudioDevice() { 653 return getSafeAvrDeviceInfo() != null; 654 } 655 656 HdmiCecDeviceInfo getSafeAvrDeviceInfo() { 657 return getSafeDeviceInfo(HdmiCec.ADDR_AUDIO_SYSTEM); 658 } 659 660 /** 661 * Thread safe version of {@link #getDeviceInfo(int)}. 662 * 663 * @param logicalAddress logical address to be retrieved 664 * @return {@link HdmiCecDeviceInfo} matched with the given {@code logicalAddress}. 665 * Returns null if no logical address matched 666 */ 667 HdmiCecDeviceInfo getSafeDeviceInfo(int logicalAddress) { 668 synchronized (mLock) { 669 return mSafeAllDeviceInfos.get(logicalAddress); 670 } 671 } 672 673 /** 674 * Called when a device is newly added or a new device is detected. 675 * 676 * @param info device info of a new device. 677 */ 678 @ServiceThreadOnly 679 final void addCecDevice(HdmiCecDeviceInfo info) { 680 assertRunOnServiceThread(); 681 addDeviceInfo(info); 682 if (info.getLogicalAddress() == mAddress) { 683 // The addition of TV device itself should not be notified. 684 return; 685 } 686 mService.invokeDeviceEventListeners(info, true); 687 } 688 689 /** 690 * Called when a device is removed or removal of device is detected. 691 * 692 * @param address a logical address of a device to be removed 693 */ 694 @ServiceThreadOnly 695 final void removeCecDevice(int address) { 696 assertRunOnServiceThread(); 697 HdmiCecDeviceInfo info = removeDeviceInfo(address); 698 mCecMessageCache.flushMessagesFrom(address); 699 mService.invokeDeviceEventListeners(info, false); 700 } 701 702 /** 703 * Returns the {@link HdmiCecDeviceInfo} instance whose physical address matches 704 * the given routing path. CEC devices use routing path for its physical address to 705 * describe the hierarchy of the devices in the network. 706 * 707 * @param path routing path or physical address 708 * @return {@link HdmiCecDeviceInfo} if the matched info is found; otherwise null 709 */ 710 @ServiceThreadOnly 711 final HdmiCecDeviceInfo getDeviceInfoByPath(int path) { 712 assertRunOnServiceThread(); 713 for (HdmiCecDeviceInfo info : getDeviceInfoList(false)) { 714 if (info.getPhysicalAddress() == path) { 715 return info; 716 } 717 } 718 return null; 719 } 720 721 /** 722 * Whether a device of the specified physical address and logical address exists 723 * in a device info list. However, both are minimal condition and it could 724 * be different device from the original one. 725 * 726 * @param physicalAddress physical address of a device to be searched 727 * @param logicalAddress logical address of a device to be searched 728 * @return true if exist; otherwise false 729 */ 730 @ServiceThreadOnly 731 boolean isInDeviceList(int physicalAddress, int logicalAddress) { 732 assertRunOnServiceThread(); 733 HdmiCecDeviceInfo device = getDeviceInfo(logicalAddress); 734 if (device == null) { 735 return false; 736 } 737 return device.getPhysicalAddress() == physicalAddress; 738 } 739 740 @Override 741 @ServiceThreadOnly 742 void onHotplug(int portNo, boolean connected) { 743 assertRunOnServiceThread(); 744 745 // Tv device will have permanent HotplugDetectionAction. 746 List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class); 747 if (!hotplugActions.isEmpty()) { 748 // Note that hotplug action is single action running on a machine. 749 // "pollAllDevicesNow" cleans up timer and start poll action immediately. 750 hotplugActions.get(0).pollAllDevicesNow(); 751 } 752 } 753} 754