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