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