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 17 package com.android.server.hdmi; 18 19 import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CEC_DISABLE; 20 import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION; 21 import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE; 22 import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CEC_DISABLED; 23 import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION; 24 import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN; 25 import static android.hardware.hdmi.HdmiControlManager.OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT; 26 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED; 27 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION; 28 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE; 29 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_ANALOGUE; 30 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL; 31 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL; 32 33 import android.hardware.hdmi.HdmiControlManager; 34 import android.hardware.hdmi.HdmiDeviceInfo; 35 import android.hardware.hdmi.HdmiPortInfo; 36 import android.hardware.hdmi.HdmiRecordSources; 37 import android.hardware.hdmi.HdmiTimerRecordSources; 38 import android.hardware.hdmi.IHdmiControlCallback; 39 import android.media.AudioManager; 40 import android.media.AudioSystem; 41 import android.media.tv.TvInputInfo; 42 import android.media.tv.TvInputManager.TvInputCallback; 43 import android.os.RemoteException; 44 import android.provider.Settings.Global; 45 import android.util.ArraySet; 46 import android.util.Slog; 47 import android.util.SparseArray; 48 import android.util.SparseBooleanArray; 49 50 import com.android.internal.annotations.GuardedBy; 51 import com.android.internal.util.IndentingPrintWriter; 52 import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback; 53 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; 54 import com.android.server.hdmi.HdmiControlService.SendMessageCallback; 55 import java.io.UnsupportedEncodingException; 56 import java.util.ArrayList; 57 import java.util.Arrays; 58 import java.util.Collection; 59 import java.util.Collections; 60 import java.util.Iterator; 61 import java.util.List; 62 import java.util.HashMap; 63 64 /** 65 * Represent a logical device of type TV residing in Android system. 66 */ 67 final 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 setActivePath(path); 351 // TODO: Handle PAP/PIP case. 352 // Show OSD port change banner 353 if (notifyInputChange) { 354 ActiveSource activeSource = getActiveSource(); 355 HdmiDeviceInfo info = getCecDeviceInfo(activeSource.logicalAddress); 356 if (info == null) { 357 info = mService.getDeviceInfoByPort(getActivePortId()); 358 if (info == null) { 359 // No CEC/MHL device is present at the port. Attempt to switch to 360 // the hardware port itself for non-CEC devices that may be connected. 361 info = new HdmiDeviceInfo(path, getActivePortId()); 362 } 363 } 364 mService.invokeInputChangeListener(info); 365 } 366 } 367 368 @ServiceThreadOnly 369 void doManualPortSwitching(int portId, IHdmiControlCallback callback) { 370 assertRunOnServiceThread(); 371 // Seq #20 372 if (!mService.isValidPortId(portId)) { 373 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); 374 return; 375 } 376 if (portId == getActivePortId()) { 377 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS); 378 return; 379 } 380 mActiveSource.invalidate(); 381 if (!mService.isControlEnabled()) { 382 setActivePortId(portId); 383 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); 384 return; 385 } 386 int oldPath = getActivePortId() != Constants.INVALID_PORT_ID 387 ? mService.portIdToPath(getActivePortId()) : getDeviceInfo().getPhysicalAddress(); 388 setActivePath(oldPath); 389 if (mSkipRoutingControl) { 390 mSkipRoutingControl = false; 391 return; 392 } 393 int newPath = mService.portIdToPath(portId); 394 startRoutingControl(oldPath, newPath, true, callback); 395 } 396 397 @ServiceThreadOnly 398 void startRoutingControl(int oldPath, int newPath, boolean queryDevicePowerStatus, 399 IHdmiControlCallback callback) { 400 assertRunOnServiceThread(); 401 if (oldPath == newPath) { 402 return; 403 } 404 HdmiCecMessage routingChange = 405 HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath); 406 mService.sendCecCommand(routingChange); 407 removeAction(RoutingControlAction.class); 408 addAndStartAction( 409 new RoutingControlAction(this, newPath, queryDevicePowerStatus, callback)); 410 } 411 412 @ServiceThreadOnly 413 int getPowerStatus() { 414 assertRunOnServiceThread(); 415 return mService.getPowerStatus(); 416 } 417 418 /** 419 * Sends key to a target CEC device. 420 * 421 * @param keyCode key code to send. Defined in {@link android.view.KeyEvent}. 422 * @param isPressed true if this is key press event 423 */ 424 @Override 425 @ServiceThreadOnly 426 protected void sendKeyEvent(int keyCode, boolean isPressed) { 427 assertRunOnServiceThread(); 428 if (!HdmiCecKeycode.isSupportedKeycode(keyCode)) { 429 Slog.w(TAG, "Unsupported key: " + keyCode); 430 return; 431 } 432 List<SendKeyAction> action = getActions(SendKeyAction.class); 433 int logicalAddress = findKeyReceiverAddress(); 434 if (logicalAddress == mAddress) { 435 Slog.w(TAG, "Discard key event to itself :" + keyCode + " pressed:" + isPressed); 436 return; 437 } 438 if (!action.isEmpty()) { 439 action.get(0).processKeyEvent(keyCode, isPressed); 440 } else { 441 if (isPressed) { 442 if (logicalAddress != Constants.ADDR_INVALID) { 443 addAndStartAction(new SendKeyAction(this, logicalAddress, keyCode)); 444 return; 445 } 446 } 447 Slog.w(TAG, "Discard key event: " + keyCode + " pressed:" + isPressed); 448 } 449 } 450 451 private int findKeyReceiverAddress() { 452 if (getActiveSource().isValid()) { 453 return getActiveSource().logicalAddress; 454 } 455 HdmiDeviceInfo info = getDeviceInfoByPath(getActivePath()); 456 if (info != null) { 457 return info.getLogicalAddress(); 458 } 459 return Constants.ADDR_INVALID; 460 } 461 462 private static void invokeCallback(IHdmiControlCallback callback, int result) { 463 if (callback == null) { 464 return; 465 } 466 try { 467 callback.onComplete(result); 468 } catch (RemoteException e) { 469 Slog.e(TAG, "Invoking callback failed:" + e); 470 } 471 } 472 473 @Override 474 @ServiceThreadOnly 475 protected boolean handleActiveSource(HdmiCecMessage message) { 476 assertRunOnServiceThread(); 477 int logicalAddress = message.getSource(); 478 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); 479 HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress); 480 if (info == null) { 481 if (!handleNewDeviceAtTheTailOfActivePath(physicalAddress)) { 482 HdmiLogger.debug("Device info %X not found; buffering the command", logicalAddress); 483 mDelayedMessageBuffer.add(message); 484 } 485 } else if (!isInputReady(info.getId())) { 486 HdmiLogger.debug("Input not ready for device: %X; buffering the command", info.getId()); 487 mDelayedMessageBuffer.add(message); 488 } else { 489 updateDevicePowerStatus(logicalAddress, HdmiControlManager.POWER_STATUS_ON); 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 } else { 790 setSystemAudioMode(false, true); 791 } 792 } 793 }); 794 addAndStartAction(action); 795 } 796 797 @ServiceThreadOnly 798 void onNewAvrAdded(HdmiDeviceInfo avr) { 799 assertRunOnServiceThread(); 800 if (getSystemAudioModeSetting() && !isSystemAudioActivated()) { 801 addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress())); 802 } 803 if (isArcFeatureEnabled(avr.getPortId()) 804 && !hasAction(SetArcTransmissionStateAction.class)) { 805 startArcAction(true); 806 } 807 } 808 809 // Clear all device info. 810 @ServiceThreadOnly 811 private void clearDeviceInfoList() { 812 assertRunOnServiceThread(); 813 for (HdmiDeviceInfo info : mSafeExternalInputs) { 814 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); 815 } 816 mDeviceInfos.clear(); 817 updateSafeDeviceInfoList(); 818 } 819 820 @ServiceThreadOnly 821 // Seq #32 822 void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) { 823 assertRunOnServiceThread(); 824 if (!mService.isControlEnabled() || hasAction(DeviceDiscoveryAction.class)) { 825 setSystemAudioMode(false, true); 826 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); 827 return; 828 } 829 HdmiDeviceInfo avr = getAvrDeviceInfo(); 830 if (avr == null) { 831 setSystemAudioMode(false, true); 832 invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); 833 return; 834 } 835 836 addAndStartAction( 837 new SystemAudioActionFromTv(this, avr.getLogicalAddress(), enabled, callback)); 838 } 839 840 // # Seq 25 841 void setSystemAudioMode(boolean on, boolean updateSetting) { 842 HdmiLogger.debug("System Audio Mode change[old:%b new:%b]", mSystemAudioActivated, on); 843 844 if (updateSetting) { 845 mService.writeBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, on); 846 } 847 updateAudioManagerForSystemAudio(on); 848 synchronized (mLock) { 849 if (mSystemAudioActivated != on) { 850 mSystemAudioActivated = on; 851 mService.announceSystemAudioModeChange(on); 852 } 853 } 854 } 855 856 private void updateAudioManagerForSystemAudio(boolean on) { 857 int device = mService.getAudioManager().setHdmiSystemAudioSupported(on); 858 HdmiLogger.debug("[A]UpdateSystemAudio mode[on=%b] output=[%X]", on, device); 859 } 860 861 boolean isSystemAudioActivated() { 862 if (!hasSystemAudioDevice()) { 863 return false; 864 } 865 synchronized (mLock) { 866 return mSystemAudioActivated; 867 } 868 } 869 870 boolean getSystemAudioModeSetting() { 871 return mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, false); 872 } 873 874 /** 875 * Change ARC status into the given {@code enabled} status. 876 * 877 * @return {@code true} if ARC was in "Enabled" status 878 */ 879 @ServiceThreadOnly 880 boolean setArcStatus(boolean enabled) { 881 assertRunOnServiceThread(); 882 883 HdmiLogger.debug("Set Arc Status[old:%b new:%b]", mArcEstablished, enabled); 884 boolean oldStatus = mArcEstablished; 885 // 1. Enable/disable ARC circuit. 886 setAudioReturnChannel(enabled); 887 // 2. Notify arc status to audio service. 888 notifyArcStatusToAudioService(enabled); 889 // 3. Update arc status; 890 mArcEstablished = enabled; 891 return oldStatus; 892 } 893 894 /** 895 * Switch hardware ARC circuit in the system. 896 */ 897 @ServiceThreadOnly 898 void setAudioReturnChannel(boolean enabled) { 899 assertRunOnServiceThread(); 900 HdmiDeviceInfo avr = getAvrDeviceInfo(); 901 if (avr != null) { 902 mService.setAudioReturnChannel(avr.getPortId(), enabled); 903 } 904 } 905 906 @ServiceThreadOnly 907 private void updateArcFeatureStatus(int portId, boolean isConnected) { 908 assertRunOnServiceThread(); 909 HdmiPortInfo portInfo = mService.getPortInfo(portId); 910 if (!portInfo.isArcSupported()) { 911 return; 912 } 913 HdmiDeviceInfo avr = getAvrDeviceInfo(); 914 if (avr == null) { 915 if (isConnected) { 916 // Update the status (since TV may not have seen AVR yet) so 917 // that ARC can be initiated after discovery. 918 mArcFeatureEnabled.put(portId, isConnected); 919 } 920 return; 921 } 922 // HEAC 2.4, HEACT 5-15 923 // Should not activate ARC if +5V status is false. 924 if (avr.getPortId() == portId) { 925 changeArcFeatureEnabled(portId, isConnected); 926 } 927 } 928 929 @ServiceThreadOnly 930 boolean isConnected(int portId) { 931 assertRunOnServiceThread(); 932 return mService.isConnected(portId); 933 } 934 935 private void notifyArcStatusToAudioService(boolean enabled) { 936 // Note that we don't set any name to ARC. 937 mService.getAudioManager().setWiredDeviceConnectionState( 938 AudioSystem.DEVICE_OUT_HDMI_ARC, 939 enabled ? 1 : 0, "", ""); 940 } 941 942 /** 943 * Returns true if ARC is currently established on a certain port. 944 */ 945 @ServiceThreadOnly 946 boolean isArcEstablished() { 947 assertRunOnServiceThread(); 948 if (mArcEstablished) { 949 for (int i = 0; i < mArcFeatureEnabled.size(); i++) { 950 if (mArcFeatureEnabled.valueAt(i)) return true; 951 } 952 } 953 return false; 954 } 955 956 @ServiceThreadOnly 957 void changeArcFeatureEnabled(int portId, boolean enabled) { 958 assertRunOnServiceThread(); 959 960 if (mArcFeatureEnabled.get(portId) != enabled) { 961 mArcFeatureEnabled.put(portId, enabled); 962 if (enabled) { 963 if (!mArcEstablished) { 964 startArcAction(true); 965 } 966 } else { 967 if (mArcEstablished) { 968 startArcAction(false); 969 } 970 } 971 } 972 } 973 974 @ServiceThreadOnly 975 boolean isArcFeatureEnabled(int portId) { 976 assertRunOnServiceThread(); 977 return mArcFeatureEnabled.get(portId); 978 } 979 980 @ServiceThreadOnly 981 void startArcAction(boolean enabled) { 982 assertRunOnServiceThread(); 983 HdmiDeviceInfo info = getAvrDeviceInfo(); 984 if (info == null) { 985 Slog.w(TAG, "Failed to start arc action; No AVR device."); 986 return; 987 } 988 if (!canStartArcUpdateAction(info.getLogicalAddress(), enabled)) { 989 Slog.w(TAG, "Failed to start arc action; ARC configuration check failed."); 990 if (enabled && !isConnectedToArcPort(info.getPhysicalAddress())) { 991 displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT); 992 } 993 return; 994 } 995 996 // Terminate opposite action and start action if not exist. 997 if (enabled) { 998 removeAction(RequestArcTerminationAction.class); 999 if (!hasAction(RequestArcInitiationAction.class)) { 1000 addAndStartAction(new RequestArcInitiationAction(this, info.getLogicalAddress())); 1001 } 1002 } else { 1003 removeAction(RequestArcInitiationAction.class); 1004 if (!hasAction(RequestArcTerminationAction.class)) { 1005 addAndStartAction(new RequestArcTerminationAction(this, info.getLogicalAddress())); 1006 } 1007 } 1008 } 1009 1010 private boolean isDirectConnectAddress(int physicalAddress) { 1011 return (physicalAddress & Constants.ROUTING_PATH_TOP_MASK) == physicalAddress; 1012 } 1013 1014 void setAudioStatus(boolean mute, int volume) { 1015 synchronized (mLock) { 1016 mSystemAudioMute = mute; 1017 mSystemAudioVolume = volume; 1018 int maxVolume = mService.getAudioManager().getStreamMaxVolume( 1019 AudioManager.STREAM_MUSIC); 1020 mService.setAudioStatus(mute, 1021 VolumeControlAction.scaleToCustomVolume(volume, maxVolume)); 1022 displayOsd(HdmiControlManager.OSD_MESSAGE_AVR_VOLUME_CHANGED, 1023 mute ? HdmiControlManager.AVR_VOLUME_MUTED : volume); 1024 } 1025 } 1026 1027 @ServiceThreadOnly 1028 void changeVolume(int curVolume, int delta, int maxVolume) { 1029 assertRunOnServiceThread(); 1030 if (delta == 0 || !isSystemAudioActivated()) { 1031 return; 1032 } 1033 1034 int targetVolume = curVolume + delta; 1035 int cecVolume = VolumeControlAction.scaleToCecVolume(targetVolume, maxVolume); 1036 synchronized (mLock) { 1037 // If new volume is the same as current system audio volume, just ignore it. 1038 // Note that UNKNOWN_VOLUME is not in range of cec volume scale. 1039 if (cecVolume == mSystemAudioVolume) { 1040 // Update tv volume with system volume value. 1041 mService.setAudioStatus(false, 1042 VolumeControlAction.scaleToCustomVolume(mSystemAudioVolume, maxVolume)); 1043 return; 1044 } 1045 } 1046 1047 List<VolumeControlAction> actions = getActions(VolumeControlAction.class); 1048 if (actions.isEmpty()) { 1049 addAndStartAction(new VolumeControlAction(this, 1050 getAvrDeviceInfo().getLogicalAddress(), delta > 0)); 1051 } else { 1052 actions.get(0).handleVolumeChange(delta > 0); 1053 } 1054 } 1055 1056 @ServiceThreadOnly 1057 void changeMute(boolean mute) { 1058 assertRunOnServiceThread(); 1059 HdmiLogger.debug("[A]:Change mute:%b", mute); 1060 synchronized (mLock) { 1061 if (mSystemAudioMute == mute) { 1062 HdmiLogger.debug("No need to change mute."); 1063 return; 1064 } 1065 } 1066 if (!isSystemAudioActivated()) { 1067 HdmiLogger.debug("[A]:System audio is not activated."); 1068 return; 1069 } 1070 1071 // Remove existing volume action. 1072 removeAction(VolumeControlAction.class); 1073 sendUserControlPressedAndReleased(getAvrDeviceInfo().getLogicalAddress(), 1074 mute ? HdmiCecKeycode.CEC_KEYCODE_MUTE_FUNCTION : 1075 HdmiCecKeycode.CEC_KEYCODE_RESTORE_VOLUME_FUNCTION); 1076 } 1077 1078 @Override 1079 @ServiceThreadOnly 1080 protected boolean handleInitiateArc(HdmiCecMessage message) { 1081 assertRunOnServiceThread(); 1082 1083 if (!canStartArcUpdateAction(message.getSource(), true)) { 1084 if (getAvrDeviceInfo() == null) { 1085 // AVR may not have been discovered yet. Delay the message processing. 1086 mDelayedMessageBuffer.add(message); 1087 return true; 1088 } 1089 mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); 1090 if (!isConnectedToArcPort(message.getSource())) { 1091 displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT); 1092 } 1093 return true; 1094 } 1095 1096 // In case where <Initiate Arc> is started by <Request ARC Initiation> 1097 // need to clean up RequestArcInitiationAction. 1098 removeAction(RequestArcInitiationAction.class); 1099 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 1100 message.getSource(), true); 1101 addAndStartAction(action); 1102 return true; 1103 } 1104 1105 private boolean canStartArcUpdateAction(int avrAddress, boolean shouldCheckArcFeatureEnabled) { 1106 HdmiDeviceInfo avr = getAvrDeviceInfo(); 1107 if (avr != null 1108 && (avrAddress == avr.getLogicalAddress()) 1109 && isConnectedToArcPort(avr.getPhysicalAddress()) 1110 && isDirectConnectAddress(avr.getPhysicalAddress())) { 1111 if (shouldCheckArcFeatureEnabled) { 1112 return isArcFeatureEnabled(avr.getPortId()); 1113 } else { 1114 return true; 1115 } 1116 } else { 1117 return false; 1118 } 1119 } 1120 1121 @Override 1122 @ServiceThreadOnly 1123 protected boolean handleTerminateArc(HdmiCecMessage message) { 1124 assertRunOnServiceThread(); 1125 if (mService .isPowerStandbyOrTransient()) { 1126 setArcStatus(false); 1127 return true; 1128 } 1129 // Do not check ARC configuration since the AVR might have been already removed. 1130 // Clean up RequestArcTerminationAction in case <Terminate Arc> was started by 1131 // <Request ARC Termination>. 1132 removeAction(RequestArcTerminationAction.class); 1133 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 1134 message.getSource(), false); 1135 addAndStartAction(action); 1136 return true; 1137 } 1138 1139 @Override 1140 @ServiceThreadOnly 1141 protected boolean handleSetSystemAudioMode(HdmiCecMessage message) { 1142 assertRunOnServiceThread(); 1143 if (!isMessageForSystemAudio(message)) { 1144 if (getAvrDeviceInfo() == null) { 1145 // AVR may not have been discovered yet. Delay the message processing. 1146 mDelayedMessageBuffer.add(message); 1147 return true; 1148 } 1149 HdmiLogger.warning("Invalid <Set System Audio Mode> message:" + message); 1150 mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); 1151 return true; 1152 } 1153 SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this, 1154 message.getSource(), HdmiUtils.parseCommandParamSystemAudioStatus(message), null); 1155 addAndStartAction(action); 1156 return true; 1157 } 1158 1159 @Override 1160 @ServiceThreadOnly 1161 protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) { 1162 assertRunOnServiceThread(); 1163 if (!isMessageForSystemAudio(message)) { 1164 HdmiLogger.warning("Invalid <System Audio Mode Status> message:" + message); 1165 // Ignore this message. 1166 return true; 1167 } 1168 setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message), true); 1169 return true; 1170 } 1171 1172 // Seq #53 1173 @Override 1174 @ServiceThreadOnly 1175 protected boolean handleRecordTvScreen(HdmiCecMessage message) { 1176 List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class); 1177 if (!actions.isEmpty()) { 1178 // Assumes only one OneTouchRecordAction. 1179 OneTouchRecordAction action = actions.get(0); 1180 if (action.getRecorderAddress() != message.getSource()) { 1181 announceOneTouchRecordResult( 1182 message.getSource(), 1183 HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS); 1184 } 1185 return super.handleRecordTvScreen(message); 1186 } 1187 1188 int recorderAddress = message.getSource(); 1189 byte[] recordSource = mService.invokeRecordRequestListener(recorderAddress); 1190 int reason = startOneTouchRecord(recorderAddress, recordSource); 1191 if (reason != Constants.ABORT_NO_ERROR) { 1192 mService.maySendFeatureAbortCommand(message, reason); 1193 } 1194 return true; 1195 } 1196 1197 @Override 1198 protected boolean handleTimerClearedStatus(HdmiCecMessage message) { 1199 byte[] params = message.getParams(); 1200 int timerClearedStatusData = params[0] & 0xFF; 1201 announceTimerRecordingResult(message.getSource(), timerClearedStatusData); 1202 return true; 1203 } 1204 1205 void announceOneTouchRecordResult(int recorderAddress, int result) { 1206 mService.invokeOneTouchRecordResult(recorderAddress, result); 1207 } 1208 1209 void announceTimerRecordingResult(int recorderAddress, int result) { 1210 mService.invokeTimerRecordingResult(recorderAddress, result); 1211 } 1212 1213 void announceClearTimerRecordingResult(int recorderAddress, int result) { 1214 mService.invokeClearTimerRecordingResult(recorderAddress, result); 1215 } 1216 1217 private boolean isMessageForSystemAudio(HdmiCecMessage message) { 1218 return mService.isControlEnabled() 1219 && message.getSource() == Constants.ADDR_AUDIO_SYSTEM 1220 && (message.getDestination() == Constants.ADDR_TV 1221 || message.getDestination() == Constants.ADDR_BROADCAST) 1222 && getAvrDeviceInfo() != null; 1223 } 1224 1225 /** 1226 * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same 1227 * logical address as new device info's. 1228 * 1229 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 1230 * 1231 * @param deviceInfo a new {@link HdmiDeviceInfo} to be added. 1232 * @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo} 1233 * that has the same logical address as new one has. 1234 */ 1235 @ServiceThreadOnly 1236 private HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) { 1237 assertRunOnServiceThread(); 1238 HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress()); 1239 if (oldDeviceInfo != null) { 1240 removeDeviceInfo(deviceInfo.getId()); 1241 } 1242 mDeviceInfos.append(deviceInfo.getId(), deviceInfo); 1243 updateSafeDeviceInfoList(); 1244 return oldDeviceInfo; 1245 } 1246 1247 /** 1248 * Remove a device info corresponding to the given {@code logicalAddress}. 1249 * It returns removed {@link HdmiDeviceInfo} if exists. 1250 * 1251 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 1252 * 1253 * @param id id of device to be removed 1254 * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null} 1255 */ 1256 @ServiceThreadOnly 1257 private HdmiDeviceInfo removeDeviceInfo(int id) { 1258 assertRunOnServiceThread(); 1259 HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id); 1260 if (deviceInfo != null) { 1261 mDeviceInfos.remove(id); 1262 } 1263 updateSafeDeviceInfoList(); 1264 return deviceInfo; 1265 } 1266 1267 /** 1268 * Return a list of all {@link HdmiDeviceInfo}. 1269 * 1270 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 1271 * This is not thread-safe. For thread safety, call {@link #getSafeExternalInputsLocked} which 1272 * does not include local device. 1273 */ 1274 @ServiceThreadOnly 1275 List<HdmiDeviceInfo> getDeviceInfoList(boolean includeLocalDevice) { 1276 assertRunOnServiceThread(); 1277 if (includeLocalDevice) { 1278 return HdmiUtils.sparseArrayToList(mDeviceInfos); 1279 } else { 1280 ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>(); 1281 for (int i = 0; i < mDeviceInfos.size(); ++i) { 1282 HdmiDeviceInfo info = mDeviceInfos.valueAt(i); 1283 if (!isLocalDeviceAddress(info.getLogicalAddress())) { 1284 infoList.add(info); 1285 } 1286 } 1287 return infoList; 1288 } 1289 } 1290 1291 /** 1292 * Return external input devices. 1293 */ 1294 List<HdmiDeviceInfo> getSafeExternalInputsLocked() { 1295 return mSafeExternalInputs; 1296 } 1297 1298 @ServiceThreadOnly 1299 private void updateSafeDeviceInfoList() { 1300 assertRunOnServiceThread(); 1301 List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos); 1302 List<HdmiDeviceInfo> externalInputs = getInputDevices(); 1303 synchronized (mLock) { 1304 mSafeAllDeviceInfos = copiedDevices; 1305 mSafeExternalInputs = externalInputs; 1306 } 1307 } 1308 1309 /** 1310 * Return a list of external cec input (source) devices. 1311 * 1312 * <p>Note that this effectively excludes non-source devices like system audio, 1313 * secondary TV. 1314 */ 1315 private List<HdmiDeviceInfo> getInputDevices() { 1316 ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>(); 1317 for (int i = 0; i < mDeviceInfos.size(); ++i) { 1318 HdmiDeviceInfo info = mDeviceInfos.valueAt(i); 1319 if (isLocalDeviceAddress(info.getLogicalAddress())) { 1320 continue; 1321 } 1322 if (info.isSourceType() && !hideDevicesBehindLegacySwitch(info)) { 1323 infoList.add(info); 1324 } 1325 } 1326 return infoList; 1327 } 1328 1329 // Check if we are hiding CEC devices connected to a legacy (non-CEC) switch. 1330 // Returns true if the policy is set to true, and the device to check does not have 1331 // a parent CEC device (which should be the CEC-enabled switch) in the list. 1332 private boolean hideDevicesBehindLegacySwitch(HdmiDeviceInfo info) { 1333 return HdmiConfig.HIDE_DEVICES_BEHIND_LEGACY_SWITCH 1334 && !isConnectedToCecSwitch(info.getPhysicalAddress(), mCecSwitches); 1335 } 1336 1337 private static boolean isConnectedToCecSwitch(int path, Collection<Integer> switches) { 1338 for (int switchPath : switches) { 1339 if (isParentPath(switchPath, path)) { 1340 return true; 1341 } 1342 } 1343 return false; 1344 } 1345 1346 private static boolean isParentPath(int parentPath, int childPath) { 1347 // (A000, AB00) (AB00, ABC0), (ABC0, ABCD) 1348 // If child's last non-zero nibble is removed, the result equals to the parent. 1349 for (int i = 0; i <= 12; i += 4) { 1350 int nibble = (childPath >> i) & 0xF; 1351 if (nibble != 0) { 1352 int parentNibble = (parentPath >> i) & 0xF; 1353 return parentNibble == 0 && (childPath >> i+4) == (parentPath >> i+4); 1354 } 1355 } 1356 return false; 1357 } 1358 1359 private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) { 1360 if (!hideDevicesBehindLegacySwitch(info)) { 1361 mService.invokeDeviceEventListeners(info, status); 1362 } 1363 } 1364 1365 private boolean isLocalDeviceAddress(int address) { 1366 return mLocalDeviceAddresses.contains(address); 1367 } 1368 1369 @ServiceThreadOnly 1370 HdmiDeviceInfo getAvrDeviceInfo() { 1371 assertRunOnServiceThread(); 1372 return getCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); 1373 } 1374 1375 /** 1376 * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}. 1377 * 1378 * This is not thread-safe. For thread safety, call {@link #getSafeCecDeviceInfo(int)}. 1379 * 1380 * @param logicalAddress logical address of the device to be retrieved 1381 * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}. 1382 * Returns null if no logical address matched 1383 */ 1384 @ServiceThreadOnly 1385 HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) { 1386 assertRunOnServiceThread(); 1387 return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress)); 1388 } 1389 1390 boolean hasSystemAudioDevice() { 1391 return getSafeAvrDeviceInfo() != null; 1392 } 1393 1394 HdmiDeviceInfo getSafeAvrDeviceInfo() { 1395 return getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); 1396 } 1397 1398 /** 1399 * Thread safe version of {@link #getCecDeviceInfo(int)}. 1400 * 1401 * @param logicalAddress logical address to be retrieved 1402 * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}. 1403 * Returns null if no logical address matched 1404 */ 1405 HdmiDeviceInfo getSafeCecDeviceInfo(int logicalAddress) { 1406 synchronized (mLock) { 1407 for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { 1408 if (info.isCecDevice() && info.getLogicalAddress() == logicalAddress) { 1409 return info; 1410 } 1411 } 1412 return null; 1413 } 1414 } 1415 1416 List<HdmiDeviceInfo> getSafeCecDevicesLocked() { 1417 ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>(); 1418 for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { 1419 if (isLocalDeviceAddress(info.getLogicalAddress())) { 1420 continue; 1421 } 1422 infoList.add(info); 1423 } 1424 return infoList; 1425 } 1426 1427 /** 1428 * Called when a device is newly added or a new device is detected or 1429 * existing device is updated. 1430 * 1431 * @param info device info of a new device. 1432 */ 1433 @ServiceThreadOnly 1434 final void addCecDevice(HdmiDeviceInfo info) { 1435 assertRunOnServiceThread(); 1436 HdmiDeviceInfo old = addDeviceInfo(info); 1437 if (info.getLogicalAddress() == mAddress) { 1438 // The addition of TV device itself should not be notified. 1439 return; 1440 } 1441 if (old == null) { 1442 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE); 1443 } else if (!old.equals(info)) { 1444 invokeDeviceEventListener(old, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); 1445 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE); 1446 } 1447 } 1448 1449 /** 1450 * Called when a device is removed or removal of device is detected. 1451 * 1452 * @param address a logical address of a device to be removed 1453 */ 1454 @ServiceThreadOnly 1455 final void removeCecDevice(int address) { 1456 assertRunOnServiceThread(); 1457 HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address)); 1458 1459 mCecMessageCache.flushMessagesFrom(address); 1460 invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); 1461 } 1462 1463 @ServiceThreadOnly 1464 void handleRemoveActiveRoutingPath(int path) { 1465 assertRunOnServiceThread(); 1466 // Seq #23 1467 if (isTailOfActivePath(path, getActivePath())) { 1468 int newPath = mService.portIdToPath(getActivePortId()); 1469 startRoutingControl(getActivePath(), newPath, true, null); 1470 } 1471 } 1472 1473 /** 1474 * Launch routing control process. 1475 * 1476 * @param routingForBootup true if routing control is initiated due to One Touch Play 1477 * or TV power on 1478 */ 1479 @ServiceThreadOnly 1480 void launchRoutingControl(boolean routingForBootup) { 1481 assertRunOnServiceThread(); 1482 // Seq #24 1483 if (getActivePortId() != Constants.INVALID_PORT_ID) { 1484 if (!routingForBootup && !isProhibitMode()) { 1485 int newPath = mService.portIdToPath(getActivePortId()); 1486 setActivePath(newPath); 1487 startRoutingControl(getActivePath(), newPath, routingForBootup, null); 1488 } 1489 } else { 1490 int activePath = mService.getPhysicalAddress(); 1491 setActivePath(activePath); 1492 if (!routingForBootup 1493 && !mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) { 1494 mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(mAddress, 1495 activePath)); 1496 } 1497 } 1498 } 1499 1500 /** 1501 * Returns the {@link HdmiDeviceInfo} instance whose physical address matches 1502 * the given routing path. CEC devices use routing path for its physical address to 1503 * describe the hierarchy of the devices in the network. 1504 * 1505 * @param path routing path or physical address 1506 * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null 1507 */ 1508 @ServiceThreadOnly 1509 final HdmiDeviceInfo getDeviceInfoByPath(int path) { 1510 assertRunOnServiceThread(); 1511 for (HdmiDeviceInfo info : getDeviceInfoList(false)) { 1512 if (info.getPhysicalAddress() == path) { 1513 return info; 1514 } 1515 } 1516 return null; 1517 } 1518 1519 /** 1520 * Returns the {@link HdmiDeviceInfo} instance whose physical address matches 1521 * the given routing path. This is the version accessible safely from threads 1522 * other than service thread. 1523 * 1524 * @param path routing path or physical address 1525 * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null 1526 */ 1527 HdmiDeviceInfo getSafeDeviceInfoByPath(int path) { 1528 synchronized (mLock) { 1529 for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { 1530 if (info.getPhysicalAddress() == path) { 1531 return info; 1532 } 1533 } 1534 return null; 1535 } 1536 } 1537 1538 /** 1539 * Whether a device of the specified physical address and logical address exists 1540 * in a device info list. However, both are minimal condition and it could 1541 * be different device from the original one. 1542 * 1543 * @param logicalAddress logical address of a device to be searched 1544 * @param physicalAddress physical address of a device to be searched 1545 * @return true if exist; otherwise false 1546 */ 1547 @ServiceThreadOnly 1548 boolean isInDeviceList(int logicalAddress, int physicalAddress) { 1549 assertRunOnServiceThread(); 1550 HdmiDeviceInfo device = getCecDeviceInfo(logicalAddress); 1551 if (device == null) { 1552 return false; 1553 } 1554 return device.getPhysicalAddress() == physicalAddress; 1555 } 1556 1557 @Override 1558 @ServiceThreadOnly 1559 void onHotplug(int portId, boolean connected) { 1560 assertRunOnServiceThread(); 1561 1562 if (!connected) { 1563 removeCecSwitches(portId); 1564 } 1565 // Tv device will have permanent HotplugDetectionAction. 1566 List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class); 1567 if (!hotplugActions.isEmpty()) { 1568 // Note that hotplug action is single action running on a machine. 1569 // "pollAllDevicesNow" cleans up timer and start poll action immediately. 1570 // It covers seq #40, #43. 1571 hotplugActions.get(0).pollAllDevicesNow(); 1572 } 1573 updateArcFeatureStatus(portId, connected); 1574 } 1575 1576 private void removeCecSwitches(int portId) { 1577 Iterator<Integer> it = mCecSwitches.iterator(); 1578 while (!it.hasNext()) { 1579 int path = it.next(); 1580 if (pathToPortId(path) == portId) { 1581 it.remove(); 1582 } 1583 } 1584 } 1585 1586 @Override 1587 @ServiceThreadOnly 1588 void setAutoDeviceOff(boolean enabled) { 1589 assertRunOnServiceThread(); 1590 mAutoDeviceOff = enabled; 1591 } 1592 1593 @ServiceThreadOnly 1594 void setAutoWakeup(boolean enabled) { 1595 assertRunOnServiceThread(); 1596 mAutoWakeup = enabled; 1597 } 1598 1599 @ServiceThreadOnly 1600 boolean getAutoWakeup() { 1601 assertRunOnServiceThread(); 1602 return mAutoWakeup; 1603 } 1604 1605 @Override 1606 @ServiceThreadOnly 1607 protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) { 1608 assertRunOnServiceThread(); 1609 mService.unregisterTvInputCallback(mTvInputCallback); 1610 // Remove any repeated working actions. 1611 // HotplugDetectionAction will be reinstated during the wake up process. 1612 // HdmiControlService.onWakeUp() -> initializeLocalDevices() -> 1613 // LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery(). 1614 removeAction(DeviceDiscoveryAction.class); 1615 removeAction(HotplugDetectionAction.class); 1616 removeAction(PowerStatusMonitorAction.class); 1617 // Remove recording actions. 1618 removeAction(OneTouchRecordAction.class); 1619 removeAction(TimerRecordingAction.class); 1620 1621 disableSystemAudioIfExist(); 1622 disableArcIfExist(); 1623 1624 super.disableDevice(initiatedByCec, callback); 1625 clearDeviceInfoList(); 1626 getActiveSource().invalidate(); 1627 setActivePath(Constants.INVALID_PHYSICAL_ADDRESS); 1628 checkIfPendingActionsCleared(); 1629 } 1630 1631 @ServiceThreadOnly 1632 private void disableSystemAudioIfExist() { 1633 assertRunOnServiceThread(); 1634 if (getAvrDeviceInfo() == null) { 1635 return; 1636 } 1637 1638 // Seq #31. 1639 removeAction(SystemAudioActionFromAvr.class); 1640 removeAction(SystemAudioActionFromTv.class); 1641 removeAction(SystemAudioAutoInitiationAction.class); 1642 removeAction(SystemAudioStatusAction.class); 1643 removeAction(VolumeControlAction.class); 1644 } 1645 1646 @ServiceThreadOnly 1647 private void disableArcIfExist() { 1648 assertRunOnServiceThread(); 1649 HdmiDeviceInfo avr = getAvrDeviceInfo(); 1650 if (avr == null) { 1651 return; 1652 } 1653 1654 // Seq #44. 1655 removeAction(RequestArcInitiationAction.class); 1656 if (!hasAction(RequestArcTerminationAction.class) && isArcEstablished()) { 1657 addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress())); 1658 } 1659 } 1660 1661 @Override 1662 @ServiceThreadOnly 1663 protected void onStandby(boolean initiatedByCec, int standbyAction) { 1664 assertRunOnServiceThread(); 1665 // Seq #11 1666 if (!mService.isControlEnabled()) { 1667 return; 1668 } 1669 if (!initiatedByCec && mAutoDeviceOff) { 1670 mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby( 1671 mAddress, Constants.ADDR_BROADCAST)); 1672 } 1673 } 1674 1675 boolean isProhibitMode() { 1676 return mService.isProhibitMode(); 1677 } 1678 1679 boolean isPowerStandbyOrTransient() { 1680 return mService.isPowerStandbyOrTransient(); 1681 } 1682 1683 @ServiceThreadOnly 1684 void displayOsd(int messageId) { 1685 assertRunOnServiceThread(); 1686 mService.displayOsd(messageId); 1687 } 1688 1689 @ServiceThreadOnly 1690 void displayOsd(int messageId, int extra) { 1691 assertRunOnServiceThread(); 1692 mService.displayOsd(messageId, extra); 1693 } 1694 1695 // Seq #54 and #55 1696 @ServiceThreadOnly 1697 int startOneTouchRecord(int recorderAddress, byte[] recordSource) { 1698 assertRunOnServiceThread(); 1699 if (!mService.isControlEnabled()) { 1700 Slog.w(TAG, "Can not start one touch record. CEC control is disabled."); 1701 announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED); 1702 return Constants.ABORT_NOT_IN_CORRECT_MODE; 1703 } 1704 1705 if (!checkRecorder(recorderAddress)) { 1706 Slog.w(TAG, "Invalid recorder address:" + recorderAddress); 1707 announceOneTouchRecordResult(recorderAddress, 1708 ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION); 1709 return Constants.ABORT_NOT_IN_CORRECT_MODE; 1710 } 1711 1712 if (!checkRecordSource(recordSource)) { 1713 Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource)); 1714 announceOneTouchRecordResult(recorderAddress, 1715 ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN); 1716 return Constants.ABORT_CANNOT_PROVIDE_SOURCE; 1717 } 1718 1719 addAndStartAction(new OneTouchRecordAction(this, recorderAddress, recordSource)); 1720 Slog.i(TAG, "Start new [One Touch Record]-Target:" + recorderAddress + ", recordSource:" 1721 + Arrays.toString(recordSource)); 1722 return Constants.ABORT_NO_ERROR; 1723 } 1724 1725 @ServiceThreadOnly 1726 void stopOneTouchRecord(int recorderAddress) { 1727 assertRunOnServiceThread(); 1728 if (!mService.isControlEnabled()) { 1729 Slog.w(TAG, "Can not stop one touch record. CEC control is disabled."); 1730 announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED); 1731 return; 1732 } 1733 1734 if (!checkRecorder(recorderAddress)) { 1735 Slog.w(TAG, "Invalid recorder address:" + recorderAddress); 1736 announceOneTouchRecordResult(recorderAddress, 1737 ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION); 1738 return; 1739 } 1740 1741 // Remove one touch record action so that other one touch record can be started. 1742 removeAction(OneTouchRecordAction.class); 1743 mService.sendCecCommand(HdmiCecMessageBuilder.buildRecordOff(mAddress, recorderAddress)); 1744 Slog.i(TAG, "Stop [One Touch Record]-Target:" + recorderAddress); 1745 } 1746 1747 private boolean checkRecorder(int recorderAddress) { 1748 HdmiDeviceInfo device = getCecDeviceInfo(recorderAddress); 1749 return (device != null) 1750 && (HdmiUtils.getTypeFromAddress(recorderAddress) 1751 == HdmiDeviceInfo.DEVICE_RECORDER); 1752 } 1753 1754 private boolean checkRecordSource(byte[] recordSource) { 1755 return (recordSource != null) && HdmiRecordSources.checkRecordSource(recordSource); 1756 } 1757 1758 @ServiceThreadOnly 1759 void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) { 1760 assertRunOnServiceThread(); 1761 if (!mService.isControlEnabled()) { 1762 Slog.w(TAG, "Can not start one touch record. CEC control is disabled."); 1763 announceTimerRecordingResult(recorderAddress, 1764 TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED); 1765 return; 1766 } 1767 1768 if (!checkRecorder(recorderAddress)) { 1769 Slog.w(TAG, "Invalid recorder address:" + recorderAddress); 1770 announceTimerRecordingResult(recorderAddress, 1771 TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION); 1772 return; 1773 } 1774 1775 if (!checkTimerRecordingSource(sourceType, recordSource)) { 1776 Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource)); 1777 announceTimerRecordingResult( 1778 recorderAddress, 1779 TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE); 1780 return; 1781 } 1782 1783 addAndStartAction( 1784 new TimerRecordingAction(this, recorderAddress, sourceType, recordSource)); 1785 Slog.i(TAG, "Start [Timer Recording]-Target:" + recorderAddress + ", SourceType:" 1786 + sourceType + ", RecordSource:" + Arrays.toString(recordSource)); 1787 } 1788 1789 private boolean checkTimerRecordingSource(int sourceType, byte[] recordSource) { 1790 return (recordSource != null) 1791 && HdmiTimerRecordSources.checkTimerRecordSource(sourceType, recordSource); 1792 } 1793 1794 @ServiceThreadOnly 1795 void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) { 1796 assertRunOnServiceThread(); 1797 if (!mService.isControlEnabled()) { 1798 Slog.w(TAG, "Can not start one touch record. CEC control is disabled."); 1799 announceClearTimerRecordingResult(recorderAddress, CLEAR_TIMER_STATUS_CEC_DISABLE); 1800 return; 1801 } 1802 1803 if (!checkRecorder(recorderAddress)) { 1804 Slog.w(TAG, "Invalid recorder address:" + recorderAddress); 1805 announceClearTimerRecordingResult(recorderAddress, 1806 CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION); 1807 return; 1808 } 1809 1810 if (!checkTimerRecordingSource(sourceType, recordSource)) { 1811 Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource)); 1812 announceClearTimerRecordingResult(recorderAddress, 1813 CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE); 1814 return; 1815 } 1816 1817 sendClearTimerMessage(recorderAddress, sourceType, recordSource); 1818 } 1819 1820 private void sendClearTimerMessage(final int recorderAddress, int sourceType, 1821 byte[] recordSource) { 1822 HdmiCecMessage message = null; 1823 switch (sourceType) { 1824 case TIMER_RECORDING_TYPE_DIGITAL: 1825 message = HdmiCecMessageBuilder.buildClearDigitalTimer(mAddress, recorderAddress, 1826 recordSource); 1827 break; 1828 case TIMER_RECORDING_TYPE_ANALOGUE: 1829 message = HdmiCecMessageBuilder.buildClearAnalogueTimer(mAddress, recorderAddress, 1830 recordSource); 1831 break; 1832 case TIMER_RECORDING_TYPE_EXTERNAL: 1833 message = HdmiCecMessageBuilder.buildClearExternalTimer(mAddress, recorderAddress, 1834 recordSource); 1835 break; 1836 default: 1837 Slog.w(TAG, "Invalid source type:" + recorderAddress); 1838 announceClearTimerRecordingResult(recorderAddress, 1839 CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE); 1840 return; 1841 1842 } 1843 mService.sendCecCommand(message, new SendMessageCallback() { 1844 @Override 1845 public void onSendCompleted(int error) { 1846 if (error != Constants.SEND_RESULT_SUCCESS) { 1847 announceClearTimerRecordingResult(recorderAddress, 1848 CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE); 1849 } 1850 } 1851 }); 1852 } 1853 1854 void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) { 1855 HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress); 1856 if (info == null) { 1857 Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress); 1858 return; 1859 } 1860 1861 if (info.getDevicePowerStatus() == newPowerStatus) { 1862 return; 1863 } 1864 1865 HdmiDeviceInfo newInfo = HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus); 1866 // addDeviceInfo replaces old device info with new one if exists. 1867 addDeviceInfo(newInfo); 1868 1869 invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE); 1870 } 1871 1872 @Override 1873 protected boolean handleMenuStatus(HdmiCecMessage message) { 1874 // Do nothing and just return true not to prevent from responding <Feature Abort>. 1875 return true; 1876 } 1877 1878 @Override 1879 protected void sendStandby(int deviceId) { 1880 HdmiDeviceInfo targetDevice = mDeviceInfos.get(deviceId); 1881 if (targetDevice == null) { 1882 return; 1883 } 1884 int targetAddress = targetDevice.getLogicalAddress(); 1885 mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress)); 1886 } 1887 1888 @ServiceThreadOnly 1889 void processAllDelayedMessages() { 1890 assertRunOnServiceThread(); 1891 mDelayedMessageBuffer.processAllMessages(); 1892 } 1893 1894 @ServiceThreadOnly 1895 void processDelayedMessages(int address) { 1896 assertRunOnServiceThread(); 1897 mDelayedMessageBuffer.processMessagesForDevice(address); 1898 } 1899 1900 @ServiceThreadOnly 1901 void processDelayedActiveSource(int address) { 1902 assertRunOnServiceThread(); 1903 mDelayedMessageBuffer.processActiveSource(address); 1904 } 1905 1906 @Override 1907 protected void dump(final IndentingPrintWriter pw) { 1908 super.dump(pw); 1909 pw.println("mArcEstablished: " + mArcEstablished); 1910 pw.println("mArcFeatureEnabled: " + mArcFeatureEnabled); 1911 pw.println("mSystemAudioActivated: " + mSystemAudioActivated); 1912 pw.println("mSystemAudioMute: " + mSystemAudioMute); 1913 pw.println("mAutoDeviceOff: " + mAutoDeviceOff); 1914 pw.println("mAutoWakeup: " + mAutoWakeup); 1915 pw.println("mSkipRoutingControl: " + mSkipRoutingControl); 1916 pw.println("mPrevPortId: " + mPrevPortId); 1917 pw.println("CEC devices:"); 1918 pw.increaseIndent(); 1919 for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { 1920 pw.println(info); 1921 } 1922 pw.decreaseIndent(); 1923 } 1924 } 1925