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