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.DeviceFeatures.FEATURE_NOT_SUPPORTED; 20 import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED; 21 import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CEC_DISABLE; 22 import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION; 23 import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE; 24 import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CEC_DISABLED; 25 import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION; 26 import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN; 27 import static android.hardware.hdmi.HdmiControlManager.OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT; 28 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED; 29 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION; 30 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE; 31 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_ANALOGUE; 32 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL; 33 import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL; 34 35 import android.annotation.Nullable; 36 import android.hardware.hdmi.DeviceFeatures; 37 import android.hardware.hdmi.HdmiControlManager; 38 import android.hardware.hdmi.HdmiDeviceInfo; 39 import android.hardware.hdmi.HdmiPortInfo; 40 import android.hardware.hdmi.HdmiRecordSources; 41 import android.hardware.hdmi.HdmiTimerRecordSources; 42 import android.hardware.hdmi.IHdmiControlCallback; 43 import android.hardware.tv.cec.V1_0.SendMessageResult; 44 import android.media.AudioDescriptor; 45 import android.media.AudioDeviceAttributes; 46 import android.media.AudioDeviceInfo; 47 import android.media.AudioProfile; 48 import android.media.tv.TvInputInfo; 49 import android.media.tv.TvInputManager.TvInputCallback; 50 import android.util.Slog; 51 import android.util.SparseBooleanArray; 52 53 import com.android.internal.annotations.GuardedBy; 54 import com.android.internal.annotations.VisibleForTesting; 55 import com.android.internal.util.IndentingPrintWriter; 56 import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback; 57 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; 58 import com.android.server.hdmi.HdmiControlService.SendMessageCallback; 59 60 import java.util.ArrayList; 61 import java.util.Arrays; 62 import java.util.HashMap; 63 import java.util.List; 64 import java.util.stream.Collectors; 65 66 /** 67 * Represent a logical device of type TV residing in Android system. 68 */ 69 final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { 70 private static final String TAG = "HdmiCecLocalDeviceTv"; 71 72 // Whether ARC is available or not. "true" means that ARC is established between TV and 73 // AVR as audio receiver. 74 @ServiceThreadOnly 75 private boolean mArcEstablished = false; 76 77 // Stores whether ARC feature is enabled per port. 78 // True by default for all the ARC-enabled ports. 79 private final SparseBooleanArray mArcFeatureEnabled = new SparseBooleanArray(); 80 81 // Whether the System Audio Control feature is enabled or not. True by default. 82 @GuardedBy("mLock") 83 private boolean mSystemAudioControlFeatureEnabled; 84 85 // The previous port id (input) before switching to the new one. This is remembered in order to 86 // be able to switch to it upon receiving <Inactive Source> from currently active source. 87 // This remains valid only when the active source was switched via one touch play operation 88 // (either by TV or source device). Manual port switching invalidates this value to 89 // Constants.PORT_INVALID, for which case <Inactive Source> does not do anything. 90 @GuardedBy("mLock") 91 private int mPrevPortId; 92 93 @GuardedBy("mLock") 94 private int mSystemAudioVolume = Constants.UNKNOWN_VOLUME; 95 96 @GuardedBy("mLock") 97 private boolean mSystemAudioMute = false; 98 99 // If true, do not do routing control/send active source for internal source. 100 // Set to true when the device was woken up by <Text/Image View On>. 101 private boolean mSkipRoutingControl; 102 103 // Message buffer used to buffer selected messages to process later. <Active Source> 104 // from a source device, for instance, needs to be buffered if the device is not 105 // discovered yet. The buffered commands are taken out and when they are ready to 106 // handle. 107 private final DelayedMessageBuffer mDelayedMessageBuffer = new DelayedMessageBuffer(this); 108 109 // Defines the callback invoked when TV input framework is updated with input status. 110 // We are interested in the notification for HDMI input addition event, in order to 111 // process any CEC commands that arrived before the input is added. 112 private final TvInputCallback mTvInputCallback = new TvInputCallback() { 113 @Override 114 public void onInputAdded(String inputId) { 115 TvInputInfo tvInfo = mService.getTvInputManager().getTvInputInfo(inputId); 116 if (tvInfo == null) return; 117 HdmiDeviceInfo info = tvInfo.getHdmiDeviceInfo(); 118 if (info == null) return; 119 addTvInput(inputId, info.getId()); 120 if (info.isCecDevice()) { 121 processDelayedActiveSource(info.getLogicalAddress()); 122 } 123 } 124 125 @Override 126 public void onInputRemoved(String inputId) { 127 removeTvInput(inputId); 128 } 129 }; 130 131 // Keeps the mapping (TV input ID, HDMI device ID) to keep track of the TV inputs ready to 132 // accept input switching request from HDMI devices. Requests for which the corresponding 133 // input ID is not yet registered by TV input framework need to be buffered for delayed 134 // processing. 135 private final HashMap<String, Integer> mTvInputs = new HashMap<>(); 136 137 @ServiceThreadOnly addTvInput(String inputId, int deviceId)138 private void addTvInput(String inputId, int deviceId) { 139 assertRunOnServiceThread(); 140 mTvInputs.put(inputId, deviceId); 141 } 142 143 @ServiceThreadOnly removeTvInput(String inputId)144 private void removeTvInput(String inputId) { 145 assertRunOnServiceThread(); 146 mTvInputs.remove(inputId); 147 } 148 149 @Override 150 @ServiceThreadOnly isInputReady(int deviceId)151 protected boolean isInputReady(int deviceId) { 152 assertRunOnServiceThread(); 153 return mTvInputs.containsValue(deviceId); 154 } 155 156 private SelectRequestBuffer mSelectRequestBuffer; 157 HdmiCecLocalDeviceTv(HdmiControlService service)158 HdmiCecLocalDeviceTv(HdmiControlService service) { 159 super(service, HdmiDeviceInfo.DEVICE_TV); 160 mPrevPortId = Constants.INVALID_PORT_ID; 161 mSystemAudioControlFeatureEnabled = service.getHdmiCecConfig().getIntValue( 162 HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL) 163 == HdmiControlManager.SYSTEM_AUDIO_CONTROL_ENABLED; 164 mStandbyHandler = new HdmiCecStandbyModeHandler(service, this); 165 } 166 167 @Override 168 @ServiceThreadOnly onAddressAllocated(int logicalAddress, int reason)169 protected void onAddressAllocated(int logicalAddress, int reason) { 170 assertRunOnServiceThread(); 171 List<HdmiPortInfo> ports = mService.getPortInfo(); 172 for (HdmiPortInfo port : ports) { 173 mArcFeatureEnabled.put(port.getId(), port.isArcSupported()); 174 } 175 mService.registerTvInputCallback(mTvInputCallback); 176 mService.sendCecCommand( 177 HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( 178 getDeviceInfo().getLogicalAddress(), 179 mService.getPhysicalAddress(), 180 mDeviceType)); 181 mService.sendCecCommand( 182 HdmiCecMessageBuilder.buildDeviceVendorIdCommand( 183 getDeviceInfo().getLogicalAddress(), mService.getVendorId())); 184 mService.getHdmiCecNetwork().addCecSwitch( 185 mService.getHdmiCecNetwork().getPhysicalAddress()); // TV is a CEC switch too. 186 mTvInputs.clear(); 187 mSkipRoutingControl = (reason == HdmiControlService.INITIATED_BY_WAKE_UP_MESSAGE); 188 launchRoutingControl(reason != HdmiControlService.INITIATED_BY_ENABLE_CEC && 189 reason != HdmiControlService.INITIATED_BY_BOOT_UP); 190 resetSelectRequestBuffer(); 191 launchDeviceDiscovery(); 192 startQueuedActions(); 193 if (!mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) { 194 mService.sendCecCommand( 195 HdmiCecMessageBuilder.buildRequestActiveSource( 196 getDeviceInfo().getLogicalAddress())); 197 } 198 } 199 200 @ServiceThreadOnly setSelectRequestBuffer(SelectRequestBuffer requestBuffer)201 public void setSelectRequestBuffer(SelectRequestBuffer requestBuffer) { 202 assertRunOnServiceThread(); 203 mSelectRequestBuffer = requestBuffer; 204 } 205 206 @ServiceThreadOnly resetSelectRequestBuffer()207 private void resetSelectRequestBuffer() { 208 assertRunOnServiceThread(); 209 setSelectRequestBuffer(SelectRequestBuffer.EMPTY_BUFFER); 210 } 211 212 @Override getPreferredAddress()213 protected int getPreferredAddress() { 214 return Constants.ADDR_TV; 215 } 216 217 @Override setPreferredAddress(int addr)218 protected void setPreferredAddress(int addr) { 219 Slog.w(TAG, "Preferred addres will not be stored for TV"); 220 } 221 222 @Override 223 @ServiceThreadOnly 224 @VisibleForTesting 225 @Constants.HandleMessageResult dispatchMessage(HdmiCecMessage message)226 protected int dispatchMessage(HdmiCecMessage message) { 227 assertRunOnServiceThread(); 228 if (mService.isPowerStandby() && !mService.isWakeUpMessageReceived() 229 && mStandbyHandler.handleCommand(message)) { 230 return Constants.HANDLED; 231 } 232 return super.onMessage(message); 233 } 234 235 /** 236 * Performs the action 'device select', or 'one touch play' initiated by TV. 237 * 238 * @param id id of HDMI device to select 239 * @param callback callback object to report the result with 240 */ 241 @ServiceThreadOnly deviceSelect(int id, IHdmiControlCallback callback)242 void deviceSelect(int id, IHdmiControlCallback callback) { 243 assertRunOnServiceThread(); 244 HdmiDeviceInfo targetDevice = mService.getHdmiCecNetwork().getDeviceInfo(id); 245 if (targetDevice == null) { 246 invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); 247 return; 248 } 249 int targetAddress = targetDevice.getLogicalAddress(); 250 if (isAlreadyActiveSource(targetDevice, targetAddress, callback)) { 251 return; 252 } 253 if (targetAddress == Constants.ADDR_INTERNAL) { 254 handleSelectInternalSource(); 255 // Switching to internal source is always successful even when CEC control is disabled. 256 setActiveSource(targetAddress, mService.getPhysicalAddress(), 257 "HdmiCecLocalDeviceTv#deviceSelect()"); 258 setActivePath(mService.getPhysicalAddress()); 259 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS); 260 return; 261 } 262 if (!mService.isControlEnabled()) { 263 setActiveSource(targetDevice, "HdmiCecLocalDeviceTv#deviceSelect()"); 264 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); 265 return; 266 } 267 removeAction(DeviceSelectActionFromTv.class); 268 addAndStartAction(new DeviceSelectActionFromTv(this, targetDevice, callback)); 269 } 270 271 @ServiceThreadOnly handleSelectInternalSource()272 private void handleSelectInternalSource() { 273 assertRunOnServiceThread(); 274 // Seq #18 275 if (mService.isControlEnabled() 276 && getActiveSource().logicalAddress != getDeviceInfo().getLogicalAddress()) { 277 updateActiveSource( 278 getDeviceInfo().getLogicalAddress(), 279 mService.getPhysicalAddress(), 280 "HdmiCecLocalDeviceTv#handleSelectInternalSource()"); 281 if (mSkipRoutingControl) { 282 mSkipRoutingControl = false; 283 return; 284 } 285 HdmiCecMessage activeSource = 286 HdmiCecMessageBuilder.buildActiveSource( 287 getDeviceInfo().getLogicalAddress(), mService.getPhysicalAddress()); 288 mService.sendCecCommand(activeSource); 289 } 290 } 291 292 @ServiceThreadOnly updateActiveSource(int logicalAddress, int physicalAddress, String caller)293 void updateActiveSource(int logicalAddress, int physicalAddress, String caller) { 294 assertRunOnServiceThread(); 295 updateActiveSource(ActiveSource.of(logicalAddress, physicalAddress), caller); 296 } 297 298 @ServiceThreadOnly updateActiveSource(ActiveSource newActive, String caller)299 void updateActiveSource(ActiveSource newActive, String caller) { 300 assertRunOnServiceThread(); 301 // Seq #14 302 if (getActiveSource().equals(newActive)) { 303 return; 304 } 305 setActiveSource(newActive, caller); 306 int logicalAddress = newActive.logicalAddress; 307 if (mService.getHdmiCecNetwork().getCecDeviceInfo(logicalAddress) != null 308 && logicalAddress != getDeviceInfo().getLogicalAddress()) { 309 if (mService.pathToPortId(newActive.physicalAddress) == getActivePortId()) { 310 setPrevPortId(getActivePortId()); 311 } 312 // TODO: Show the OSD banner related to the new active source device. 313 } else { 314 // TODO: If displayed, remove the OSD banner related to the previous 315 // active source device. 316 } 317 } 318 319 /** 320 * Returns the previous port id kept to handle input switching on <Inactive Source>. 321 */ getPrevPortId()322 int getPrevPortId() { 323 synchronized (mLock) { 324 return mPrevPortId; 325 } 326 } 327 328 /** 329 * Sets the previous port id. INVALID_PORT_ID invalidates it, hence no actions will be 330 * taken for <Inactive Source>. 331 */ setPrevPortId(int portId)332 void setPrevPortId(int portId) { 333 synchronized (mLock) { 334 mPrevPortId = portId; 335 } 336 } 337 338 @ServiceThreadOnly updateActiveInput(int path, boolean notifyInputChange)339 void updateActiveInput(int path, boolean notifyInputChange) { 340 assertRunOnServiceThread(); 341 // Seq #15 342 setActivePath(path); 343 // TODO: Handle PAP/PIP case. 344 // Show OSD port change banner 345 if (notifyInputChange) { 346 ActiveSource activeSource = getActiveSource(); 347 HdmiDeviceInfo info = mService.getHdmiCecNetwork().getCecDeviceInfo( 348 activeSource.logicalAddress); 349 if (info == null) { 350 info = mService.getDeviceInfoByPort(getActivePortId()); 351 if (info == null) { 352 // No CEC/MHL device is present at the port. Attempt to switch to 353 // the hardware port itself for non-CEC devices that may be connected. 354 info = HdmiDeviceInfo.hardwarePort(path, getActivePortId()); 355 } 356 } 357 mService.invokeInputChangeListener(info); 358 } 359 } 360 361 @ServiceThreadOnly doManualPortSwitching(int portId, IHdmiControlCallback callback)362 void doManualPortSwitching(int portId, IHdmiControlCallback callback) { 363 assertRunOnServiceThread(); 364 // Seq #20 365 if (!mService.isValidPortId(portId)) { 366 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); 367 return; 368 } 369 if (portId == getActivePortId()) { 370 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS); 371 return; 372 } 373 getActiveSource().invalidate(); 374 if (!mService.isControlEnabled()) { 375 setActivePortId(portId); 376 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); 377 return; 378 } 379 int oldPath = getActivePortId() != Constants.INVALID_PORT_ID 380 ? mService.portIdToPath(getActivePortId()) : getDeviceInfo().getPhysicalAddress(); 381 setActivePath(oldPath); 382 if (mSkipRoutingControl) { 383 mSkipRoutingControl = false; 384 return; 385 } 386 int newPath = mService.portIdToPath(portId); 387 startRoutingControl(oldPath, newPath, callback); 388 } 389 390 @ServiceThreadOnly startRoutingControl(int oldPath, int newPath, IHdmiControlCallback callback)391 void startRoutingControl(int oldPath, int newPath, IHdmiControlCallback callback) { 392 assertRunOnServiceThread(); 393 if (oldPath == newPath) { 394 return; 395 } 396 HdmiCecMessage routingChange = 397 HdmiCecMessageBuilder.buildRoutingChange( 398 getDeviceInfo().getLogicalAddress(), oldPath, newPath); 399 mService.sendCecCommand(routingChange); 400 removeAction(RoutingControlAction.class); 401 addAndStartAction( 402 new RoutingControlAction(this, newPath, callback)); 403 } 404 405 @ServiceThreadOnly getPowerStatus()406 int getPowerStatus() { 407 assertRunOnServiceThread(); 408 return mService.getPowerStatus(); 409 } 410 411 @Override findKeyReceiverAddress()412 protected int findKeyReceiverAddress() { 413 if (getActiveSource().isValid()) { 414 return getActiveSource().logicalAddress; 415 } 416 HdmiDeviceInfo info = mService.getHdmiCecNetwork().getDeviceInfoByPath(getActivePath()); 417 if (info != null) { 418 return info.getLogicalAddress(); 419 } 420 return Constants.ADDR_INVALID; 421 } 422 423 @Override findAudioReceiverAddress()424 protected int findAudioReceiverAddress() { 425 return Constants.ADDR_AUDIO_SYSTEM; 426 } 427 428 @Override 429 @ServiceThreadOnly 430 @Constants.HandleMessageResult handleActiveSource(HdmiCecMessage message)431 protected int handleActiveSource(HdmiCecMessage message) { 432 assertRunOnServiceThread(); 433 int logicalAddress = message.getSource(); 434 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); 435 HdmiDeviceInfo info = mService.getHdmiCecNetwork().getCecDeviceInfo(logicalAddress); 436 if (info == null) { 437 if (!handleNewDeviceAtTheTailOfActivePath(physicalAddress)) { 438 HdmiLogger.debug("Device info %X not found; buffering the command", logicalAddress); 439 mDelayedMessageBuffer.add(message); 440 } 441 } else if (isInputReady(info.getId()) 442 || info.getDeviceType() == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) { 443 mService.getHdmiCecNetwork().updateDevicePowerStatus(logicalAddress, 444 HdmiControlManager.POWER_STATUS_ON); 445 ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress); 446 ActiveSourceHandler.create(this, null).process(activeSource, info.getDeviceType()); 447 } else { 448 HdmiLogger.debug("Input not ready for device: %X; buffering the command", info.getId()); 449 mDelayedMessageBuffer.add(message); 450 } 451 return Constants.HANDLED; 452 } 453 454 @Override 455 @ServiceThreadOnly 456 @Constants.HandleMessageResult handleInactiveSource(HdmiCecMessage message)457 protected int handleInactiveSource(HdmiCecMessage message) { 458 assertRunOnServiceThread(); 459 // Seq #10 460 461 // Ignore <Inactive Source> from non-active source device. 462 if (getActiveSource().logicalAddress != message.getSource()) { 463 return Constants.HANDLED; 464 } 465 if (isProhibitMode()) { 466 return Constants.HANDLED; 467 } 468 int portId = getPrevPortId(); 469 if (portId != Constants.INVALID_PORT_ID) { 470 // TODO: Do this only if TV is not showing multiview like PIP/PAP. 471 472 HdmiDeviceInfo inactiveSource = mService.getHdmiCecNetwork().getCecDeviceInfo( 473 message.getSource()); 474 if (inactiveSource == null) { 475 return Constants.HANDLED; 476 } 477 if (mService.pathToPortId(inactiveSource.getPhysicalAddress()) == portId) { 478 return Constants.HANDLED; 479 } 480 // TODO: Switch the TV freeze mode off 481 482 doManualPortSwitching(portId, null); 483 setPrevPortId(Constants.INVALID_PORT_ID); 484 } else { 485 // No HDMI port to switch to was found. Notify the input change listers to 486 // switch to the lastly shown internal input. 487 getActiveSource().invalidate(); 488 setActivePath(Constants.INVALID_PHYSICAL_ADDRESS); 489 mService.invokeInputChangeListener(HdmiDeviceInfo.INACTIVE_DEVICE); 490 } 491 return Constants.HANDLED; 492 } 493 494 @Override 495 @ServiceThreadOnly 496 @Constants.HandleMessageResult handleRequestActiveSource(HdmiCecMessage message)497 protected int handleRequestActiveSource(HdmiCecMessage message) { 498 assertRunOnServiceThread(); 499 // Seq #19 500 if (getDeviceInfo().getLogicalAddress() == getActiveSource().logicalAddress) { 501 mService.sendCecCommand( 502 HdmiCecMessageBuilder.buildActiveSource( 503 getDeviceInfo().getLogicalAddress(), getActivePath())); 504 } 505 return Constants.HANDLED; 506 } 507 508 @Override 509 @ServiceThreadOnly 510 @Constants.HandleMessageResult handleGetMenuLanguage(HdmiCecMessage message)511 protected int handleGetMenuLanguage(HdmiCecMessage message) { 512 assertRunOnServiceThread(); 513 if (!broadcastMenuLanguage(mService.getLanguage())) { 514 Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString()); 515 } 516 return Constants.HANDLED; 517 } 518 519 @ServiceThreadOnly broadcastMenuLanguage(String language)520 boolean broadcastMenuLanguage(String language) { 521 assertRunOnServiceThread(); 522 HdmiCecMessage command = 523 HdmiCecMessageBuilder.buildSetMenuLanguageCommand( 524 getDeviceInfo().getLogicalAddress(), language); 525 if (command != null) { 526 mService.sendCecCommand(command); 527 return true; 528 } 529 return false; 530 } 531 532 @Override 533 @Constants.HandleMessageResult handleReportPhysicalAddress(HdmiCecMessage message)534 protected int handleReportPhysicalAddress(HdmiCecMessage message) { 535 super.handleReportPhysicalAddress(message); 536 int path = HdmiUtils.twoBytesToInt(message.getParams()); 537 int address = message.getSource(); 538 int type = message.getParams()[2]; 539 540 if (!mService.getHdmiCecNetwork().isInDeviceList(address, path)) { 541 handleNewDeviceAtTheTailOfActivePath(path); 542 } 543 startNewDeviceAction(ActiveSource.of(address, path), type); 544 return Constants.HANDLED; 545 } 546 547 @Override 548 @Constants.HandleMessageResult handleTimerStatus(HdmiCecMessage message)549 protected int handleTimerStatus(HdmiCecMessage message) { 550 // Do nothing. 551 return Constants.HANDLED; 552 } 553 554 @Override 555 @Constants.HandleMessageResult handleRecordStatus(HdmiCecMessage message)556 protected int handleRecordStatus(HdmiCecMessage message) { 557 // Do nothing. 558 return Constants.HANDLED; 559 } 560 startNewDeviceAction(ActiveSource activeSource, int deviceType)561 void startNewDeviceAction(ActiveSource activeSource, int deviceType) { 562 for (NewDeviceAction action : getActions(NewDeviceAction.class)) { 563 // If there is new device action which has the same logical address and path 564 // ignore new request. 565 // NewDeviceAction is created whenever it receives <Report Physical Address>. 566 // And there is a chance starting NewDeviceAction for the same source. 567 // Usually, new device sends <Report Physical Address> when it's plugged 568 // in. However, TV can detect a new device from HotPlugDetectionAction, 569 // which sends <Give Physical Address> to the source for newly detected 570 // device. 571 if (action.isActionOf(activeSource)) { 572 return; 573 } 574 } 575 576 addAndStartAction(new NewDeviceAction(this, activeSource.logicalAddress, 577 activeSource.physicalAddress, deviceType)); 578 } 579 handleNewDeviceAtTheTailOfActivePath(int path)580 private boolean handleNewDeviceAtTheTailOfActivePath(int path) { 581 // Seq #22 582 if (isTailOfActivePath(path, getActivePath())) { 583 int newPath = mService.portIdToPath(getActivePortId()); 584 setActivePath(newPath); 585 startRoutingControl(getActivePath(), newPath, null); 586 return true; 587 } 588 return false; 589 } 590 591 /** 592 * Whether the given path is located in the tail of current active path. 593 * 594 * @param path to be tested 595 * @param activePath current active path 596 * @return true if the given path is located in the tail of current active path; otherwise, 597 * false 598 */ isTailOfActivePath(int path, int activePath)599 static boolean isTailOfActivePath(int path, int activePath) { 600 // If active routing path is internal source, return false. 601 if (activePath == 0) { 602 return false; 603 } 604 for (int i = 12; i >= 0; i -= 4) { 605 int curActivePath = (activePath >> i) & 0xF; 606 if (curActivePath == 0) { 607 return true; 608 } else { 609 int curPath = (path >> i) & 0xF; 610 if (curPath != curActivePath) { 611 return false; 612 } 613 } 614 } 615 return false; 616 } 617 618 @Override 619 @ServiceThreadOnly 620 @Constants.HandleMessageResult handleRoutingChange(HdmiCecMessage message)621 protected int handleRoutingChange(HdmiCecMessage message) { 622 assertRunOnServiceThread(); 623 // Seq #21 624 byte[] params = message.getParams(); 625 int currentPath = HdmiUtils.twoBytesToInt(params); 626 if (HdmiUtils.isAffectingActiveRoutingPath(getActivePath(), currentPath)) { 627 getActiveSource().invalidate(); 628 removeAction(RoutingControlAction.class); 629 int newPath = HdmiUtils.twoBytesToInt(params, 2); 630 addAndStartAction(new RoutingControlAction(this, newPath, null)); 631 } 632 return Constants.HANDLED; 633 } 634 635 @Override 636 @ServiceThreadOnly 637 @Constants.HandleMessageResult handleReportAudioStatus(HdmiCecMessage message)638 protected int handleReportAudioStatus(HdmiCecMessage message) { 639 assertRunOnServiceThread(); 640 if (mService.getHdmiCecVolumeControl() 641 == HdmiControlManager.VOLUME_CONTROL_DISABLED) { 642 return Constants.ABORT_REFUSED; 643 } 644 645 boolean mute = HdmiUtils.isAudioStatusMute(message); 646 int volume = HdmiUtils.getAudioStatusVolume(message); 647 setAudioStatus(mute, volume); 648 return Constants.HANDLED; 649 } 650 651 @Override 652 @ServiceThreadOnly 653 @Constants.HandleMessageResult handleTextViewOn(HdmiCecMessage message)654 protected int handleTextViewOn(HdmiCecMessage message) { 655 assertRunOnServiceThread(); 656 657 // Note that if the device is in sleep mode, the <Text View On> (and <Image View On>) 658 // command won't be handled here in most cases. A dedicated microcontroller should be in 659 // charge while the Android system is in sleep mode, and the command doesn't need to be 660 // passed up to this service. 661 // The only situations where the command reaches this handler are 662 // 1. if sleep mode is implemented in such a way that Android system is not really put to 663 // standby mode but only the display is set to blank. Then the command leads to 664 // turning on the display by the invocation of PowerManager.wakeUp(). 665 // 2. if the device is in dream mode, not sleep mode. Then this command leads to 666 // waking up the device from dream mode by the invocation of PowerManager.wakeUp(). 667 if (getAutoWakeup()) { 668 mService.wakeUp(); 669 } 670 return Constants.HANDLED; 671 } 672 673 @Override 674 @ServiceThreadOnly 675 @Constants.HandleMessageResult handleImageViewOn(HdmiCecMessage message)676 protected int handleImageViewOn(HdmiCecMessage message) { 677 assertRunOnServiceThread(); 678 // Currently, it's the same as <Text View On>. 679 return handleTextViewOn(message); 680 } 681 682 @ServiceThreadOnly launchDeviceDiscovery()683 private void launchDeviceDiscovery() { 684 assertRunOnServiceThread(); 685 clearDeviceInfoList(); 686 DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, 687 new DeviceDiscoveryCallback() { 688 @Override 689 public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) { 690 for (HdmiDeviceInfo info : deviceInfos) { 691 mService.getHdmiCecNetwork().addCecDevice(info); 692 } 693 694 // Since we removed all devices when it starts and 695 // device discovery action does not poll local devices, 696 // we should put device info of local device manually here 697 for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) { 698 mService.getHdmiCecNetwork().addCecDevice(device.getDeviceInfo()); 699 } 700 701 mSelectRequestBuffer.process(); 702 resetSelectRequestBuffer(); 703 704 List<HotplugDetectionAction> hotplugActions 705 = getActions(HotplugDetectionAction.class); 706 if (hotplugActions.isEmpty()) { 707 addAndStartAction( 708 new HotplugDetectionAction(HdmiCecLocalDeviceTv.this)); 709 } 710 711 List<PowerStatusMonitorAction> powerStatusActions 712 = getActions(PowerStatusMonitorAction.class); 713 if (powerStatusActions.isEmpty()) { 714 addAndStartAction( 715 new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this)); 716 } 717 718 HdmiDeviceInfo avr = getAvrDeviceInfo(); 719 if (avr != null) { 720 onNewAvrAdded(avr); 721 } else { 722 setSystemAudioMode(false); 723 } 724 } 725 }); 726 addAndStartAction(action); 727 } 728 729 @ServiceThreadOnly onNewAvrAdded(HdmiDeviceInfo avr)730 void onNewAvrAdded(HdmiDeviceInfo avr) { 731 assertRunOnServiceThread(); 732 addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress())); 733 if (!isDirectConnectAddress(avr.getPhysicalAddress())) { 734 startArcAction(false); 735 } else if (isConnected(avr.getPortId()) && isArcFeatureEnabled(avr.getPortId()) 736 && !hasAction(SetArcTransmissionStateAction.class)) { 737 startArcAction(true); 738 } 739 } 740 741 @ServiceThreadOnly 742 // Seq #32 changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback)743 void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) { 744 assertRunOnServiceThread(); 745 if (!mService.isControlEnabled() || hasAction(DeviceDiscoveryAction.class)) { 746 setSystemAudioMode(false); 747 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); 748 return; 749 } 750 HdmiDeviceInfo avr = getAvrDeviceInfo(); 751 if (avr == null) { 752 setSystemAudioMode(false); 753 invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); 754 return; 755 } 756 757 addAndStartAction( 758 new SystemAudioActionFromTv(this, avr.getLogicalAddress(), enabled, callback)); 759 } 760 761 // # Seq 25 setSystemAudioMode(boolean on)762 void setSystemAudioMode(boolean on) { 763 if (!isSystemAudioControlFeatureEnabled() && on) { 764 HdmiLogger.debug("Cannot turn on system audio mode " 765 + "because the System Audio Control feature is disabled."); 766 return; 767 } 768 HdmiLogger.debug("System Audio Mode change[old:%b new:%b]", 769 mService.isSystemAudioActivated(), on); 770 updateAudioManagerForSystemAudio(on); 771 synchronized (mLock) { 772 if (mService.isSystemAudioActivated() != on) { 773 mService.setSystemAudioActivated(on); 774 mService.announceSystemAudioModeChange(on); 775 } 776 if (on && !mArcEstablished) { 777 startArcAction(true); 778 } else if (!on) { 779 startArcAction(false); 780 } 781 } 782 } 783 updateAudioManagerForSystemAudio(boolean on)784 private void updateAudioManagerForSystemAudio(boolean on) { 785 int device = mService.getAudioManager().setHdmiSystemAudioSupported(on); 786 HdmiLogger.debug("[A]UpdateSystemAudio mode[on=%b] output=[%X]", on, device); 787 } 788 isSystemAudioActivated()789 boolean isSystemAudioActivated() { 790 if (!hasSystemAudioDevice()) { 791 return false; 792 } 793 return mService.isSystemAudioActivated(); 794 } 795 796 @ServiceThreadOnly setSystemAudioControlFeatureEnabled(boolean enabled)797 void setSystemAudioControlFeatureEnabled(boolean enabled) { 798 assertRunOnServiceThread(); 799 synchronized (mLock) { 800 mSystemAudioControlFeatureEnabled = enabled; 801 } 802 if (hasSystemAudioDevice()) { 803 changeSystemAudioMode(enabled, null); 804 } 805 } 806 isSystemAudioControlFeatureEnabled()807 boolean isSystemAudioControlFeatureEnabled() { 808 synchronized (mLock) { 809 return mSystemAudioControlFeatureEnabled; 810 } 811 } 812 813 @ServiceThreadOnly enableArc(List<byte[]> supportedSads)814 void enableArc(List<byte[]> supportedSads) { 815 assertRunOnServiceThread(); 816 HdmiLogger.debug("Set Arc Status[old:%b new:true]", mArcEstablished); 817 818 enableAudioReturnChannel(true); 819 notifyArcStatusToAudioService(true, supportedSads); 820 mArcEstablished = true; 821 } 822 823 @ServiceThreadOnly disableArc()824 void disableArc() { 825 assertRunOnServiceThread(); 826 HdmiLogger.debug("Set Arc Status[old:%b new:false]", mArcEstablished); 827 828 enableAudioReturnChannel(false); 829 notifyArcStatusToAudioService(false, new ArrayList<>()); 830 mArcEstablished = false; 831 } 832 833 /** 834 * Switch hardware ARC circuit in the system. 835 */ 836 @ServiceThreadOnly enableAudioReturnChannel(boolean enabled)837 void enableAudioReturnChannel(boolean enabled) { 838 assertRunOnServiceThread(); 839 HdmiDeviceInfo avr = getAvrDeviceInfo(); 840 if (avr != null) { 841 mService.enableAudioReturnChannel(avr.getPortId(), enabled); 842 } 843 } 844 845 @ServiceThreadOnly isConnected(int portId)846 boolean isConnected(int portId) { 847 assertRunOnServiceThread(); 848 return mService.isConnected(portId); 849 } 850 notifyArcStatusToAudioService(boolean enabled, List<byte[]> supportedSads)851 private void notifyArcStatusToAudioService(boolean enabled, List<byte[]> supportedSads) { 852 // Note that we don't set any name to ARC. 853 AudioDeviceAttributes attributes = new AudioDeviceAttributes( 854 AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI_ARC, "", "", 855 new ArrayList<AudioProfile>(), supportedSads.stream() 856 .map(sad -> new AudioDescriptor(AudioDescriptor.STANDARD_EDID, 857 AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE, sad)) 858 .collect(Collectors.toList())); 859 mService.getAudioManager().setWiredDeviceConnectionState(attributes, enabled ? 1 : 0); 860 } 861 862 /** 863 * Returns true if ARC is currently established on a certain port. 864 */ 865 @ServiceThreadOnly isArcEstablished()866 boolean isArcEstablished() { 867 assertRunOnServiceThread(); 868 if (mArcEstablished) { 869 for (int i = 0; i < mArcFeatureEnabled.size(); i++) { 870 if (mArcFeatureEnabled.valueAt(i)) return true; 871 } 872 } 873 return false; 874 } 875 876 @ServiceThreadOnly changeArcFeatureEnabled(int portId, boolean enabled)877 void changeArcFeatureEnabled(int portId, boolean enabled) { 878 assertRunOnServiceThread(); 879 if (mArcFeatureEnabled.get(portId) == enabled) { 880 return; 881 } 882 mArcFeatureEnabled.put(portId, enabled); 883 HdmiDeviceInfo avr = getAvrDeviceInfo(); 884 if (avr == null || avr.getPortId() != portId) { 885 return; 886 } 887 if (enabled && !mArcEstablished) { 888 startArcAction(true); 889 } else if (!enabled && mArcEstablished) { 890 startArcAction(false); 891 } 892 } 893 894 @ServiceThreadOnly isArcFeatureEnabled(int portId)895 boolean isArcFeatureEnabled(int portId) { 896 assertRunOnServiceThread(); 897 return mArcFeatureEnabled.get(portId); 898 } 899 900 @ServiceThreadOnly startArcAction(boolean enabled)901 void startArcAction(boolean enabled) { 902 assertRunOnServiceThread(); 903 HdmiDeviceInfo info = getAvrDeviceInfo(); 904 if (info == null) { 905 Slog.w(TAG, "Failed to start arc action; No AVR device."); 906 return; 907 } 908 if (!canStartArcUpdateAction(info.getLogicalAddress(), enabled)) { 909 Slog.w(TAG, "Failed to start arc action; ARC configuration check failed."); 910 if (enabled && !isConnectedToArcPort(info.getPhysicalAddress())) { 911 displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT); 912 } 913 return; 914 } 915 916 // Terminate opposite action and start action if not exist. 917 if (enabled) { 918 removeAction(RequestArcTerminationAction.class); 919 if (!hasAction(RequestArcInitiationAction.class)) { 920 addAndStartAction(new RequestArcInitiationAction(this, info.getLogicalAddress())); 921 } 922 } else { 923 removeAction(RequestArcInitiationAction.class); 924 if (!hasAction(RequestArcTerminationAction.class)) { 925 addAndStartAction(new RequestArcTerminationAction(this, info.getLogicalAddress())); 926 } 927 } 928 } 929 isDirectConnectAddress(int physicalAddress)930 private boolean isDirectConnectAddress(int physicalAddress) { 931 return (physicalAddress & Constants.ROUTING_PATH_TOP_MASK) == physicalAddress; 932 } 933 setAudioStatus(boolean mute, int volume)934 void setAudioStatus(boolean mute, int volume) { 935 if (!isSystemAudioActivated() || mService.getHdmiCecVolumeControl() 936 == HdmiControlManager.VOLUME_CONTROL_DISABLED) { 937 return; 938 } 939 synchronized (mLock) { 940 mSystemAudioMute = mute; 941 mSystemAudioVolume = volume; 942 displayOsd(HdmiControlManager.OSD_MESSAGE_AVR_VOLUME_CHANGED, 943 mute ? HdmiControlManager.AVR_VOLUME_MUTED : volume); 944 } 945 } 946 947 @ServiceThreadOnly changeVolume(int curVolume, int delta, int maxVolume)948 void changeVolume(int curVolume, int delta, int maxVolume) { 949 assertRunOnServiceThread(); 950 if (getAvrDeviceInfo() == null) { 951 // On initialization process, getAvrDeviceInfo() may return null and cause exception 952 return; 953 } 954 if (delta == 0 || !isSystemAudioActivated() || mService.getHdmiCecVolumeControl() 955 == HdmiControlManager.VOLUME_CONTROL_DISABLED) { 956 return; 957 } 958 959 int targetVolume = curVolume + delta; 960 int cecVolume = VolumeControlAction.scaleToCecVolume(targetVolume, maxVolume); 961 synchronized (mLock) { 962 // If new volume is the same as current system audio volume, just ignore it. 963 // Note that UNKNOWN_VOLUME is not in range of cec volume scale. 964 if (cecVolume == mSystemAudioVolume) { 965 // Update tv volume with system volume value. 966 mService.setAudioStatus(false, 967 VolumeControlAction.scaleToCustomVolume(mSystemAudioVolume, maxVolume)); 968 return; 969 } 970 } 971 972 List<VolumeControlAction> actions = getActions(VolumeControlAction.class); 973 if (actions.isEmpty()) { 974 addAndStartAction(new VolumeControlAction(this, 975 getAvrDeviceInfo().getLogicalAddress(), delta > 0)); 976 } else { 977 actions.get(0).handleVolumeChange(delta > 0); 978 } 979 } 980 981 @ServiceThreadOnly changeMute(boolean mute)982 void changeMute(boolean mute) { 983 assertRunOnServiceThread(); 984 if (getAvrDeviceInfo() == null || mService.getHdmiCecVolumeControl() 985 == HdmiControlManager.VOLUME_CONTROL_DISABLED) { 986 // On initialization process, getAvrDeviceInfo() may return null and cause exception 987 return; 988 } 989 HdmiLogger.debug("[A]:Change mute:%b", mute); 990 synchronized (mLock) { 991 if (mSystemAudioMute == mute) { 992 HdmiLogger.debug("No need to change mute."); 993 return; 994 } 995 } 996 if (!isSystemAudioActivated()) { 997 HdmiLogger.debug("[A]:System audio is not activated."); 998 return; 999 } 1000 1001 // Remove existing volume action. 1002 removeAction(VolumeControlAction.class); 1003 sendUserControlPressedAndReleased(getAvrDeviceInfo().getLogicalAddress(), 1004 HdmiCecKeycode.getMuteKey(mute)); 1005 } 1006 1007 @Override 1008 @ServiceThreadOnly 1009 @Constants.HandleMessageResult handleInitiateArc(HdmiCecMessage message)1010 protected int handleInitiateArc(HdmiCecMessage message) { 1011 assertRunOnServiceThread(); 1012 1013 if (!canStartArcUpdateAction(message.getSource(), true)) { 1014 HdmiDeviceInfo avrDeviceInfo = getAvrDeviceInfo(); 1015 if (avrDeviceInfo == null) { 1016 // AVR may not have been discovered yet. Delay the message processing. 1017 mDelayedMessageBuffer.add(message); 1018 return Constants.HANDLED; 1019 } 1020 if (!isConnectedToArcPort(avrDeviceInfo.getPhysicalAddress())) { 1021 displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT); 1022 } 1023 return Constants.ABORT_REFUSED; 1024 } 1025 1026 // In case where <Initiate Arc> is started by <Request ARC Initiation> 1027 // need to clean up RequestArcInitiationAction. 1028 removeAction(RequestArcInitiationAction.class); 1029 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 1030 message.getSource(), true); 1031 addAndStartAction(action); 1032 return Constants.HANDLED; 1033 } 1034 canStartArcUpdateAction(int avrAddress, boolean enabled)1035 private boolean canStartArcUpdateAction(int avrAddress, boolean enabled) { 1036 HdmiDeviceInfo avr = getAvrDeviceInfo(); 1037 if (avr != null 1038 && (avrAddress == avr.getLogicalAddress()) 1039 && isConnectedToArcPort(avr.getPhysicalAddress())) { 1040 if (enabled) { 1041 return isConnected(avr.getPortId()) 1042 && isArcFeatureEnabled(avr.getPortId()) 1043 && isDirectConnectAddress(avr.getPhysicalAddress()); 1044 } else { 1045 return true; 1046 } 1047 } else { 1048 return false; 1049 } 1050 } 1051 1052 @Override 1053 @ServiceThreadOnly 1054 @Constants.HandleMessageResult handleTerminateArc(HdmiCecMessage message)1055 protected int handleTerminateArc(HdmiCecMessage message) { 1056 assertRunOnServiceThread(); 1057 if (mService .isPowerStandbyOrTransient()) { 1058 disableArc(); 1059 return Constants.HANDLED; 1060 } 1061 // Do not check ARC configuration since the AVR might have been already removed. 1062 // Clean up RequestArcTerminationAction in case <Terminate Arc> was started by 1063 // <Request ARC Termination>. 1064 removeAction(RequestArcTerminationAction.class); 1065 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 1066 message.getSource(), false); 1067 addAndStartAction(action); 1068 return Constants.HANDLED; 1069 } 1070 1071 @Override 1072 @ServiceThreadOnly 1073 @Constants.HandleMessageResult handleSetSystemAudioMode(HdmiCecMessage message)1074 protected int handleSetSystemAudioMode(HdmiCecMessage message) { 1075 assertRunOnServiceThread(); 1076 boolean systemAudioStatus = HdmiUtils.parseCommandParamSystemAudioStatus(message); 1077 if (!isMessageForSystemAudio(message)) { 1078 if (getAvrDeviceInfo() == null) { 1079 // AVR may not have been discovered yet. Delay the message processing. 1080 mDelayedMessageBuffer.add(message); 1081 } else { 1082 HdmiLogger.warning("Invalid <Set System Audio Mode> message:" + message); 1083 return Constants.ABORT_REFUSED; 1084 } 1085 } else if (systemAudioStatus && !isSystemAudioControlFeatureEnabled()) { 1086 HdmiLogger.debug("Ignoring <Set System Audio Mode> message " 1087 + "because the System Audio Control feature is disabled: %s", message); 1088 return Constants.ABORT_REFUSED; 1089 } 1090 removeAction(SystemAudioAutoInitiationAction.class); 1091 SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this, 1092 message.getSource(), systemAudioStatus, null); 1093 addAndStartAction(action); 1094 return Constants.HANDLED; 1095 } 1096 1097 @Override 1098 @ServiceThreadOnly 1099 @Constants.HandleMessageResult handleSystemAudioModeStatus(HdmiCecMessage message)1100 protected int handleSystemAudioModeStatus(HdmiCecMessage message) { 1101 assertRunOnServiceThread(); 1102 if (!isMessageForSystemAudio(message)) { 1103 HdmiLogger.warning("Invalid <System Audio Mode Status> message:" + message); 1104 // Ignore this message. 1105 return Constants.HANDLED; 1106 } 1107 boolean tvSystemAudioMode = isSystemAudioControlFeatureEnabled(); 1108 boolean avrSystemAudioMode = HdmiUtils.parseCommandParamSystemAudioStatus(message); 1109 // Set System Audio Mode according to TV's settings. 1110 // Handle <System Audio Mode Status> here only when 1111 // SystemAudioAutoInitiationAction timeout 1112 HdmiDeviceInfo avr = getAvrDeviceInfo(); 1113 if (avr == null) { 1114 setSystemAudioMode(false); 1115 } else if (avrSystemAudioMode != tvSystemAudioMode) { 1116 addAndStartAction(new SystemAudioActionFromTv(this, avr.getLogicalAddress(), 1117 tvSystemAudioMode, null)); 1118 } else { 1119 setSystemAudioMode(tvSystemAudioMode); 1120 } 1121 1122 return Constants.HANDLED; 1123 } 1124 1125 // Seq #53 1126 @Override 1127 @ServiceThreadOnly 1128 @Constants.HandleMessageResult handleRecordTvScreen(HdmiCecMessage message)1129 protected int handleRecordTvScreen(HdmiCecMessage message) { 1130 List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class); 1131 if (!actions.isEmpty()) { 1132 // Assumes only one OneTouchRecordAction. 1133 OneTouchRecordAction action = actions.get(0); 1134 if (action.getRecorderAddress() != message.getSource()) { 1135 announceOneTouchRecordResult( 1136 message.getSource(), 1137 HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS); 1138 } 1139 // The default behavior of <Record TV Screen> is replying <Feature Abort> with 1140 // "Cannot provide source". 1141 return Constants.ABORT_CANNOT_PROVIDE_SOURCE; 1142 } 1143 1144 int recorderAddress = message.getSource(); 1145 byte[] recordSource = mService.invokeRecordRequestListener(recorderAddress); 1146 return startOneTouchRecord(recorderAddress, recordSource); 1147 } 1148 1149 @Override 1150 @Constants.HandleMessageResult handleTimerClearedStatus(HdmiCecMessage message)1151 protected int handleTimerClearedStatus(HdmiCecMessage message) { 1152 byte[] params = message.getParams(); 1153 int timerClearedStatusData = params[0] & 0xFF; 1154 announceTimerRecordingResult(message.getSource(), timerClearedStatusData); 1155 return Constants.HANDLED; 1156 } 1157 1158 @Override 1159 @Constants.HandleMessageResult handleSetAudioVolumeLevel(SetAudioVolumeLevelMessage message)1160 protected int handleSetAudioVolumeLevel(SetAudioVolumeLevelMessage message) { 1161 // <Set Audio Volume Level> should only be sent to the System Audio device, so we don't 1162 // handle it when System Audio Mode is enabled. 1163 if (mService.isSystemAudioActivated()) { 1164 return Constants.ABORT_NOT_IN_CORRECT_MODE; 1165 } else { 1166 mService.setStreamMusicVolume(message.getAudioVolumeLevel(), 0); 1167 return Constants.HANDLED; 1168 } 1169 } 1170 announceOneTouchRecordResult(int recorderAddress, int result)1171 void announceOneTouchRecordResult(int recorderAddress, int result) { 1172 mService.invokeOneTouchRecordResult(recorderAddress, result); 1173 } 1174 announceTimerRecordingResult(int recorderAddress, int result)1175 void announceTimerRecordingResult(int recorderAddress, int result) { 1176 mService.invokeTimerRecordingResult(recorderAddress, result); 1177 } 1178 announceClearTimerRecordingResult(int recorderAddress, int result)1179 void announceClearTimerRecordingResult(int recorderAddress, int result) { 1180 mService.invokeClearTimerRecordingResult(recorderAddress, result); 1181 } 1182 isMessageForSystemAudio(HdmiCecMessage message)1183 private boolean isMessageForSystemAudio(HdmiCecMessage message) { 1184 return mService.isControlEnabled() 1185 && message.getSource() == Constants.ADDR_AUDIO_SYSTEM 1186 && (message.getDestination() == Constants.ADDR_TV 1187 || message.getDestination() == Constants.ADDR_BROADCAST) 1188 && getAvrDeviceInfo() != null; 1189 } 1190 1191 @Nullable 1192 @ServiceThreadOnly getAvrDeviceInfo()1193 HdmiDeviceInfo getAvrDeviceInfo() { 1194 assertRunOnServiceThread(); 1195 return mService.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); 1196 } 1197 hasSystemAudioDevice()1198 boolean hasSystemAudioDevice() { 1199 return getSafeAvrDeviceInfo() != null; 1200 } 1201 1202 @Nullable getSafeAvrDeviceInfo()1203 HdmiDeviceInfo getSafeAvrDeviceInfo() { 1204 return mService.getHdmiCecNetwork().getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); 1205 } 1206 1207 /** 1208 * Returns the audio output device used for System Audio Mode. 1209 */ getSystemAudioOutputDevice()1210 AudioDeviceAttributes getSystemAudioOutputDevice() { 1211 return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_ARC; 1212 } 1213 1214 1215 @ServiceThreadOnly handleRemoveActiveRoutingPath(int path)1216 void handleRemoveActiveRoutingPath(int path) { 1217 assertRunOnServiceThread(); 1218 // Seq #23 1219 if (isTailOfActivePath(path, getActivePath())) { 1220 int newPath = mService.portIdToPath(getActivePortId()); 1221 startRoutingControl(getActivePath(), newPath, null); 1222 } 1223 } 1224 1225 /** 1226 * Launch routing control process. 1227 * 1228 * @param routingForBootup true if routing control is initiated due to One Touch Play 1229 * or TV power on 1230 */ 1231 @ServiceThreadOnly launchRoutingControl(boolean routingForBootup)1232 void launchRoutingControl(boolean routingForBootup) { 1233 assertRunOnServiceThread(); 1234 // Seq #24 1235 if (getActivePortId() != Constants.INVALID_PORT_ID) { 1236 if (!routingForBootup && !isProhibitMode()) { 1237 int newPath = mService.portIdToPath(getActivePortId()); 1238 setActivePath(newPath); 1239 startRoutingControl(getActivePath(), newPath, null); 1240 } 1241 } else { 1242 int activePath = mService.getPhysicalAddress(); 1243 setActivePath(activePath); 1244 if (!routingForBootup 1245 && !mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) { 1246 mService.sendCecCommand( 1247 HdmiCecMessageBuilder.buildActiveSource( 1248 getDeviceInfo().getLogicalAddress(), activePath)); 1249 } 1250 } 1251 } 1252 1253 @Override 1254 @ServiceThreadOnly onHotplug(int portId, boolean connected)1255 void onHotplug(int portId, boolean connected) { 1256 assertRunOnServiceThread(); 1257 1258 if (!connected) { 1259 mService.getHdmiCecNetwork().removeCecSwitches(portId); 1260 } 1261 1262 // Turning System Audio Mode off when the AVR is unlugged or standby. 1263 // When the device is not unplugged but reawaken from standby, we check if the System 1264 // Audio Control Feature is enabled or not then decide if turning SAM on/off accordingly. 1265 if (getAvrDeviceInfo() != null && portId == getAvrDeviceInfo().getPortId()) { 1266 HdmiLogger.debug("Port ID:%d, 5v=%b", portId, connected); 1267 if (!connected) { 1268 setSystemAudioMode(false); 1269 } else { 1270 onNewAvrAdded(getAvrDeviceInfo()); 1271 } 1272 } 1273 1274 // Tv device will have permanent HotplugDetectionAction. 1275 List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class); 1276 if (!hotplugActions.isEmpty()) { 1277 // Note that hotplug action is single action running on a machine. 1278 // "pollAllDevicesNow" cleans up timer and start poll action immediately. 1279 // It covers seq #40, #43. 1280 hotplugActions.get(0).pollAllDevicesNow(); 1281 } 1282 } 1283 1284 @ServiceThreadOnly getAutoWakeup()1285 boolean getAutoWakeup() { 1286 assertRunOnServiceThread(); 1287 return mService.getHdmiCecConfig().getIntValue( 1288 HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY) 1289 == HdmiControlManager.TV_WAKE_ON_ONE_TOUCH_PLAY_ENABLED; 1290 } 1291 1292 @Override 1293 @ServiceThreadOnly disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback)1294 protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) { 1295 assertRunOnServiceThread(); 1296 mService.unregisterTvInputCallback(mTvInputCallback); 1297 // Remove any repeated working actions. 1298 // HotplugDetectionAction will be reinstated during the wake up process. 1299 // HdmiControlService.onWakeUp() -> initializeLocalDevices() -> 1300 // LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery(). 1301 removeAction(DeviceDiscoveryAction.class); 1302 removeAction(HotplugDetectionAction.class); 1303 removeAction(PowerStatusMonitorAction.class); 1304 // Remove recording actions. 1305 removeAction(OneTouchRecordAction.class); 1306 removeAction(TimerRecordingAction.class); 1307 removeAction(NewDeviceAction.class); 1308 removeAction(AbsoluteVolumeAudioStatusAction.class); 1309 1310 disableSystemAudioIfExist(); 1311 disableArcIfExist(); 1312 1313 super.disableDevice(initiatedByCec, callback); 1314 clearDeviceInfoList(); 1315 getActiveSource().invalidate(); 1316 setActivePath(Constants.INVALID_PHYSICAL_ADDRESS); 1317 checkIfPendingActionsCleared(); 1318 } 1319 1320 @ServiceThreadOnly disableSystemAudioIfExist()1321 private void disableSystemAudioIfExist() { 1322 assertRunOnServiceThread(); 1323 if (getAvrDeviceInfo() == null) { 1324 return; 1325 } 1326 1327 // Seq #31. 1328 removeAction(SystemAudioActionFromAvr.class); 1329 removeAction(SystemAudioActionFromTv.class); 1330 removeAction(SystemAudioAutoInitiationAction.class); 1331 removeAction(VolumeControlAction.class); 1332 1333 if (!mService.isControlEnabled()) { 1334 setSystemAudioMode(false); 1335 } 1336 } 1337 1338 @ServiceThreadOnly disableArcIfExist()1339 private void disableArcIfExist() { 1340 assertRunOnServiceThread(); 1341 HdmiDeviceInfo avr = getAvrDeviceInfo(); 1342 if (avr == null) { 1343 return; 1344 } 1345 disableArc(); 1346 1347 // Seq #44. 1348 removeAllRunningArcAction(); 1349 if (!hasAction(RequestArcTerminationAction.class) && isArcEstablished()) { 1350 addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress())); 1351 } 1352 } 1353 1354 @ServiceThreadOnly removeAllRunningArcAction()1355 private void removeAllRunningArcAction() { 1356 // Running or pending actions make TV fail to broadcast <Standby> to connected devices 1357 removeAction(RequestArcTerminationAction.class); 1358 removeAction(RequestArcInitiationAction.class); 1359 removeAction(SetArcTransmissionStateAction.class); 1360 } 1361 1362 @Override 1363 @ServiceThreadOnly onStandby(boolean initiatedByCec, int standbyAction)1364 protected void onStandby(boolean initiatedByCec, int standbyAction) { 1365 assertRunOnServiceThread(); 1366 // Seq #11 1367 if (!mService.isControlEnabled()) { 1368 return; 1369 } 1370 boolean sendStandbyOnSleep = 1371 mService.getHdmiCecConfig().getIntValue( 1372 HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP) 1373 == HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED; 1374 if (!initiatedByCec && sendStandbyOnSleep) { 1375 mService.sendCecCommand( 1376 HdmiCecMessageBuilder.buildStandby( 1377 getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST)); 1378 } 1379 } 1380 isProhibitMode()1381 boolean isProhibitMode() { 1382 return mService.isProhibitMode(); 1383 } 1384 isPowerStandbyOrTransient()1385 boolean isPowerStandbyOrTransient() { 1386 return mService.isPowerStandbyOrTransient(); 1387 } 1388 1389 @ServiceThreadOnly displayOsd(int messageId)1390 void displayOsd(int messageId) { 1391 assertRunOnServiceThread(); 1392 mService.displayOsd(messageId); 1393 } 1394 1395 @ServiceThreadOnly displayOsd(int messageId, int extra)1396 void displayOsd(int messageId, int extra) { 1397 assertRunOnServiceThread(); 1398 mService.displayOsd(messageId, extra); 1399 } 1400 1401 // Seq #54 and #55 1402 @ServiceThreadOnly 1403 @Constants.HandleMessageResult startOneTouchRecord(int recorderAddress, byte[] recordSource)1404 int startOneTouchRecord(int recorderAddress, byte[] recordSource) { 1405 assertRunOnServiceThread(); 1406 if (!mService.isControlEnabled()) { 1407 Slog.w(TAG, "Can not start one touch record. CEC control is disabled."); 1408 announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED); 1409 return Constants.ABORT_NOT_IN_CORRECT_MODE; 1410 } 1411 1412 if (!checkRecorder(recorderAddress)) { 1413 Slog.w(TAG, "Invalid recorder address:" + recorderAddress); 1414 announceOneTouchRecordResult(recorderAddress, 1415 ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION); 1416 return Constants.ABORT_NOT_IN_CORRECT_MODE; 1417 } 1418 1419 if (!checkRecordSource(recordSource)) { 1420 Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource)); 1421 announceOneTouchRecordResult(recorderAddress, 1422 ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN); 1423 return Constants.ABORT_CANNOT_PROVIDE_SOURCE; 1424 } 1425 1426 addAndStartAction(new OneTouchRecordAction(this, recorderAddress, recordSource)); 1427 Slog.i(TAG, "Start new [One Touch Record]-Target:" + recorderAddress + ", recordSource:" 1428 + Arrays.toString(recordSource)); 1429 return Constants.HANDLED; 1430 } 1431 1432 @ServiceThreadOnly stopOneTouchRecord(int recorderAddress)1433 void stopOneTouchRecord(int recorderAddress) { 1434 assertRunOnServiceThread(); 1435 if (!mService.isControlEnabled()) { 1436 Slog.w(TAG, "Can not stop one touch record. CEC control is disabled."); 1437 announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED); 1438 return; 1439 } 1440 1441 if (!checkRecorder(recorderAddress)) { 1442 Slog.w(TAG, "Invalid recorder address:" + recorderAddress); 1443 announceOneTouchRecordResult(recorderAddress, 1444 ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION); 1445 return; 1446 } 1447 1448 // Remove one touch record action so that other one touch record can be started. 1449 removeAction(OneTouchRecordAction.class); 1450 mService.sendCecCommand( 1451 HdmiCecMessageBuilder.buildRecordOff( 1452 getDeviceInfo().getLogicalAddress(), recorderAddress)); 1453 Slog.i(TAG, "Stop [One Touch Record]-Target:" + recorderAddress); 1454 } 1455 checkRecorder(int recorderAddress)1456 private boolean checkRecorder(int recorderAddress) { 1457 HdmiDeviceInfo device = mService.getHdmiCecNetwork().getCecDeviceInfo(recorderAddress); 1458 return (device != null) && (HdmiUtils.isEligibleAddressForDevice( 1459 HdmiDeviceInfo.DEVICE_RECORDER, recorderAddress)); 1460 } 1461 checkRecordSource(byte[] recordSource)1462 private boolean checkRecordSource(byte[] recordSource) { 1463 return (recordSource != null) && HdmiRecordSources.checkRecordSource(recordSource); 1464 } 1465 1466 @ServiceThreadOnly startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource)1467 void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) { 1468 assertRunOnServiceThread(); 1469 if (!mService.isControlEnabled()) { 1470 Slog.w(TAG, "Can not start one touch record. CEC control is disabled."); 1471 announceTimerRecordingResult(recorderAddress, 1472 TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED); 1473 return; 1474 } 1475 1476 if (!checkRecorder(recorderAddress)) { 1477 Slog.w(TAG, "Invalid recorder address:" + recorderAddress); 1478 announceTimerRecordingResult(recorderAddress, 1479 TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION); 1480 return; 1481 } 1482 1483 if (!checkTimerRecordingSource(sourceType, recordSource)) { 1484 Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource)); 1485 announceTimerRecordingResult( 1486 recorderAddress, 1487 TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE); 1488 return; 1489 } 1490 1491 addAndStartAction( 1492 new TimerRecordingAction(this, recorderAddress, sourceType, recordSource)); 1493 Slog.i(TAG, "Start [Timer Recording]-Target:" + recorderAddress + ", SourceType:" 1494 + sourceType + ", RecordSource:" + Arrays.toString(recordSource)); 1495 } 1496 checkTimerRecordingSource(int sourceType, byte[] recordSource)1497 private boolean checkTimerRecordingSource(int sourceType, byte[] recordSource) { 1498 return (recordSource != null) 1499 && HdmiTimerRecordSources.checkTimerRecordSource(sourceType, recordSource); 1500 } 1501 1502 @ServiceThreadOnly clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource)1503 void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) { 1504 assertRunOnServiceThread(); 1505 if (!mService.isControlEnabled()) { 1506 Slog.w(TAG, "Can not start one touch record. CEC control is disabled."); 1507 announceClearTimerRecordingResult(recorderAddress, CLEAR_TIMER_STATUS_CEC_DISABLE); 1508 return; 1509 } 1510 1511 if (!checkRecorder(recorderAddress)) { 1512 Slog.w(TAG, "Invalid recorder address:" + recorderAddress); 1513 announceClearTimerRecordingResult(recorderAddress, 1514 CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION); 1515 return; 1516 } 1517 1518 if (!checkTimerRecordingSource(sourceType, recordSource)) { 1519 Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource)); 1520 announceClearTimerRecordingResult(recorderAddress, 1521 CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE); 1522 return; 1523 } 1524 1525 sendClearTimerMessage(recorderAddress, sourceType, recordSource); 1526 } 1527 sendClearTimerMessage(final int recorderAddress, int sourceType, byte[] recordSource)1528 private void sendClearTimerMessage(final int recorderAddress, int sourceType, 1529 byte[] recordSource) { 1530 HdmiCecMessage message = null; 1531 switch (sourceType) { 1532 case TIMER_RECORDING_TYPE_DIGITAL: 1533 message = 1534 HdmiCecMessageBuilder.buildClearDigitalTimer( 1535 getDeviceInfo().getLogicalAddress(), recorderAddress, recordSource); 1536 break; 1537 case TIMER_RECORDING_TYPE_ANALOGUE: 1538 message = 1539 HdmiCecMessageBuilder.buildClearAnalogueTimer( 1540 getDeviceInfo().getLogicalAddress(), recorderAddress, recordSource); 1541 break; 1542 case TIMER_RECORDING_TYPE_EXTERNAL: 1543 message = 1544 HdmiCecMessageBuilder.buildClearExternalTimer( 1545 getDeviceInfo().getLogicalAddress(), recorderAddress, recordSource); 1546 break; 1547 default: 1548 Slog.w(TAG, "Invalid source type:" + recorderAddress); 1549 announceClearTimerRecordingResult(recorderAddress, 1550 CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE); 1551 return; 1552 1553 } 1554 mService.sendCecCommand(message, new SendMessageCallback() { 1555 @Override 1556 public void onSendCompleted(int error) { 1557 if (error != SendMessageResult.SUCCESS) { 1558 announceClearTimerRecordingResult(recorderAddress, 1559 CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE); 1560 } 1561 } 1562 }); 1563 } 1564 1565 @Override 1566 @Constants.HandleMessageResult handleMenuStatus(HdmiCecMessage message)1567 protected int handleMenuStatus(HdmiCecMessage message) { 1568 // Do nothing and just return true not to prevent from responding <Feature Abort>. 1569 return Constants.HANDLED; 1570 } 1571 1572 @Constants.RcProfile 1573 @Override getRcProfile()1574 protected int getRcProfile() { 1575 return Constants.RC_PROFILE_TV; 1576 } 1577 1578 @Override getRcFeatures()1579 protected List<Integer> getRcFeatures() { 1580 List<Integer> features = new ArrayList<>(); 1581 @HdmiControlManager.RcProfileTv int profile = mService.getHdmiCecConfig().getIntValue( 1582 HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV); 1583 features.add(profile); 1584 return features; 1585 } 1586 1587 @Override computeDeviceFeatures()1588 protected DeviceFeatures computeDeviceFeatures() { 1589 boolean hasArcPort = false; 1590 List<HdmiPortInfo> ports = mService.getPortInfo(); 1591 for (HdmiPortInfo port : ports) { 1592 if (isArcFeatureEnabled(port.getId())) { 1593 hasArcPort = true; 1594 break; 1595 } 1596 } 1597 1598 return DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder() 1599 .setRecordTvScreenSupport(FEATURE_SUPPORTED) 1600 .setArcTxSupport(hasArcPort ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED) 1601 .setSetAudioVolumeLevelSupport(FEATURE_SUPPORTED) 1602 .build(); 1603 } 1604 1605 @Override sendStandby(int deviceId)1606 protected void sendStandby(int deviceId) { 1607 HdmiDeviceInfo targetDevice = mService.getHdmiCecNetwork().getDeviceInfo(deviceId); 1608 if (targetDevice == null) { 1609 return; 1610 } 1611 int targetAddress = targetDevice.getLogicalAddress(); 1612 mService.sendCecCommand( 1613 HdmiCecMessageBuilder.buildStandby( 1614 getDeviceInfo().getLogicalAddress(), targetAddress)); 1615 } 1616 1617 @ServiceThreadOnly processAllDelayedMessages()1618 void processAllDelayedMessages() { 1619 assertRunOnServiceThread(); 1620 mDelayedMessageBuffer.processAllMessages(); 1621 } 1622 1623 @ServiceThreadOnly processDelayedMessages(int address)1624 void processDelayedMessages(int address) { 1625 assertRunOnServiceThread(); 1626 mDelayedMessageBuffer.processMessagesForDevice(address); 1627 } 1628 1629 @ServiceThreadOnly processDelayedActiveSource(int address)1630 void processDelayedActiveSource(int address) { 1631 assertRunOnServiceThread(); 1632 mDelayedMessageBuffer.processActiveSource(address); 1633 } 1634 1635 @Override dump(final IndentingPrintWriter pw)1636 protected void dump(final IndentingPrintWriter pw) { 1637 super.dump(pw); 1638 pw.println("mArcEstablished: " + mArcEstablished); 1639 pw.println("mArcFeatureEnabled: " + mArcFeatureEnabled); 1640 pw.println("mSystemAudioMute: " + mSystemAudioMute); 1641 pw.println("mSystemAudioControlFeatureEnabled: " + mSystemAudioControlFeatureEnabled); 1642 pw.println("mSkipRoutingControl: " + mSkipRoutingControl); 1643 pw.println("mPrevPortId: " + mPrevPortId); 1644 } 1645 } 1646