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