HdmiCecLocalDeviceTv.java revision 2ee0d6f44d7274bb1846cc6ff7a60451539a2b51
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 static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CEC_DISABLE; 20import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION; 21import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE; 22import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CEC_DISABLED; 23import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION; 24import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN; 25import static android.hardware.hdmi.HdmiControlManager.OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT; 26import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED; 27import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION; 28import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE; 29import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_ANALOGUE; 30import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL; 31import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL; 32 33import android.annotation.Nullable; 34import android.content.Context; 35import android.hardware.hdmi.HdmiControlManager; 36import android.hardware.hdmi.HdmiDeviceInfo; 37import android.hardware.hdmi.HdmiPortInfo; 38import android.hardware.hdmi.HdmiRecordSources; 39import android.hardware.hdmi.HdmiTimerRecordSources; 40import android.hardware.hdmi.IHdmiControlCallback; 41import android.media.AudioManager; 42import android.media.AudioSystem; 43import android.media.tv.TvInputInfo; 44import android.media.tv.TvInputManager; 45import android.media.tv.TvInputManager.TvInputCallback; 46import android.os.RemoteException; 47import android.os.SystemProperties; 48import android.provider.Settings.Global; 49import android.util.ArraySet; 50import android.util.Slog; 51import android.util.SparseArray; 52 53import com.android.internal.annotations.GuardedBy; 54import com.android.internal.util.IndentingPrintWriter; 55import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback; 56import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; 57import com.android.server.hdmi.HdmiControlService.SendMessageCallback; 58import com.android.server.SystemService; 59 60import java.io.UnsupportedEncodingException; 61import java.util.ArrayList; 62import java.util.Arrays; 63import java.util.Collection; 64import java.util.Collections; 65import java.util.Iterator; 66import java.util.List; 67import java.util.HashMap; 68 69/** 70 * Represent a logical device of type TV residing in Android system. 71 */ 72final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { 73 private static final String TAG = "HdmiCecLocalDeviceTv"; 74 75 // Whether ARC is available or not. "true" means that ARC is established between TV and 76 // AVR as audio receiver. 77 @ServiceThreadOnly 78 private boolean mArcEstablished = false; 79 80 // Whether ARC feature is enabled or not. The default value is true. 81 // TODO: once adding system setting for it, read the value to it. 82 private boolean mArcFeatureEnabled = true; 83 84 // Whether System audio mode is activated or not. 85 // This becomes true only when all system audio sequences are finished. 86 @GuardedBy("mLock") 87 private boolean mSystemAudioActivated = false; 88 89 // The previous port id (input) before switching to the new one. This is remembered in order to 90 // be able to switch to it upon receiving <Inactive Source> from currently active source. 91 // This remains valid only when the active source was switched via one touch play operation 92 // (either by TV or source device). Manual port switching invalidates this value to 93 // Constants.PORT_INVALID, for which case <Inactive Source> does not do anything. 94 @GuardedBy("mLock") 95 private int mPrevPortId; 96 97 @GuardedBy("mLock") 98 private int mSystemAudioVolume = Constants.UNKNOWN_VOLUME; 99 100 @GuardedBy("mLock") 101 private boolean mSystemAudioMute = false; 102 103 // Copy of mDeviceInfos to guarantee thread-safety. 104 @GuardedBy("mLock") 105 private List<HdmiDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList(); 106 // All external cec input(source) devices. Does not include system audio device. 107 @GuardedBy("mLock") 108 private List<HdmiDeviceInfo> mSafeExternalInputs = Collections.emptyList(); 109 110 // Map-like container of all cec devices including local ones. 111 // device id is used as key of container. 112 // This is not thread-safe. For external purpose use mSafeDeviceInfos. 113 private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>(); 114 115 // If true, TV going to standby mode puts other devices also to standby. 116 private boolean mAutoDeviceOff; 117 118 // If true, TV wakes itself up when receiving <Text/Image View On>. 119 private boolean mAutoWakeup; 120 121 // List of the logical address of local CEC devices. Unmodifiable, thread-safe. 122 private List<Integer> mLocalDeviceAddresses; 123 124 private final HdmiCecStandbyModeHandler mStandbyHandler; 125 126 // If true, do not do routing control/send active source for internal source. 127 // Set to true when the device was woken up by <Text/Image View On>. 128 private boolean mSkipRoutingControl; 129 130 // Set of physical addresses of CEC switches on the CEC bus. Managed independently from 131 // other CEC devices since they might not have logical address. 132 private final ArraySet<Integer> mCecSwitches = new ArraySet<Integer>(); 133 134 // Message buffer used to buffer selected messages to process later. <Active Source> 135 // from a source device, for instance, needs to be buffered if the device is not 136 // discovered yet. The buffered commands are taken out and when they are ready to 137 // handle. 138 private final DelayedMessageBuffer mDelayedMessageBuffer = new DelayedMessageBuffer(this); 139 140 // Defines the callback invoked when TV input framework is updated with input status. 141 // We are interested in the notification for HDMI input addition event, in order to 142 // process any CEC commands that arrived before the input is added. 143 private final TvInputCallback mTvInputCallback = new TvInputCallback() { 144 @Override 145 public void onInputAdded(String inputId) { 146 TvInputInfo tvInfo = mService.getTvInputManager().getTvInputInfo(inputId); 147 HdmiDeviceInfo info = tvInfo.getHdmiDeviceInfo(); 148 if (info == null) return; 149 addTvInput(inputId, info.getId()); 150 if (info.isCecDevice()) { 151 processDelayedActiveSource(info.getLogicalAddress()); 152 } 153 } 154 155 @Override 156 public void onInputRemoved(String inputId) { 157 removeTvInput(inputId); 158 } 159 }; 160 161 // Keeps the mapping (TV input ID, HDMI device ID) to keep track of the TV inputs ready to 162 // accept input switching request from HDMI devices. Requests for which the corresponding 163 // input ID is not yet registered by TV input framework need to be buffered for delayed 164 // processing. 165 private final HashMap<String, Integer> mTvInputs = new HashMap<>(); 166 167 @ServiceThreadOnly 168 private void addTvInput(String inputId, int deviceId) { 169 assertRunOnServiceThread(); 170 mTvInputs.put(inputId, deviceId); 171 } 172 173 @ServiceThreadOnly 174 private void removeTvInput(String inputId) { 175 assertRunOnServiceThread(); 176 mTvInputs.remove(inputId); 177 } 178 179 @Override 180 @ServiceThreadOnly 181 protected boolean isInputReady(int deviceId) { 182 assertRunOnServiceThread(); 183 return mTvInputs.containsValue(deviceId); 184 } 185 186 HdmiCecLocalDeviceTv(HdmiControlService service) { 187 super(service, HdmiDeviceInfo.DEVICE_TV); 188 mPrevPortId = Constants.INVALID_PORT_ID; 189 mAutoDeviceOff = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, 190 true); 191 mAutoWakeup = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, true); 192 mStandbyHandler = new HdmiCecStandbyModeHandler(service, this); 193 } 194 195 @Override 196 @ServiceThreadOnly 197 protected void onAddressAllocated(int logicalAddress, int reason) { 198 assertRunOnServiceThread(); 199 mService.registerTvInputCallback(mTvInputCallback); 200 mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( 201 mAddress, mService.getPhysicalAddress(), mDeviceType)); 202 mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand( 203 mAddress, mService.getVendorId())); 204 mCecSwitches.add(mService.getPhysicalAddress()); // TV is a CEC switch too. 205 mTvInputs.clear(); 206 mSkipRoutingControl = (reason == HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE); 207 launchRoutingControl(reason != HdmiControlService.INITIATED_BY_ENABLE_CEC && 208 reason != HdmiControlService.INITIATED_BY_BOOT_UP); 209 mLocalDeviceAddresses = initLocalDeviceAddresses(); 210 launchDeviceDiscovery(); 211 startQueuedActions(); 212 } 213 214 215 @ServiceThreadOnly 216 private List<Integer> initLocalDeviceAddresses() { 217 assertRunOnServiceThread(); 218 List<Integer> addresses = new ArrayList<>(); 219 for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) { 220 addresses.add(device.getDeviceInfo().getLogicalAddress()); 221 } 222 return Collections.unmodifiableList(addresses); 223 } 224 225 @Override 226 protected int getPreferredAddress() { 227 return Constants.ADDR_TV; 228 } 229 230 @Override 231 protected void setPreferredAddress(int addr) { 232 Slog.w(TAG, "Preferred addres will not be stored for TV"); 233 } 234 235 @Override 236 @ServiceThreadOnly 237 boolean dispatchMessage(HdmiCecMessage message) { 238 assertRunOnServiceThread(); 239 if (mService.isPowerStandby() && mStandbyHandler.handleCommand(message)) { 240 return true; 241 } 242 return super.onMessage(message); 243 } 244 245 /** 246 * Performs the action 'device select', or 'one touch play' initiated by TV. 247 * 248 * @param id id of HDMI device to select 249 * @param callback callback object to report the result with 250 */ 251 @ServiceThreadOnly 252 void deviceSelect(int id, IHdmiControlCallback callback) { 253 assertRunOnServiceThread(); 254 HdmiDeviceInfo targetDevice = mDeviceInfos.get(id); 255 if (targetDevice == null) { 256 invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); 257 return; 258 } 259 int targetAddress = targetDevice.getLogicalAddress(); 260 ActiveSource active = getActiveSource(); 261 if (active.isValid() && targetAddress == active.logicalAddress) { 262 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS); 263 return; 264 } 265 if (targetAddress == Constants.ADDR_INTERNAL) { 266 handleSelectInternalSource(); 267 // Switching to internal source is always successful even when CEC control is disabled. 268 setActiveSource(targetAddress, mService.getPhysicalAddress()); 269 setActivePath(mService.getPhysicalAddress()); 270 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS); 271 return; 272 } 273 if (!mService.isControlEnabled()) { 274 setActiveSource(targetDevice); 275 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); 276 return; 277 } 278 removeAction(DeviceSelectAction.class); 279 addAndStartAction(new DeviceSelectAction(this, targetDevice, callback)); 280 } 281 282 @ServiceThreadOnly 283 private void handleSelectInternalSource() { 284 assertRunOnServiceThread(); 285 // Seq #18 286 if (mService.isControlEnabled() && mActiveSource.logicalAddress != mAddress) { 287 updateActiveSource(mAddress, mService.getPhysicalAddress()); 288 if (mSkipRoutingControl) { 289 mSkipRoutingControl = false; 290 return; 291 } 292 HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource( 293 mAddress, mService.getPhysicalAddress()); 294 mService.sendCecCommand(activeSource); 295 } 296 } 297 298 @ServiceThreadOnly 299 void updateActiveSource(int logicalAddress, int physicalAddress) { 300 assertRunOnServiceThread(); 301 updateActiveSource(ActiveSource.of(logicalAddress, physicalAddress)); 302 } 303 304 @ServiceThreadOnly 305 void updateActiveSource(ActiveSource newActive) { 306 assertRunOnServiceThread(); 307 // Seq #14 308 if (mActiveSource.equals(newActive)) { 309 return; 310 } 311 setActiveSource(newActive); 312 int logicalAddress = newActive.logicalAddress; 313 if (getCecDeviceInfo(logicalAddress) != null && logicalAddress != mAddress) { 314 if (mService.pathToPortId(newActive.physicalAddress) == getActivePortId()) { 315 setPrevPortId(getActivePortId()); 316 } 317 // TODO: Show the OSD banner related to the new active source device. 318 } else { 319 // TODO: If displayed, remove the OSD banner related to the previous 320 // active source device. 321 } 322 } 323 324 int getPortId(int physicalAddress) { 325 return mService.pathToPortId(physicalAddress); 326 } 327 328 /** 329 * Returns the previous port id kept to handle input switching on <Inactive Source>. 330 */ 331 int getPrevPortId() { 332 synchronized (mLock) { 333 return mPrevPortId; 334 } 335 } 336 337 /** 338 * Sets the previous port id. INVALID_PORT_ID invalidates it, hence no actions will be 339 * taken for <Inactive Source>. 340 */ 341 void setPrevPortId(int portId) { 342 synchronized (mLock) { 343 mPrevPortId = portId; 344 } 345 } 346 347 @ServiceThreadOnly 348 void updateActiveInput(int path, boolean notifyInputChange) { 349 assertRunOnServiceThread(); 350 // Seq #15 351 setPrevPortId(getActivePortId()); 352 setActivePath(path); 353 // TODO: Handle PAP/PIP case. 354 // Show OSD port change banner 355 if (notifyInputChange) { 356 ActiveSource activeSource = getActiveSource(); 357 HdmiDeviceInfo info = getCecDeviceInfo(activeSource.logicalAddress); 358 if (info == null) { 359 info = mService.getDeviceInfoByPort(getActivePortId()); 360 if (info == null) { 361 // No CEC/MHL device is present at the port. Attempt to switch to 362 // the hardware port itself for non-CEC devices that may be connected. 363 info = new HdmiDeviceInfo(path, getActivePortId()); 364 } 365 } 366 mService.invokeInputChangeListener(info); 367 } 368 } 369 370 @ServiceThreadOnly 371 void doManualPortSwitching(int portId, IHdmiControlCallback callback) { 372 assertRunOnServiceThread(); 373 // Seq #20 374 if (!mService.isValidPortId(portId)) { 375 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); 376 return; 377 } 378 if (portId == getActivePortId()) { 379 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS); 380 return; 381 } 382 mActiveSource.invalidate(); 383 if (!mService.isControlEnabled()) { 384 setActivePortId(portId); 385 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); 386 return; 387 } 388 int oldPath = getActivePortId() != Constants.INVALID_PORT_ID 389 ? mService.portIdToPath(getActivePortId()) : getDeviceInfo().getPhysicalAddress(); 390 setActivePath(oldPath); 391 if (mSkipRoutingControl) { 392 mSkipRoutingControl = false; 393 return; 394 } 395 int newPath = mService.portIdToPath(portId); 396 startRoutingControl(oldPath, newPath, true, callback); 397 } 398 399 @ServiceThreadOnly 400 void startRoutingControl(int oldPath, int newPath, boolean queryDevicePowerStatus, 401 IHdmiControlCallback callback) { 402 assertRunOnServiceThread(); 403 if (oldPath == newPath) { 404 return; 405 } 406 HdmiCecMessage routingChange = 407 HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath); 408 mService.sendCecCommand(routingChange); 409 removeAction(RoutingControlAction.class); 410 addAndStartAction( 411 new RoutingControlAction(this, newPath, queryDevicePowerStatus, callback)); 412 } 413 414 @ServiceThreadOnly 415 int getPowerStatus() { 416 assertRunOnServiceThread(); 417 return mService.getPowerStatus(); 418 } 419 420 /** 421 * Sends key to a target CEC device. 422 * 423 * @param keyCode key code to send. Defined in {@link android.view.KeyEvent}. 424 * @param isPressed true if this is key press event 425 */ 426 @Override 427 @ServiceThreadOnly 428 protected void sendKeyEvent(int keyCode, boolean isPressed) { 429 assertRunOnServiceThread(); 430 if (!HdmiCecKeycode.isSupportedKeycode(keyCode)) { 431 Slog.w(TAG, "Unsupported key: " + keyCode); 432 return; 433 } 434 List<SendKeyAction> action = getActions(SendKeyAction.class); 435 if (!action.isEmpty()) { 436 action.get(0).processKeyEvent(keyCode, isPressed); 437 } else { 438 if (isPressed) { 439 int logicalAddress = findKeyReceiverAddress(); 440 if (logicalAddress != Constants.ADDR_INVALID) { 441 addAndStartAction(new SendKeyAction(this, logicalAddress, keyCode)); 442 return; 443 } 444 } 445 Slog.w(TAG, "Discard key event: " + keyCode + " pressed:" + isPressed); 446 } 447 } 448 449 private int findKeyReceiverAddress() { 450 if (getActiveSource().isValid()) { 451 return getActiveSource().logicalAddress; 452 } 453 HdmiDeviceInfo info = getDeviceInfoByPath(getActivePath()); 454 if (info != null) { 455 return info.getLogicalAddress(); 456 } 457 return Constants.ADDR_INVALID; 458 } 459 460 private static void invokeCallback(IHdmiControlCallback callback, int result) { 461 if (callback == null) { 462 return; 463 } 464 try { 465 callback.onComplete(result); 466 } catch (RemoteException e) { 467 Slog.e(TAG, "Invoking callback failed:" + e); 468 } 469 } 470 471 @Override 472 @ServiceThreadOnly 473 protected boolean handleActiveSource(HdmiCecMessage message) { 474 assertRunOnServiceThread(); 475 int logicalAddress = message.getSource(); 476 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); 477 HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress); 478 if (info == null) { 479 if (!handleNewDeviceAtTheTailOfActivePath(physicalAddress)) { 480 HdmiLogger.debug("Device info %X not found; buffering the command", logicalAddress); 481 mDelayedMessageBuffer.add(message); 482 } 483 } else if (!isInputReady(info.getId())) { 484 HdmiLogger.debug("Input not ready for device: %X; buffering the command", info.getId()); 485 mDelayedMessageBuffer.add(message); 486 } else { 487 ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress); 488 ActiveSourceHandler.create(this, null).process(activeSource, info.getDeviceType()); 489 } 490 return true; 491 } 492 493 @Override 494 @ServiceThreadOnly 495 protected boolean handleInactiveSource(HdmiCecMessage message) { 496 assertRunOnServiceThread(); 497 // Seq #10 498 499 // Ignore <Inactive Source> from non-active source device. 500 if (getActiveSource().logicalAddress != message.getSource()) { 501 return true; 502 } 503 if (isProhibitMode()) { 504 return true; 505 } 506 int portId = getPrevPortId(); 507 if (portId != Constants.INVALID_PORT_ID) { 508 // TODO: Do this only if TV is not showing multiview like PIP/PAP. 509 510 HdmiDeviceInfo inactiveSource = getCecDeviceInfo(message.getSource()); 511 if (inactiveSource == null) { 512 return true; 513 } 514 if (mService.pathToPortId(inactiveSource.getPhysicalAddress()) == portId) { 515 return true; 516 } 517 // TODO: Switch the TV freeze mode off 518 519 doManualPortSwitching(portId, null); 520 setPrevPortId(Constants.INVALID_PORT_ID); 521 } else { 522 // No HDMI port to switch to was found. Notify the input change listers to 523 // switch to the lastly shown internal input. 524 mActiveSource.invalidate(); 525 setActivePath(Constants.INVALID_PHYSICAL_ADDRESS); 526 mService.invokeInputChangeListener(HdmiDeviceInfo.INACTIVE_DEVICE); 527 } 528 return true; 529 } 530 531 @Override 532 @ServiceThreadOnly 533 protected boolean handleRequestActiveSource(HdmiCecMessage message) { 534 assertRunOnServiceThread(); 535 // Seq #19 536 if (mAddress == getActiveSource().logicalAddress) { 537 mService.sendCecCommand( 538 HdmiCecMessageBuilder.buildActiveSource(mAddress, getActivePath())); 539 } 540 return true; 541 } 542 543 @Override 544 @ServiceThreadOnly 545 protected boolean handleGetMenuLanguage(HdmiCecMessage message) { 546 assertRunOnServiceThread(); 547 if (!broadcastMenuLanguage(mService.getLanguage())) { 548 Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString()); 549 } 550 return true; 551 } 552 553 @ServiceThreadOnly 554 boolean broadcastMenuLanguage(String language) { 555 assertRunOnServiceThread(); 556 HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand( 557 mAddress, language); 558 if (command != null) { 559 mService.sendCecCommand(command); 560 return true; 561 } 562 return false; 563 } 564 565 @Override 566 @ServiceThreadOnly 567 protected boolean handleReportPhysicalAddress(HdmiCecMessage message) { 568 assertRunOnServiceThread(); 569 int path = HdmiUtils.twoBytesToInt(message.getParams()); 570 int address = message.getSource(); 571 int type = message.getParams()[2]; 572 573 if (updateCecSwitchInfo(address, type, path)) return true; 574 575 // Ignore if [Device Discovery Action] is going on. 576 if (hasAction(DeviceDiscoveryAction.class)) { 577 Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message); 578 return true; 579 } 580 581 if (!isInDeviceList(address, path)) { 582 handleNewDeviceAtTheTailOfActivePath(path); 583 } 584 startNewDeviceAction(ActiveSource.of(address, path), type); 585 return true; 586 } 587 588 @Override 589 protected boolean handleReportPowerStatus(HdmiCecMessage command) { 590 int newStatus = command.getParams()[0] & 0xFF; 591 updateDevicePowerStatus(command.getSource(), newStatus); 592 return true; 593 } 594 595 @Override 596 protected boolean handleTimerStatus(HdmiCecMessage message) { 597 // Do nothing. 598 return true; 599 } 600 601 @Override 602 protected boolean handleRecordStatus(HdmiCecMessage message) { 603 // Do nothing. 604 return true; 605 } 606 607 boolean updateCecSwitchInfo(int address, int type, int path) { 608 if (address == Constants.ADDR_UNREGISTERED 609 && type == HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH) { 610 mCecSwitches.add(path); 611 updateSafeDeviceInfoList(); 612 return true; // Pure switch does not need further processing. Return here. 613 } 614 if (type == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) { 615 mCecSwitches.add(path); 616 } 617 return false; 618 } 619 620 void startNewDeviceAction(ActiveSource activeSource, int deviceType) { 621 for (NewDeviceAction action : getActions(NewDeviceAction.class)) { 622 // If there is new device action which has the same logical address and path 623 // ignore new request. 624 // NewDeviceAction is created whenever it receives <Report Physical Address>. 625 // And there is a chance starting NewDeviceAction for the same source. 626 // Usually, new device sends <Report Physical Address> when it's plugged 627 // in. However, TV can detect a new device from HotPlugDetectionAction, 628 // which sends <Give Physical Address> to the source for newly detected 629 // device. 630 if (action.isActionOf(activeSource)) { 631 return; 632 } 633 } 634 635 addAndStartAction(new NewDeviceAction(this, activeSource.logicalAddress, 636 activeSource.physicalAddress, deviceType)); 637 } 638 639 private boolean handleNewDeviceAtTheTailOfActivePath(int path) { 640 // Seq #22 641 if (isTailOfActivePath(path, getActivePath())) { 642 int newPath = mService.portIdToPath(getActivePortId()); 643 setActivePath(newPath); 644 startRoutingControl(getActivePath(), newPath, false, null); 645 return true; 646 } 647 return false; 648 } 649 650 /** 651 * Whether the given path is located in the tail of current active path. 652 * 653 * @param path to be tested 654 * @param activePath current active path 655 * @return true if the given path is located in the tail of current active path; otherwise, 656 * false 657 */ 658 static boolean isTailOfActivePath(int path, int activePath) { 659 // If active routing path is internal source, return false. 660 if (activePath == 0) { 661 return false; 662 } 663 for (int i = 12; i >= 0; i -= 4) { 664 int curActivePath = (activePath >> i) & 0xF; 665 if (curActivePath == 0) { 666 return true; 667 } else { 668 int curPath = (path >> i) & 0xF; 669 if (curPath != curActivePath) { 670 return false; 671 } 672 } 673 } 674 return false; 675 } 676 677 @Override 678 @ServiceThreadOnly 679 protected boolean handleRoutingChange(HdmiCecMessage message) { 680 assertRunOnServiceThread(); 681 // Seq #21 682 byte[] params = message.getParams(); 683 int currentPath = HdmiUtils.twoBytesToInt(params); 684 if (HdmiUtils.isAffectingActiveRoutingPath(getActivePath(), currentPath)) { 685 mActiveSource.invalidate(); 686 removeAction(RoutingControlAction.class); 687 int newPath = HdmiUtils.twoBytesToInt(params, 2); 688 addAndStartAction(new RoutingControlAction(this, newPath, true, null)); 689 } 690 return true; 691 } 692 693 @Override 694 @ServiceThreadOnly 695 protected boolean handleReportAudioStatus(HdmiCecMessage message) { 696 assertRunOnServiceThread(); 697 698 byte params[] = message.getParams(); 699 int mute = params[0] & 0x80; 700 int volume = params[0] & 0x7F; 701 setAudioStatus(mute == 0x80, volume); 702 return true; 703 } 704 705 @Override 706 @ServiceThreadOnly 707 protected boolean handleTextViewOn(HdmiCecMessage message) { 708 assertRunOnServiceThread(); 709 if (mService.isPowerStandbyOrTransient() && mAutoWakeup) { 710 mService.wakeUp(); 711 } 712 return true; 713 } 714 715 @Override 716 @ServiceThreadOnly 717 protected boolean handleImageViewOn(HdmiCecMessage message) { 718 assertRunOnServiceThread(); 719 // Currently, it's the same as <Text View On>. 720 return handleTextViewOn(message); 721 } 722 723 @Override 724 @ServiceThreadOnly 725 protected boolean handleSetOsdName(HdmiCecMessage message) { 726 int source = message.getSource(); 727 HdmiDeviceInfo deviceInfo = getCecDeviceInfo(source); 728 // If the device is not in device list, ignore it. 729 if (deviceInfo == null) { 730 Slog.e(TAG, "No source device info for <Set Osd Name>." + message); 731 return true; 732 } 733 String osdName = null; 734 try { 735 osdName = new String(message.getParams(), "US-ASCII"); 736 } catch (UnsupportedEncodingException e) { 737 Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e); 738 return true; 739 } 740 741 if (deviceInfo.getDisplayName().equals(osdName)) { 742 Slog.i(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message); 743 return true; 744 } 745 746 addCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(), 747 deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(), 748 deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName)); 749 return true; 750 } 751 752 @ServiceThreadOnly 753 private void launchDeviceDiscovery() { 754 assertRunOnServiceThread(); 755 clearDeviceInfoList(); 756 DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, 757 new DeviceDiscoveryCallback() { 758 @Override 759 public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) { 760 for (HdmiDeviceInfo info : deviceInfos) { 761 addCecDevice(info); 762 } 763 764 // Since we removed all devices when it's start and 765 // device discovery action does not poll local devices, 766 // we should put device info of local device manually here 767 for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) { 768 addCecDevice(device.getDeviceInfo()); 769 } 770 771 addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this)); 772 addAndStartAction(new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this)); 773 774 // If there is AVR, initiate System Audio Auto initiation action, 775 // which turns on and off system audio according to last system 776 // audio setting. 777 HdmiDeviceInfo avr = getAvrDeviceInfo(); 778 if (avr != null) { 779 onNewAvrAdded(avr); 780 } 781 } 782 }); 783 addAndStartAction(action); 784 } 785 786 @ServiceThreadOnly 787 void onNewAvrAdded(HdmiDeviceInfo avr) { 788 assertRunOnServiceThread(); 789 if (getSystemAudioModeSetting() && !isSystemAudioActivated()) { 790 addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress())); 791 } 792 if (isArcFeatureEnabled() && !hasAction(SetArcTransmissionStateAction.class)) { 793 startArcAction(true); 794 } 795 } 796 797 // Clear all device info. 798 @ServiceThreadOnly 799 private void clearDeviceInfoList() { 800 assertRunOnServiceThread(); 801 for (HdmiDeviceInfo info : mSafeExternalInputs) { 802 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); 803 } 804 mDeviceInfos.clear(); 805 updateSafeDeviceInfoList(); 806 } 807 808 @ServiceThreadOnly 809 // Seq #32 810 void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) { 811 assertRunOnServiceThread(); 812 if (!mService.isControlEnabled() || hasAction(DeviceDiscoveryAction.class)) { 813 setSystemAudioMode(false, true); 814 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); 815 return; 816 } 817 HdmiDeviceInfo avr = getAvrDeviceInfo(); 818 if (avr == null) { 819 setSystemAudioMode(false, true); 820 invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); 821 return; 822 } 823 824 addAndStartAction( 825 new SystemAudioActionFromTv(this, avr.getLogicalAddress(), enabled, callback)); 826 } 827 828 // # Seq 25 829 void setSystemAudioMode(boolean on, boolean updateSetting) { 830 HdmiLogger.debug("System Audio Mode change[old:%b new:%b]", mSystemAudioActivated, on); 831 832 if (updateSetting) { 833 mService.writeBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, on); 834 } 835 updateAudioManagerForSystemAudio(on); 836 synchronized (mLock) { 837 if (mSystemAudioActivated != on) { 838 mSystemAudioActivated = on; 839 mService.announceSystemAudioModeChange(on); 840 } 841 } 842 } 843 844 private void updateAudioManagerForSystemAudio(boolean on) { 845 int device = mService.getAudioManager().setHdmiSystemAudioSupported(on); 846 HdmiLogger.debug("[A]UpdateSystemAudio mode[on=%b] output=[%X]", on, device); 847 } 848 849 boolean isSystemAudioActivated() { 850 if (!hasSystemAudioDevice()) { 851 return false; 852 } 853 synchronized (mLock) { 854 return mSystemAudioActivated; 855 } 856 } 857 858 boolean getSystemAudioModeSetting() { 859 return mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, false); 860 } 861 862 /** 863 * Change ARC status into the given {@code enabled} status. 864 * 865 * @return {@code true} if ARC was in "Enabled" status 866 */ 867 @ServiceThreadOnly 868 boolean setArcStatus(boolean enabled) { 869 assertRunOnServiceThread(); 870 871 HdmiLogger.debug("Set Arc Status[old:%b new:%b]", mArcEstablished, enabled); 872 boolean oldStatus = mArcEstablished; 873 // 1. Enable/disable ARC circuit. 874 mService.setAudioReturnChannel(getAvrDeviceInfo().getPortId(), enabled); 875 // 2. Notify arc status to audio service. 876 notifyArcStatusToAudioService(enabled); 877 // 3. Update arc status; 878 mArcEstablished = enabled; 879 return oldStatus; 880 } 881 882 @ServiceThreadOnly 883 private void updateArcFeatureStatus(int portId, boolean isConnected) { 884 assertRunOnServiceThread(); 885 // HEAC 2.4, HEACT 5-15 886 // Should not activate ARC if +5V status is false. 887 HdmiPortInfo portInfo = mService.getPortInfo(portId); 888 if (portInfo.isArcSupported()) { 889 changeArcFeatureEnabled(isConnected); 890 } 891 } 892 893 private void notifyArcStatusToAudioService(boolean enabled) { 894 // Note that we don't set any name to ARC. 895 mService.getAudioManager().setWiredDeviceConnectionState( 896 AudioSystem.DEVICE_OUT_HDMI_ARC, 897 enabled ? 1 : 0, ""); 898 } 899 900 /** 901 * Returns whether ARC is enabled or not. 902 */ 903 @ServiceThreadOnly 904 boolean isArcEstabilished() { 905 assertRunOnServiceThread(); 906 return mArcFeatureEnabled && mArcEstablished; 907 } 908 909 @ServiceThreadOnly 910 void changeArcFeatureEnabled(boolean enabled) { 911 assertRunOnServiceThread(); 912 913 if (mArcFeatureEnabled != enabled) { 914 mArcFeatureEnabled = enabled; 915 if (enabled) { 916 if (!mArcEstablished) { 917 startArcAction(true); 918 } 919 } else { 920 if (mArcEstablished) { 921 startArcAction(false); 922 } 923 } 924 } 925 } 926 927 @ServiceThreadOnly 928 boolean isArcFeatureEnabled() { 929 assertRunOnServiceThread(); 930 return mArcFeatureEnabled; 931 } 932 933 @ServiceThreadOnly 934 void startArcAction(boolean enabled) { 935 assertRunOnServiceThread(); 936 HdmiDeviceInfo info = getAvrDeviceInfo(); 937 if (info == null) { 938 Slog.w(TAG, "Failed to start arc action; No AVR device."); 939 return; 940 } 941 if (!canStartArcUpdateAction(info.getLogicalAddress(), enabled)) { 942 Slog.w(TAG, "Failed to start arc action; ARC configuration check failed."); 943 if (enabled && !isConnectedToArcPort(info.getPhysicalAddress())) { 944 displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT); 945 } 946 return; 947 } 948 949 // Terminate opposite action and start action if not exist. 950 if (enabled) { 951 removeAction(RequestArcTerminationAction.class); 952 if (!hasAction(RequestArcInitiationAction.class)) { 953 addAndStartAction(new RequestArcInitiationAction(this, info.getLogicalAddress())); 954 } 955 } else { 956 removeAction(RequestArcInitiationAction.class); 957 if (!hasAction(RequestArcTerminationAction.class)) { 958 addAndStartAction(new RequestArcTerminationAction(this, info.getLogicalAddress())); 959 } 960 } 961 } 962 963 private boolean isDirectConnectAddress(int physicalAddress) { 964 return (physicalAddress & Constants.ROUTING_PATH_TOP_MASK) == physicalAddress; 965 } 966 967 void setAudioStatus(boolean mute, int volume) { 968 synchronized (mLock) { 969 mSystemAudioMute = mute; 970 mSystemAudioVolume = volume; 971 int maxVolume = mService.getAudioManager().getStreamMaxVolume( 972 AudioManager.STREAM_MUSIC); 973 mService.setAudioStatus(mute, 974 VolumeControlAction.scaleToCustomVolume(volume, maxVolume)); 975 displayOsd(HdmiControlManager.OSD_MESSAGE_AVR_VOLUME_CHANGED, 976 mute ? HdmiControlManager.AVR_VOLUME_MUTED : volume); 977 } 978 } 979 980 @ServiceThreadOnly 981 void changeVolume(int curVolume, int delta, int maxVolume) { 982 assertRunOnServiceThread(); 983 if (delta == 0 || !isSystemAudioActivated()) { 984 return; 985 } 986 987 int targetVolume = curVolume + delta; 988 int cecVolume = VolumeControlAction.scaleToCecVolume(targetVolume, maxVolume); 989 synchronized (mLock) { 990 // If new volume is the same as current system audio volume, just ignore it. 991 // Note that UNKNOWN_VOLUME is not in range of cec volume scale. 992 if (cecVolume == mSystemAudioVolume) { 993 // Update tv volume with system volume value. 994 mService.setAudioStatus(false, 995 VolumeControlAction.scaleToCustomVolume(mSystemAudioVolume, maxVolume)); 996 return; 997 } 998 } 999 1000 List<VolumeControlAction> actions = getActions(VolumeControlAction.class); 1001 if (actions.isEmpty()) { 1002 addAndStartAction(new VolumeControlAction(this, 1003 getAvrDeviceInfo().getLogicalAddress(), delta > 0)); 1004 } else { 1005 actions.get(0).handleVolumeChange(delta > 0); 1006 } 1007 } 1008 1009 @ServiceThreadOnly 1010 void changeMute(boolean mute) { 1011 assertRunOnServiceThread(); 1012 HdmiLogger.debug("[A]:Change mute:%b", mute); 1013 synchronized (mLock) { 1014 if (mSystemAudioMute == mute) { 1015 HdmiLogger.debug("No need to change mute."); 1016 return; 1017 } 1018 } 1019 if (!isSystemAudioActivated()) { 1020 HdmiLogger.debug("[A]:System audio is not activated."); 1021 return; 1022 } 1023 1024 // Remove existing volume action. 1025 removeAction(VolumeControlAction.class); 1026 sendUserControlPressedAndReleased(getAvrDeviceInfo().getLogicalAddress(), 1027 mute ? HdmiCecKeycode.CEC_KEYCODE_MUTE_FUNCTION : 1028 HdmiCecKeycode.CEC_KEYCODE_RESTORE_VOLUME_FUNCTION); 1029 } 1030 1031 @Override 1032 @ServiceThreadOnly 1033 protected boolean handleInitiateArc(HdmiCecMessage message) { 1034 assertRunOnServiceThread(); 1035 1036 if (!canStartArcUpdateAction(message.getSource(), true)) { 1037 if (getAvrDeviceInfo() == null) { 1038 // AVR may not have been discovered yet. Delay the message processing. 1039 mDelayedMessageBuffer.add(message); 1040 return true; 1041 } 1042 mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); 1043 if (!isConnectedToArcPort(message.getSource())) { 1044 displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT); 1045 } 1046 return true; 1047 } 1048 1049 // In case where <Initiate Arc> is started by <Request ARC Initiation> 1050 // need to clean up RequestArcInitiationAction. 1051 removeAction(RequestArcInitiationAction.class); 1052 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 1053 message.getSource(), true); 1054 addAndStartAction(action); 1055 return true; 1056 } 1057 1058 private boolean canStartArcUpdateAction(int avrAddress, boolean shouldCheckArcFeatureEnabled) { 1059 HdmiDeviceInfo avr = getAvrDeviceInfo(); 1060 if (avr != null 1061 && (avrAddress == avr.getLogicalAddress()) 1062 && isConnectedToArcPort(avr.getPhysicalAddress()) 1063 && isDirectConnectAddress(avr.getPhysicalAddress())) { 1064 if (shouldCheckArcFeatureEnabled) { 1065 return isArcFeatureEnabled(); 1066 } else { 1067 return true; 1068 } 1069 } else { 1070 return false; 1071 } 1072 } 1073 1074 @Override 1075 @ServiceThreadOnly 1076 protected boolean handleTerminateArc(HdmiCecMessage message) { 1077 assertRunOnServiceThread(); 1078 // In cast of termination, do not check ARC configuration in that AVR device 1079 // might be removed already. 1080 1081 // In case where <Terminate Arc> is started by <Request ARC Termination> 1082 // need to clean up RequestArcInitiationAction. 1083 removeAction(RequestArcTerminationAction.class); 1084 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 1085 message.getSource(), false); 1086 addAndStartAction(action); 1087 return true; 1088 } 1089 1090 @Override 1091 @ServiceThreadOnly 1092 protected boolean handleSetSystemAudioMode(HdmiCecMessage message) { 1093 assertRunOnServiceThread(); 1094 if (!isMessageForSystemAudio(message)) { 1095 if (getAvrDeviceInfo() == null) { 1096 // AVR may not have been discovered yet. Delay the message processing. 1097 mDelayedMessageBuffer.add(message); 1098 return true; 1099 } 1100 HdmiLogger.warning("Invalid <Set System Audio Mode> message:" + message); 1101 mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); 1102 return true; 1103 } 1104 SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this, 1105 message.getSource(), HdmiUtils.parseCommandParamSystemAudioStatus(message), null); 1106 addAndStartAction(action); 1107 return true; 1108 } 1109 1110 @Override 1111 @ServiceThreadOnly 1112 protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) { 1113 assertRunOnServiceThread(); 1114 if (!isMessageForSystemAudio(message)) { 1115 HdmiLogger.warning("Invalid <System Audio Mode Status> message:" + message); 1116 // Ignore this message. 1117 return true; 1118 } 1119 setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message), true); 1120 return true; 1121 } 1122 1123 // Seq #53 1124 @Override 1125 @ServiceThreadOnly 1126 protected boolean handleRecordTvScreen(HdmiCecMessage message) { 1127 List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class); 1128 if (!actions.isEmpty()) { 1129 // Assumes only one OneTouchRecordAction. 1130 OneTouchRecordAction action = actions.get(0); 1131 if (action.getRecorderAddress() != message.getSource()) { 1132 announceOneTouchRecordResult( 1133 message.getSource(), 1134 HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS); 1135 } 1136 return super.handleRecordTvScreen(message); 1137 } 1138 1139 int recorderAddress = message.getSource(); 1140 byte[] recordSource = mService.invokeRecordRequestListener(recorderAddress); 1141 int reason = startOneTouchRecord(recorderAddress, recordSource); 1142 if (reason != Constants.ABORT_NO_ERROR) { 1143 mService.maySendFeatureAbortCommand(message, reason); 1144 } 1145 return true; 1146 } 1147 1148 @Override 1149 protected boolean handleTimerClearedStatus(HdmiCecMessage message) { 1150 byte[] params = message.getParams(); 1151 int timerClearedStatusData = params[0] & 0xFF; 1152 announceTimerRecordingResult(message.getSource(), timerClearedStatusData); 1153 return true; 1154 } 1155 1156 void announceOneTouchRecordResult(int recorderAddress, int result) { 1157 mService.invokeOneTouchRecordResult(recorderAddress, result); 1158 } 1159 1160 void announceTimerRecordingResult(int recorderAddress, int result) { 1161 mService.invokeTimerRecordingResult(recorderAddress, result); 1162 } 1163 1164 void announceClearTimerRecordingResult(int recorderAddress, int result) { 1165 mService.invokeClearTimerRecordingResult(recorderAddress, result); 1166 } 1167 1168 private boolean isMessageForSystemAudio(HdmiCecMessage message) { 1169 return mService.isControlEnabled() 1170 && message.getSource() == Constants.ADDR_AUDIO_SYSTEM 1171 && (message.getDestination() == Constants.ADDR_TV 1172 || message.getDestination() == Constants.ADDR_BROADCAST) 1173 && getAvrDeviceInfo() != null; 1174 } 1175 1176 /** 1177 * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same 1178 * logical address as new device info's. 1179 * 1180 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 1181 * 1182 * @param deviceInfo a new {@link HdmiDeviceInfo} to be added. 1183 * @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo} 1184 * that has the same logical address as new one has. 1185 */ 1186 @ServiceThreadOnly 1187 private HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) { 1188 assertRunOnServiceThread(); 1189 HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress()); 1190 if (oldDeviceInfo != null) { 1191 removeDeviceInfo(deviceInfo.getId()); 1192 } 1193 mDeviceInfos.append(deviceInfo.getId(), deviceInfo); 1194 updateSafeDeviceInfoList(); 1195 return oldDeviceInfo; 1196 } 1197 1198 /** 1199 * Remove a device info corresponding to the given {@code logicalAddress}. 1200 * It returns removed {@link HdmiDeviceInfo} if exists. 1201 * 1202 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 1203 * 1204 * @param id id of device to be removed 1205 * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null} 1206 */ 1207 @ServiceThreadOnly 1208 private HdmiDeviceInfo removeDeviceInfo(int id) { 1209 assertRunOnServiceThread(); 1210 HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id); 1211 if (deviceInfo != null) { 1212 mDeviceInfos.remove(id); 1213 } 1214 updateSafeDeviceInfoList(); 1215 return deviceInfo; 1216 } 1217 1218 /** 1219 * Return a list of all {@link HdmiDeviceInfo}. 1220 * 1221 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 1222 * This is not thread-safe. For thread safety, call {@link #getSafeExternalInputsLocked} which 1223 * does not include local device. 1224 */ 1225 @ServiceThreadOnly 1226 List<HdmiDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) { 1227 assertRunOnServiceThread(); 1228 if (includeLocalDevice) { 1229 return HdmiUtils.sparseArrayToList(mDeviceInfos); 1230 } else { 1231 ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>(); 1232 for (int i = 0; i < mDeviceInfos.size(); ++i) { 1233 HdmiDeviceInfo info = mDeviceInfos.valueAt(i); 1234 if (!isLocalDeviceAddress(info.getLogicalAddress())) { 1235 infoList.add(info); 1236 } 1237 } 1238 return infoList; 1239 } 1240 } 1241 1242 /** 1243 * Return external input devices. 1244 */ 1245 List<HdmiDeviceInfo> getSafeExternalInputsLocked() { 1246 return mSafeExternalInputs; 1247 } 1248 1249 @ServiceThreadOnly 1250 private void updateSafeDeviceInfoList() { 1251 assertRunOnServiceThread(); 1252 List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos); 1253 List<HdmiDeviceInfo> externalInputs = getInputDevices(); 1254 synchronized (mLock) { 1255 mSafeAllDeviceInfos = copiedDevices; 1256 mSafeExternalInputs = externalInputs; 1257 } 1258 } 1259 1260 /** 1261 * Return a list of external cec input (source) devices. 1262 * 1263 * <p>Note that this effectively excludes non-source devices like system audio, 1264 * secondary TV. 1265 */ 1266 private List<HdmiDeviceInfo> getInputDevices() { 1267 ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>(); 1268 for (int i = 0; i < mDeviceInfos.size(); ++i) { 1269 HdmiDeviceInfo info = mDeviceInfos.valueAt(i); 1270 if (isLocalDeviceAddress(info.getLogicalAddress())) { 1271 continue; 1272 } 1273 if (info.isSourceType() && !hideDevicesBehindLegacySwitch(info)) { 1274 infoList.add(info); 1275 } 1276 } 1277 return infoList; 1278 } 1279 1280 // Check if we are hiding CEC devices connected to a legacy (non-CEC) switch. 1281 // Returns true if the policy is set to true, and the device to check does not have 1282 // a parent CEC device (which should be the CEC-enabled switch) in the list. 1283 private boolean hideDevicesBehindLegacySwitch(HdmiDeviceInfo info) { 1284 return HdmiConfig.HIDE_DEVICES_BEHIND_LEGACY_SWITCH 1285 && !isConnectedToCecSwitch(info.getPhysicalAddress(), mCecSwitches); 1286 } 1287 1288 private static boolean isConnectedToCecSwitch(int path, Collection<Integer> switches) { 1289 for (int switchPath : switches) { 1290 if (isParentPath(switchPath, path)) { 1291 return true; 1292 } 1293 } 1294 return false; 1295 } 1296 1297 private static boolean isParentPath(int parentPath, int childPath) { 1298 // (A000, AB00) (AB00, ABC0), (ABC0, ABCD) 1299 // If child's last non-zero nibble is removed, the result equals to the parent. 1300 for (int i = 0; i <= 12; i += 4) { 1301 int nibble = (childPath >> i) & 0xF; 1302 if (nibble != 0) { 1303 int parentNibble = (parentPath >> i) & 0xF; 1304 return parentNibble == 0 && (childPath >> i+4) == (parentPath >> i+4); 1305 } 1306 } 1307 return false; 1308 } 1309 1310 private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) { 1311 if (!hideDevicesBehindLegacySwitch(info)) { 1312 mService.invokeDeviceEventListeners(info, status); 1313 } 1314 } 1315 1316 private boolean isLocalDeviceAddress(int address) { 1317 return mLocalDeviceAddresses.contains(address); 1318 } 1319 1320 @ServiceThreadOnly 1321 HdmiDeviceInfo getAvrDeviceInfo() { 1322 assertRunOnServiceThread(); 1323 return getCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); 1324 } 1325 1326 /** 1327 * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}. 1328 * 1329 * This is not thread-safe. For thread safety, call {@link #getSafeCecDeviceInfo(int)}. 1330 * 1331 * @param logicalAddress logical address of the device to be retrieved 1332 * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}. 1333 * Returns null if no logical address matched 1334 */ 1335 @ServiceThreadOnly 1336 HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) { 1337 assertRunOnServiceThread(); 1338 return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress)); 1339 } 1340 1341 boolean hasSystemAudioDevice() { 1342 return getSafeAvrDeviceInfo() != null; 1343 } 1344 1345 HdmiDeviceInfo getSafeAvrDeviceInfo() { 1346 return getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); 1347 } 1348 1349 /** 1350 * Thread safe version of {@link #getCecDeviceInfo(int)}. 1351 * 1352 * @param logicalAddress logical address to be retrieved 1353 * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}. 1354 * Returns null if no logical address matched 1355 */ 1356 HdmiDeviceInfo getSafeCecDeviceInfo(int logicalAddress) { 1357 synchronized (mLock) { 1358 for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { 1359 if (info.isCecDevice() && info.getLogicalAddress() == logicalAddress) { 1360 return info; 1361 } 1362 } 1363 return null; 1364 } 1365 } 1366 1367 List<HdmiDeviceInfo> getSafeCecDevicesLocked() { 1368 ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>(); 1369 for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { 1370 if (isLocalDeviceAddress(info.getLogicalAddress())) { 1371 continue; 1372 } 1373 infoList.add(info); 1374 } 1375 return infoList; 1376 } 1377 1378 /** 1379 * Called when a device is newly added or a new device is detected or 1380 * existing device is updated. 1381 * 1382 * @param info device info of a new device. 1383 */ 1384 @ServiceThreadOnly 1385 final void addCecDevice(HdmiDeviceInfo info) { 1386 assertRunOnServiceThread(); 1387 HdmiDeviceInfo old = addDeviceInfo(info); 1388 if (info.getLogicalAddress() == mAddress) { 1389 // The addition of TV device itself should not be notified. 1390 return; 1391 } 1392 if (old == null) { 1393 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE); 1394 } else if (!old.equals(info)) { 1395 invokeDeviceEventListener(old, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); 1396 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE); 1397 } 1398 } 1399 1400 /** 1401 * Called when a device is removed or removal of device is detected. 1402 * 1403 * @param address a logical address of a device to be removed 1404 */ 1405 @ServiceThreadOnly 1406 final void removeCecDevice(int address) { 1407 assertRunOnServiceThread(); 1408 HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address)); 1409 1410 mCecMessageCache.flushMessagesFrom(address); 1411 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); 1412 } 1413 1414 @ServiceThreadOnly 1415 void handleRemoveActiveRoutingPath(int path) { 1416 assertRunOnServiceThread(); 1417 // Seq #23 1418 if (isTailOfActivePath(path, getActivePath())) { 1419 int newPath = mService.portIdToPath(getActivePortId()); 1420 startRoutingControl(getActivePath(), newPath, true, null); 1421 } 1422 } 1423 1424 /** 1425 * Launch routing control process. 1426 * 1427 * @param routingForBootup true if routing control is initiated due to One Touch Play 1428 * or TV power on 1429 */ 1430 @ServiceThreadOnly 1431 void launchRoutingControl(boolean routingForBootup) { 1432 assertRunOnServiceThread(); 1433 // Seq #24 1434 if (getActivePortId() != Constants.INVALID_PORT_ID) { 1435 if (!routingForBootup && !isProhibitMode()) { 1436 int newPath = mService.portIdToPath(getActivePortId()); 1437 setActivePath(newPath); 1438 startRoutingControl(getActivePath(), newPath, routingForBootup, null); 1439 } 1440 } else { 1441 int activePath = mService.getPhysicalAddress(); 1442 setActivePath(activePath); 1443 if (!routingForBootup 1444 && !mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) { 1445 mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(mAddress, 1446 activePath)); 1447 } 1448 } 1449 } 1450 1451 /** 1452 * Returns the {@link HdmiDeviceInfo} instance whose physical address matches 1453 * the given routing path. CEC devices use routing path for its physical address to 1454 * describe the hierarchy of the devices in the network. 1455 * 1456 * @param path routing path or physical address 1457 * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null 1458 */ 1459 @ServiceThreadOnly 1460 final HdmiDeviceInfo getDeviceInfoByPath(int path) { 1461 assertRunOnServiceThread(); 1462 for (HdmiDeviceInfo info : getDeviceInfoList(false)) { 1463 if (info.getPhysicalAddress() == path) { 1464 return info; 1465 } 1466 } 1467 return null; 1468 } 1469 1470 /** 1471 * Returns the {@link HdmiDeviceInfo} instance whose physical address matches 1472 * the given routing path. This is the version accessible safely from threads 1473 * other than service thread. 1474 * 1475 * @param path routing path or physical address 1476 * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null 1477 */ 1478 HdmiDeviceInfo getSafeDeviceInfoByPath(int path) { 1479 synchronized (mLock) { 1480 for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { 1481 if (info.getPhysicalAddress() == path) { 1482 return info; 1483 } 1484 } 1485 return null; 1486 } 1487 } 1488 1489 /** 1490 * Whether a device of the specified physical address and logical address exists 1491 * in a device info list. However, both are minimal condition and it could 1492 * be different device from the original one. 1493 * 1494 * @param logicalAddress logical address of a device to be searched 1495 * @param physicalAddress physical address of a device to be searched 1496 * @return true if exist; otherwise false 1497 */ 1498 @ServiceThreadOnly 1499 private boolean isInDeviceList(int logicalAddress, int physicalAddress) { 1500 assertRunOnServiceThread(); 1501 HdmiDeviceInfo device = getCecDeviceInfo(logicalAddress); 1502 if (device == null) { 1503 return false; 1504 } 1505 return device.getPhysicalAddress() == physicalAddress; 1506 } 1507 1508 @Override 1509 @ServiceThreadOnly 1510 void onHotplug(int portId, boolean connected) { 1511 assertRunOnServiceThread(); 1512 1513 if (!connected) { 1514 removeCecSwitches(portId); 1515 } 1516 // Tv device will have permanent HotplugDetectionAction. 1517 List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class); 1518 if (!hotplugActions.isEmpty()) { 1519 // Note that hotplug action is single action running on a machine. 1520 // "pollAllDevicesNow" cleans up timer and start poll action immediately. 1521 // It covers seq #40, #43. 1522 hotplugActions.get(0).pollAllDevicesNow(); 1523 } 1524 updateArcFeatureStatus(portId, connected); 1525 } 1526 1527 private void removeCecSwitches(int portId) { 1528 Iterator<Integer> it = mCecSwitches.iterator(); 1529 while (!it.hasNext()) { 1530 int path = it.next(); 1531 if (pathToPortId(path) == portId) { 1532 it.remove(); 1533 } 1534 } 1535 } 1536 1537 @ServiceThreadOnly 1538 void setAutoDeviceOff(boolean enabled) { 1539 assertRunOnServiceThread(); 1540 mAutoDeviceOff = enabled; 1541 } 1542 1543 @ServiceThreadOnly 1544 void setAutoWakeup(boolean enabled) { 1545 assertRunOnServiceThread(); 1546 mAutoWakeup = enabled; 1547 } 1548 1549 @ServiceThreadOnly 1550 boolean getAutoWakeup() { 1551 assertRunOnServiceThread(); 1552 return mAutoWakeup; 1553 } 1554 1555 @Override 1556 @ServiceThreadOnly 1557 protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) { 1558 super.disableDevice(initiatedByCec, callback); 1559 assertRunOnServiceThread(); 1560 mService.unregisterTvInputCallback(mTvInputCallback); 1561 // Remove any repeated working actions. 1562 // HotplugDetectionAction will be reinstated during the wake up process. 1563 // HdmiControlService.onWakeUp() -> initializeLocalDevices() -> 1564 // LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery(). 1565 removeAction(DeviceDiscoveryAction.class); 1566 removeAction(HotplugDetectionAction.class); 1567 removeAction(PowerStatusMonitorAction.class); 1568 // Remove recording actions. 1569 removeAction(OneTouchRecordAction.class); 1570 removeAction(TimerRecordingAction.class); 1571 1572 disableSystemAudioIfExist(); 1573 disableArcIfExist(); 1574 clearDeviceInfoList(); 1575 checkIfPendingActionsCleared(); 1576 } 1577 1578 @ServiceThreadOnly 1579 private void disableSystemAudioIfExist() { 1580 assertRunOnServiceThread(); 1581 if (getAvrDeviceInfo() == null) { 1582 return; 1583 } 1584 1585 // Seq #31. 1586 removeAction(SystemAudioActionFromAvr.class); 1587 removeAction(SystemAudioActionFromTv.class); 1588 removeAction(SystemAudioAutoInitiationAction.class); 1589 removeAction(SystemAudioStatusAction.class); 1590 removeAction(VolumeControlAction.class); 1591 1592 // Turn off the mode but do not write it the settings, so that the next time TV powers on 1593 // the system audio mode setting can be restored automatically. 1594 setSystemAudioMode(false, false); 1595 } 1596 1597 @ServiceThreadOnly 1598 private void disableArcIfExist() { 1599 assertRunOnServiceThread(); 1600 HdmiDeviceInfo avr = getAvrDeviceInfo(); 1601 if (avr == null) { 1602 return; 1603 } 1604 1605 // Seq #44. 1606 removeAction(RequestArcInitiationAction.class); 1607 if (!hasAction(RequestArcTerminationAction.class) && isArcEstabilished()) { 1608 addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress())); 1609 } 1610 } 1611 1612 @Override 1613 @ServiceThreadOnly 1614 protected void onStandby(boolean initiatedByCec) { 1615 assertRunOnServiceThread(); 1616 // Seq #11 1617 if (!mService.isControlEnabled()) { 1618 return; 1619 } 1620 if (!initiatedByCec && mAutoDeviceOff) { 1621 mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby( 1622 mAddress, Constants.ADDR_BROADCAST)); 1623 } 1624 } 1625 1626 boolean isProhibitMode() { 1627 return mService.isProhibitMode(); 1628 } 1629 1630 boolean isPowerStandbyOrTransient() { 1631 return mService.isPowerStandbyOrTransient(); 1632 } 1633 1634 @ServiceThreadOnly 1635 void displayOsd(int messageId) { 1636 assertRunOnServiceThread(); 1637 mService.displayOsd(messageId); 1638 } 1639 1640 @ServiceThreadOnly 1641 void displayOsd(int messageId, int extra) { 1642 assertRunOnServiceThread(); 1643 mService.displayOsd(messageId, extra); 1644 } 1645 1646 // Seq #54 and #55 1647 @ServiceThreadOnly 1648 int startOneTouchRecord(int recorderAddress, byte[] recordSource) { 1649 assertRunOnServiceThread(); 1650 if (!mService.isControlEnabled()) { 1651 Slog.w(TAG, "Can not start one touch record. CEC control is disabled."); 1652 announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED); 1653 return Constants.ABORT_NOT_IN_CORRECT_MODE; 1654 } 1655 1656 if (!checkRecorder(recorderAddress)) { 1657 Slog.w(TAG, "Invalid recorder address:" + recorderAddress); 1658 announceOneTouchRecordResult(recorderAddress, 1659 ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION); 1660 return Constants.ABORT_NOT_IN_CORRECT_MODE; 1661 } 1662 1663 if (!checkRecordSource(recordSource)) { 1664 Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource)); 1665 announceOneTouchRecordResult(recorderAddress, 1666 ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN); 1667 return Constants.ABORT_CANNOT_PROVIDE_SOURCE; 1668 } 1669 1670 addAndStartAction(new OneTouchRecordAction(this, recorderAddress, recordSource)); 1671 Slog.i(TAG, "Start new [One Touch Record]-Target:" + recorderAddress + ", recordSource:" 1672 + Arrays.toString(recordSource)); 1673 return Constants.ABORT_NO_ERROR; 1674 } 1675 1676 @ServiceThreadOnly 1677 void stopOneTouchRecord(int recorderAddress) { 1678 assertRunOnServiceThread(); 1679 if (!mService.isControlEnabled()) { 1680 Slog.w(TAG, "Can not stop one touch record. CEC control is disabled."); 1681 announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED); 1682 return; 1683 } 1684 1685 if (!checkRecorder(recorderAddress)) { 1686 Slog.w(TAG, "Invalid recorder address:" + recorderAddress); 1687 announceOneTouchRecordResult(recorderAddress, 1688 ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION); 1689 return; 1690 } 1691 1692 // Remove one touch record action so that other one touch record can be started. 1693 removeAction(OneTouchRecordAction.class); 1694 mService.sendCecCommand(HdmiCecMessageBuilder.buildRecordOff(mAddress, recorderAddress)); 1695 Slog.i(TAG, "Stop [One Touch Record]-Target:" + recorderAddress); 1696 } 1697 1698 private boolean checkRecorder(int recorderAddress) { 1699 HdmiDeviceInfo device = getCecDeviceInfo(recorderAddress); 1700 return (device != null) 1701 && (HdmiUtils.getTypeFromAddress(recorderAddress) 1702 == HdmiDeviceInfo.DEVICE_RECORDER); 1703 } 1704 1705 private boolean checkRecordSource(byte[] recordSource) { 1706 return (recordSource != null) && HdmiRecordSources.checkRecordSource(recordSource); 1707 } 1708 1709 @ServiceThreadOnly 1710 void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) { 1711 assertRunOnServiceThread(); 1712 if (!mService.isControlEnabled()) { 1713 Slog.w(TAG, "Can not start one touch record. CEC control is disabled."); 1714 announceTimerRecordingResult(recorderAddress, 1715 TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED); 1716 return; 1717 } 1718 1719 if (!checkRecorder(recorderAddress)) { 1720 Slog.w(TAG, "Invalid recorder address:" + recorderAddress); 1721 announceTimerRecordingResult(recorderAddress, 1722 TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION); 1723 return; 1724 } 1725 1726 if (!checkTimerRecordingSource(sourceType, recordSource)) { 1727 Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource)); 1728 announceTimerRecordingResult( 1729 recorderAddress, 1730 TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE); 1731 return; 1732 } 1733 1734 addAndStartAction( 1735 new TimerRecordingAction(this, recorderAddress, sourceType, recordSource)); 1736 Slog.i(TAG, "Start [Timer Recording]-Target:" + recorderAddress + ", SourceType:" 1737 + sourceType + ", RecordSource:" + Arrays.toString(recordSource)); 1738 } 1739 1740 private boolean checkTimerRecordingSource(int sourceType, byte[] recordSource) { 1741 return (recordSource != null) 1742 && HdmiTimerRecordSources.checkTimerRecordSource(sourceType, recordSource); 1743 } 1744 1745 @ServiceThreadOnly 1746 void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) { 1747 assertRunOnServiceThread(); 1748 if (!mService.isControlEnabled()) { 1749 Slog.w(TAG, "Can not start one touch record. CEC control is disabled."); 1750 announceClearTimerRecordingResult(recorderAddress, CLEAR_TIMER_STATUS_CEC_DISABLE); 1751 return; 1752 } 1753 1754 if (!checkRecorder(recorderAddress)) { 1755 Slog.w(TAG, "Invalid recorder address:" + recorderAddress); 1756 announceClearTimerRecordingResult(recorderAddress, 1757 CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION); 1758 return; 1759 } 1760 1761 if (!checkTimerRecordingSource(sourceType, recordSource)) { 1762 Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource)); 1763 announceClearTimerRecordingResult(recorderAddress, 1764 CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE); 1765 return; 1766 } 1767 1768 sendClearTimerMessage(recorderAddress, sourceType, recordSource); 1769 } 1770 1771 private void sendClearTimerMessage(final int recorderAddress, int sourceType, 1772 byte[] recordSource) { 1773 HdmiCecMessage message = null; 1774 switch (sourceType) { 1775 case TIMER_RECORDING_TYPE_DIGITAL: 1776 message = HdmiCecMessageBuilder.buildClearDigitalTimer(mAddress, recorderAddress, 1777 recordSource); 1778 break; 1779 case TIMER_RECORDING_TYPE_ANALOGUE: 1780 message = HdmiCecMessageBuilder.buildClearAnalogueTimer(mAddress, recorderAddress, 1781 recordSource); 1782 break; 1783 case TIMER_RECORDING_TYPE_EXTERNAL: 1784 message = HdmiCecMessageBuilder.buildClearExternalTimer(mAddress, recorderAddress, 1785 recordSource); 1786 break; 1787 default: 1788 Slog.w(TAG, "Invalid source type:" + recorderAddress); 1789 announceClearTimerRecordingResult(recorderAddress, 1790 CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE); 1791 return; 1792 1793 } 1794 mService.sendCecCommand(message, new SendMessageCallback() { 1795 @Override 1796 public void onSendCompleted(int error) { 1797 if (error != Constants.SEND_RESULT_SUCCESS) { 1798 announceClearTimerRecordingResult(recorderAddress, 1799 CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE); 1800 } 1801 } 1802 }); 1803 } 1804 1805 void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) { 1806 HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress); 1807 if (info == null) { 1808 Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress); 1809 return; 1810 } 1811 1812 if (info.getDevicePowerStatus() == newPowerStatus) { 1813 return; 1814 } 1815 1816 HdmiDeviceInfo newInfo = HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus); 1817 // addDeviceInfo replaces old device info with new one if exists. 1818 addDeviceInfo(newInfo); 1819 1820 invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE); 1821 } 1822 1823 @Override 1824 protected boolean handleMenuStatus(HdmiCecMessage message) { 1825 // Do nothing and just return true not to prevent from responding <Feature Abort>. 1826 return true; 1827 } 1828 1829 @Override 1830 protected void sendStandby(int deviceId) { 1831 HdmiDeviceInfo targetDevice = mDeviceInfos.get(deviceId); 1832 if (targetDevice == null) { 1833 return; 1834 } 1835 int targetAddress = targetDevice.getLogicalAddress(); 1836 mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress)); 1837 } 1838 1839 @ServiceThreadOnly 1840 void processAllDelayedMessages() { 1841 assertRunOnServiceThread(); 1842 mDelayedMessageBuffer.processAllMessages(); 1843 } 1844 1845 @ServiceThreadOnly 1846 void processDelayedMessages(int address) { 1847 assertRunOnServiceThread(); 1848 mDelayedMessageBuffer.processMessagesForDevice(address); 1849 } 1850 1851 @ServiceThreadOnly 1852 void processDelayedActiveSource(int address) { 1853 assertRunOnServiceThread(); 1854 mDelayedMessageBuffer.processActiveSource(address); 1855 } 1856 1857 @Override 1858 protected void dump(final IndentingPrintWriter pw) { 1859 super.dump(pw); 1860 pw.println("mArcEstablished: " + mArcEstablished); 1861 pw.println("mArcFeatureEnabled: " + mArcFeatureEnabled); 1862 pw.println("mSystemAudioActivated: " + mSystemAudioActivated); 1863 pw.println("mSystemAudioMute: " + mSystemAudioMute); 1864 pw.println("mAutoDeviceOff: " + mAutoDeviceOff); 1865 pw.println("mAutoWakeup: " + mAutoWakeup); 1866 pw.println("mSkipRoutingControl: " + mSkipRoutingControl); 1867 pw.println("mPrevPortId: " + mPrevPortId); 1868 pw.println("CEC devices:"); 1869 pw.increaseIndent(); 1870 for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { 1871 pw.println(info); 1872 } 1873 pw.decreaseIndent(); 1874 } 1875} 1876