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 public 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.isCecControlEnabled()) { 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.isCecControlEnabled() 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.isCecControlEnabled()) { 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 DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, 686 new DeviceDiscoveryCallback() { 687 @Override 688 public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) { 689 for (HdmiDeviceInfo info : deviceInfos) { 690 mService.getHdmiCecNetwork().addCecDevice(info); 691 } 692 693 mSelectRequestBuffer.process(); 694 resetSelectRequestBuffer(); 695 696 List<HotplugDetectionAction> hotplugActions 697 = getActions(HotplugDetectionAction.class); 698 if (hotplugActions.isEmpty()) { 699 addAndStartAction( 700 new HotplugDetectionAction(HdmiCecLocalDeviceTv.this)); 701 } 702 703 List<PowerStatusMonitorAction> powerStatusActions 704 = getActions(PowerStatusMonitorAction.class); 705 if (powerStatusActions.isEmpty()) { 706 addAndStartAction( 707 new PowerStatusMonitorAction(HdmiCecLocalDeviceTv.this)); 708 } 709 710 HdmiDeviceInfo avr = getAvrDeviceInfo(); 711 if (avr != null) { 712 onNewAvrAdded(avr); 713 } else { 714 setSystemAudioMode(false); 715 } 716 } 717 }); 718 addAndStartAction(action); 719 } 720 721 @ServiceThreadOnly onNewAvrAdded(HdmiDeviceInfo avr)722 void onNewAvrAdded(HdmiDeviceInfo avr) { 723 assertRunOnServiceThread(); 724 addAndStartAction(new SystemAudioAutoInitiationAction(this, avr.getLogicalAddress())); 725 if (!isDirectConnectAddress(avr.getPhysicalAddress())) { 726 startArcAction(false); 727 } else if (isConnected(avr.getPortId()) && isArcFeatureEnabled(avr.getPortId()) 728 && !hasAction(SetArcTransmissionStateAction.class)) { 729 startArcAction(true); 730 } 731 } 732 733 @ServiceThreadOnly 734 // Seq #32 changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback)735 void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) { 736 assertRunOnServiceThread(); 737 if (!mService.isCecControlEnabled() || hasAction(DeviceDiscoveryAction.class)) { 738 setSystemAudioMode(false); 739 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); 740 return; 741 } 742 HdmiDeviceInfo avr = getAvrDeviceInfo(); 743 if (avr == null) { 744 setSystemAudioMode(false); 745 invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); 746 return; 747 } 748 749 addAndStartAction( 750 new SystemAudioActionFromTv(this, avr.getLogicalAddress(), enabled, callback)); 751 } 752 753 // # Seq 25 setSystemAudioMode(boolean on)754 void setSystemAudioMode(boolean on) { 755 if (!isSystemAudioControlFeatureEnabled() && on) { 756 HdmiLogger.debug("Cannot turn on system audio mode " 757 + "because the System Audio Control feature is disabled."); 758 return; 759 } 760 HdmiLogger.debug("System Audio Mode change[old:%b new:%b]", 761 mService.isSystemAudioActivated(), on); 762 updateAudioManagerForSystemAudio(on); 763 synchronized (mLock) { 764 if (mService.isSystemAudioActivated() != on) { 765 mService.setSystemAudioActivated(on); 766 mService.announceSystemAudioModeChange(on); 767 } 768 if (on && !mArcEstablished) { 769 startArcAction(true); 770 } else if (!on) { 771 startArcAction(false); 772 } 773 } 774 } 775 updateAudioManagerForSystemAudio(boolean on)776 private void updateAudioManagerForSystemAudio(boolean on) { 777 int device = mService.getAudioManager().setHdmiSystemAudioSupported(on); 778 HdmiLogger.debug("[A]UpdateSystemAudio mode[on=%b] output=[%X]", on, device); 779 } 780 isSystemAudioActivated()781 boolean isSystemAudioActivated() { 782 if (!hasSystemAudioDevice()) { 783 return false; 784 } 785 return mService.isSystemAudioActivated(); 786 } 787 788 @ServiceThreadOnly setSystemAudioControlFeatureEnabled(boolean enabled)789 void setSystemAudioControlFeatureEnabled(boolean enabled) { 790 assertRunOnServiceThread(); 791 synchronized (mLock) { 792 mSystemAudioControlFeatureEnabled = enabled; 793 } 794 if (hasSystemAudioDevice()) { 795 changeSystemAudioMode(enabled, null); 796 } 797 } 798 isSystemAudioControlFeatureEnabled()799 boolean isSystemAudioControlFeatureEnabled() { 800 synchronized (mLock) { 801 return mSystemAudioControlFeatureEnabled; 802 } 803 } 804 805 @ServiceThreadOnly enableArc(List<byte[]> supportedSads)806 void enableArc(List<byte[]> supportedSads) { 807 assertRunOnServiceThread(); 808 HdmiLogger.debug("Set Arc Status[old:%b new:true]", mArcEstablished); 809 810 enableAudioReturnChannel(true); 811 notifyArcStatusToAudioService(true, supportedSads); 812 mArcEstablished = true; 813 } 814 815 @ServiceThreadOnly disableArc()816 void disableArc() { 817 assertRunOnServiceThread(); 818 HdmiLogger.debug("Set Arc Status[old:%b new:false]", mArcEstablished); 819 820 enableAudioReturnChannel(false); 821 notifyArcStatusToAudioService(false, new ArrayList<>()); 822 mArcEstablished = false; 823 } 824 825 /** 826 * Switch hardware ARC circuit in the system. 827 */ 828 @ServiceThreadOnly enableAudioReturnChannel(boolean enabled)829 void enableAudioReturnChannel(boolean enabled) { 830 assertRunOnServiceThread(); 831 HdmiDeviceInfo avr = getAvrDeviceInfo(); 832 if (avr != null && avr.getPortId() != Constants.INVALID_PORT_ID) { 833 mService.enableAudioReturnChannel(avr.getPortId(), enabled); 834 } 835 } 836 837 @ServiceThreadOnly isConnected(int portId)838 boolean isConnected(int portId) { 839 assertRunOnServiceThread(); 840 return mService.isConnected(portId); 841 } 842 notifyArcStatusToAudioService(boolean enabled, List<byte[]> supportedSads)843 private void notifyArcStatusToAudioService(boolean enabled, List<byte[]> supportedSads) { 844 // Note that we don't set any name to ARC. 845 AudioDeviceAttributes attributes = new AudioDeviceAttributes( 846 AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI_ARC, "", "", 847 new ArrayList<AudioProfile>(), supportedSads.stream() 848 .map(sad -> new AudioDescriptor(AudioDescriptor.STANDARD_EDID, 849 AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE, sad)) 850 .collect(Collectors.toList())); 851 mService.getAudioManager().setWiredDeviceConnectionState(attributes, enabled ? 1 : 0); 852 } 853 854 /** 855 * Returns true if ARC is currently established on a certain port. 856 */ 857 @ServiceThreadOnly isArcEstablished()858 boolean isArcEstablished() { 859 assertRunOnServiceThread(); 860 if (mArcEstablished) { 861 for (int i = 0; i < mArcFeatureEnabled.size(); i++) { 862 if (mArcFeatureEnabled.valueAt(i)) return true; 863 } 864 } 865 return false; 866 } 867 868 @ServiceThreadOnly changeArcFeatureEnabled(int portId, boolean enabled)869 void changeArcFeatureEnabled(int portId, boolean enabled) { 870 assertRunOnServiceThread(); 871 if (mArcFeatureEnabled.get(portId) == enabled) { 872 return; 873 } 874 mArcFeatureEnabled.put(portId, enabled); 875 HdmiDeviceInfo avr = getAvrDeviceInfo(); 876 if (avr == null || avr.getPortId() != portId) { 877 return; 878 } 879 if (enabled && !mArcEstablished) { 880 startArcAction(true); 881 } else if (!enabled && mArcEstablished) { 882 startArcAction(false); 883 } 884 } 885 886 @ServiceThreadOnly isArcFeatureEnabled(int portId)887 boolean isArcFeatureEnabled(int portId) { 888 assertRunOnServiceThread(); 889 return mArcFeatureEnabled.get(portId); 890 } 891 892 @ServiceThreadOnly startArcAction(boolean enabled)893 void startArcAction(boolean enabled) { 894 startArcAction(enabled, null); 895 } 896 897 @ServiceThreadOnly startArcAction(boolean enabled, IHdmiControlCallback callback)898 void startArcAction(boolean enabled, IHdmiControlCallback callback) { 899 assertRunOnServiceThread(); 900 HdmiDeviceInfo info = getAvrDeviceInfo(); 901 if (info == null) { 902 Slog.w(TAG, "Failed to start arc action; No AVR device."); 903 invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); 904 return; 905 } 906 if (!canStartArcUpdateAction(info.getLogicalAddress(), enabled)) { 907 Slog.w(TAG, "Failed to start arc action; ARC configuration check failed."); 908 if (enabled && !isConnectedToArcPort(info.getPhysicalAddress())) { 909 displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT); 910 } 911 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); 912 return; 913 } 914 if (enabled && mService.earcBlocksArcConnection()) { 915 Slog.i(TAG, 916 "ARC connection blocked because eARC connection is established or being " 917 + "established."); 918 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); 919 return; 920 } 921 922 // Terminate opposite action and create an action with callback. 923 if (enabled) { 924 removeAction(RequestArcTerminationAction.class); 925 if (hasAction(RequestArcInitiationAction.class)) { 926 RequestArcInitiationAction existingInitiationAction = 927 getActions(RequestArcInitiationAction.class).get(0); 928 existingInitiationAction.addCallback(callback); 929 } else { 930 addAndStartAction( 931 new RequestArcInitiationAction(this, info.getLogicalAddress(), callback)); 932 } 933 } else { 934 removeAction(RequestArcInitiationAction.class); 935 if (hasAction(RequestArcTerminationAction.class)) { 936 RequestArcTerminationAction existingTerminationAction = 937 getActions(RequestArcTerminationAction.class).get(0); 938 existingTerminationAction.addCallback(callback); 939 } else { 940 addAndStartAction( 941 new RequestArcTerminationAction(this, info.getLogicalAddress(), callback)); 942 } 943 } 944 } 945 isDirectConnectAddress(int physicalAddress)946 private boolean isDirectConnectAddress(int physicalAddress) { 947 return (physicalAddress & Constants.ROUTING_PATH_TOP_MASK) == physicalAddress; 948 } 949 setAudioStatus(boolean mute, int volume)950 void setAudioStatus(boolean mute, int volume) { 951 if (!isSystemAudioActivated() || mService.getHdmiCecVolumeControl() 952 == HdmiControlManager.VOLUME_CONTROL_DISABLED) { 953 return; 954 } 955 synchronized (mLock) { 956 mSystemAudioMute = mute; 957 mSystemAudioVolume = volume; 958 displayOsd(HdmiControlManager.OSD_MESSAGE_AVR_VOLUME_CHANGED, 959 mute ? HdmiControlManager.AVR_VOLUME_MUTED : volume); 960 } 961 } 962 963 @ServiceThreadOnly changeVolume(int curVolume, int delta, int maxVolume)964 void changeVolume(int curVolume, int delta, int maxVolume) { 965 assertRunOnServiceThread(); 966 if (getAvrDeviceInfo() == null) { 967 // On initialization process, getAvrDeviceInfo() may return null and cause exception 968 return; 969 } 970 if (delta == 0 || !isSystemAudioActivated() || mService.getHdmiCecVolumeControl() 971 == HdmiControlManager.VOLUME_CONTROL_DISABLED) { 972 return; 973 } 974 975 int targetVolume = curVolume + delta; 976 int cecVolume = VolumeControlAction.scaleToCecVolume(targetVolume, maxVolume); 977 synchronized (mLock) { 978 // If new volume is the same as current system audio volume, just ignore it. 979 // Note that UNKNOWN_VOLUME is not in range of cec volume scale. 980 if (cecVolume == mSystemAudioVolume) { 981 // Update tv volume with system volume value. 982 mService.setAudioStatus(false, 983 VolumeControlAction.scaleToCustomVolume(mSystemAudioVolume, maxVolume)); 984 return; 985 } 986 } 987 988 List<VolumeControlAction> actions = getActions(VolumeControlAction.class); 989 if (actions.isEmpty()) { 990 addAndStartAction(new VolumeControlAction(this, 991 getAvrDeviceInfo().getLogicalAddress(), delta > 0)); 992 } else { 993 actions.get(0).handleVolumeChange(delta > 0); 994 } 995 } 996 997 @ServiceThreadOnly changeMute(boolean mute)998 void changeMute(boolean mute) { 999 assertRunOnServiceThread(); 1000 if (getAvrDeviceInfo() == null || mService.getHdmiCecVolumeControl() 1001 == HdmiControlManager.VOLUME_CONTROL_DISABLED) { 1002 // On initialization process, getAvrDeviceInfo() may return null and cause exception 1003 return; 1004 } 1005 HdmiLogger.debug("[A]:Change mute:%b", mute); 1006 synchronized (mLock) { 1007 if (mSystemAudioMute == mute) { 1008 HdmiLogger.debug("No need to change mute."); 1009 return; 1010 } 1011 } 1012 if (!isSystemAudioActivated()) { 1013 HdmiLogger.debug("[A]:System audio is not activated."); 1014 return; 1015 } 1016 1017 // Remove existing volume action. 1018 removeAction(VolumeControlAction.class); 1019 sendUserControlPressedAndReleased(getAvrDeviceInfo().getLogicalAddress(), 1020 HdmiCecKeycode.getMuteKey(mute)); 1021 } 1022 1023 @Override 1024 @ServiceThreadOnly 1025 @Constants.HandleMessageResult handleInitiateArc(HdmiCecMessage message)1026 protected int handleInitiateArc(HdmiCecMessage message) { 1027 assertRunOnServiceThread(); 1028 1029 if (mService.earcBlocksArcConnection()) { 1030 Slog.i(TAG, 1031 "ARC connection blocked because eARC connection is established or being " 1032 + "established."); 1033 return Constants.ABORT_NOT_IN_CORRECT_MODE; 1034 } 1035 1036 if (!canStartArcUpdateAction(message.getSource(), true)) { 1037 HdmiDeviceInfo avrDeviceInfo = getAvrDeviceInfo(); 1038 if (avrDeviceInfo == null) { 1039 // AVR may not have been discovered yet. Delay the message processing. 1040 mDelayedMessageBuffer.add(message); 1041 return Constants.HANDLED; 1042 } 1043 if (!isConnectedToArcPort(avrDeviceInfo.getPhysicalAddress())) { 1044 displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT); 1045 } 1046 return Constants.ABORT_REFUSED; 1047 } 1048 1049 // In case where <Initiate Arc> is started by <Request ARC Initiation>, this message is 1050 // handled in RequestArcInitiationAction as well. 1051 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 1052 message.getSource(), true); 1053 addAndStartAction(action); 1054 return Constants.HANDLED; 1055 } 1056 canStartArcUpdateAction(int avrAddress, boolean enabled)1057 private boolean canStartArcUpdateAction(int avrAddress, boolean enabled) { 1058 HdmiDeviceInfo avr = getAvrDeviceInfo(); 1059 if (avr != null 1060 && (avrAddress == avr.getLogicalAddress()) 1061 && isConnectedToArcPort(avr.getPhysicalAddress())) { 1062 if (enabled) { 1063 return isConnected(avr.getPortId()) 1064 && isArcFeatureEnabled(avr.getPortId()) 1065 && isDirectConnectAddress(avr.getPhysicalAddress()); 1066 } else { 1067 return true; 1068 } 1069 } else { 1070 return false; 1071 } 1072 } 1073 1074 @Override 1075 @ServiceThreadOnly 1076 @Constants.HandleMessageResult handleTerminateArc(HdmiCecMessage message)1077 protected int handleTerminateArc(HdmiCecMessage message) { 1078 assertRunOnServiceThread(); 1079 if (mService .isPowerStandbyOrTransient()) { 1080 disableArc(); 1081 return Constants.HANDLED; 1082 } 1083 // Do not check ARC configuration since the AVR might have been already removed. 1084 // In case where <Terminate Arc> is started by <Request ARC Termination>, this 1085 // message is handled in RequestArcTerminationAction as well. 1086 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 1087 message.getSource(), false); 1088 addAndStartAction(action); 1089 return Constants.HANDLED; 1090 } 1091 1092 @Override 1093 @ServiceThreadOnly 1094 @Constants.HandleMessageResult handleSetSystemAudioMode(HdmiCecMessage message)1095 protected int handleSetSystemAudioMode(HdmiCecMessage message) { 1096 assertRunOnServiceThread(); 1097 boolean systemAudioStatus = HdmiUtils.parseCommandParamSystemAudioStatus(message); 1098 if (!isMessageForSystemAudio(message)) { 1099 if (getAvrDeviceInfo() == null) { 1100 // AVR may not have been discovered yet. Delay the message processing. 1101 mDelayedMessageBuffer.add(message); 1102 } else { 1103 HdmiLogger.warning("Invalid <Set System Audio Mode> message:" + message); 1104 return Constants.ABORT_REFUSED; 1105 } 1106 } else if (systemAudioStatus && !isSystemAudioControlFeatureEnabled()) { 1107 HdmiLogger.debug("Ignoring <Set System Audio Mode> message " 1108 + "because the System Audio Control feature is disabled: %s", message); 1109 return Constants.ABORT_REFUSED; 1110 } 1111 removeAction(SystemAudioAutoInitiationAction.class); 1112 SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this, 1113 message.getSource(), systemAudioStatus, null); 1114 addAndStartAction(action); 1115 return Constants.HANDLED; 1116 } 1117 1118 @Override 1119 @ServiceThreadOnly 1120 @Constants.HandleMessageResult handleSystemAudioModeStatus(HdmiCecMessage message)1121 protected int handleSystemAudioModeStatus(HdmiCecMessage message) { 1122 assertRunOnServiceThread(); 1123 if (!isMessageForSystemAudio(message)) { 1124 HdmiLogger.warning("Invalid <System Audio Mode Status> message:" + message); 1125 // Ignore this message. 1126 return Constants.HANDLED; 1127 } 1128 boolean tvSystemAudioMode = isSystemAudioControlFeatureEnabled(); 1129 boolean avrSystemAudioMode = HdmiUtils.parseCommandParamSystemAudioStatus(message); 1130 // Set System Audio Mode according to TV's settings. 1131 // Handle <System Audio Mode Status> here only when 1132 // SystemAudioAutoInitiationAction timeout 1133 HdmiDeviceInfo avr = getAvrDeviceInfo(); 1134 if (avr == null) { 1135 setSystemAudioMode(false); 1136 } else if (avrSystemAudioMode != tvSystemAudioMode) { 1137 addAndStartAction(new SystemAudioActionFromTv(this, avr.getLogicalAddress(), 1138 tvSystemAudioMode, null)); 1139 } else { 1140 setSystemAudioMode(tvSystemAudioMode); 1141 } 1142 1143 return Constants.HANDLED; 1144 } 1145 1146 // Seq #53 1147 @Override 1148 @ServiceThreadOnly 1149 @Constants.HandleMessageResult handleRecordTvScreen(HdmiCecMessage message)1150 protected int handleRecordTvScreen(HdmiCecMessage message) { 1151 List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class); 1152 if (!actions.isEmpty()) { 1153 // Assumes only one OneTouchRecordAction. 1154 OneTouchRecordAction action = actions.get(0); 1155 if (action.getRecorderAddress() != message.getSource()) { 1156 announceOneTouchRecordResult( 1157 message.getSource(), 1158 HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS); 1159 } 1160 // The default behavior of <Record TV Screen> is replying <Feature Abort> with 1161 // "Cannot provide source". 1162 return Constants.ABORT_CANNOT_PROVIDE_SOURCE; 1163 } 1164 1165 int recorderAddress = message.getSource(); 1166 byte[] recordSource = mService.invokeRecordRequestListener(recorderAddress); 1167 return startOneTouchRecord(recorderAddress, recordSource); 1168 } 1169 1170 @Override 1171 @Constants.HandleMessageResult handleTimerClearedStatus(HdmiCecMessage message)1172 protected int handleTimerClearedStatus(HdmiCecMessage message) { 1173 byte[] params = message.getParams(); 1174 int timerClearedStatusData = params[0] & 0xFF; 1175 announceTimerRecordingResult(message.getSource(), timerClearedStatusData); 1176 return Constants.HANDLED; 1177 } 1178 1179 @Override 1180 @Constants.HandleMessageResult handleSetAudioVolumeLevel(SetAudioVolumeLevelMessage message)1181 protected int handleSetAudioVolumeLevel(SetAudioVolumeLevelMessage message) { 1182 // <Set Audio Volume Level> should only be sent to the System Audio device, so we don't 1183 // handle it when System Audio Mode is enabled. 1184 if (mService.isSystemAudioActivated()) { 1185 return Constants.ABORT_NOT_IN_CORRECT_MODE; 1186 } else { 1187 mService.setStreamMusicVolume(message.getAudioVolumeLevel(), 0); 1188 return Constants.HANDLED; 1189 } 1190 } 1191 announceOneTouchRecordResult(int recorderAddress, int result)1192 void announceOneTouchRecordResult(int recorderAddress, int result) { 1193 mService.invokeOneTouchRecordResult(recorderAddress, result); 1194 } 1195 announceTimerRecordingResult(int recorderAddress, int result)1196 void announceTimerRecordingResult(int recorderAddress, int result) { 1197 mService.invokeTimerRecordingResult(recorderAddress, result); 1198 } 1199 announceClearTimerRecordingResult(int recorderAddress, int result)1200 void announceClearTimerRecordingResult(int recorderAddress, int result) { 1201 mService.invokeClearTimerRecordingResult(recorderAddress, result); 1202 } 1203 isMessageForSystemAudio(HdmiCecMessage message)1204 private boolean isMessageForSystemAudio(HdmiCecMessage message) { 1205 return mService.isCecControlEnabled() 1206 && message.getSource() == Constants.ADDR_AUDIO_SYSTEM 1207 && (message.getDestination() == Constants.ADDR_TV 1208 || message.getDestination() == Constants.ADDR_BROADCAST) 1209 && getAvrDeviceInfo() != null; 1210 } 1211 1212 @Nullable 1213 @ServiceThreadOnly getAvrDeviceInfo()1214 HdmiDeviceInfo getAvrDeviceInfo() { 1215 assertRunOnServiceThread(); 1216 return mService.getHdmiCecNetwork().getCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); 1217 } 1218 hasSystemAudioDevice()1219 boolean hasSystemAudioDevice() { 1220 return getSafeAvrDeviceInfo() != null; 1221 } 1222 1223 @Nullable getSafeAvrDeviceInfo()1224 HdmiDeviceInfo getSafeAvrDeviceInfo() { 1225 return mService.getHdmiCecNetwork().getSafeCecDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); 1226 } 1227 1228 /** 1229 * Returns the audio output device used for System Audio Mode. 1230 */ getSystemAudioOutputDevice()1231 AudioDeviceAttributes getSystemAudioOutputDevice() { 1232 return HdmiControlService.AUDIO_OUTPUT_DEVICE_HDMI_ARC; 1233 } 1234 1235 1236 @ServiceThreadOnly handleRemoveActiveRoutingPath(int path)1237 void handleRemoveActiveRoutingPath(int path) { 1238 assertRunOnServiceThread(); 1239 // Seq #23 1240 if (isTailOfActivePath(path, getActivePath())) { 1241 int newPath = mService.portIdToPath(getActivePortId()); 1242 startRoutingControl(getActivePath(), newPath, null); 1243 } 1244 } 1245 1246 /** 1247 * Launch routing control process. 1248 * 1249 * @param routingForBootup true if routing control is initiated due to One Touch Play 1250 * or TV power on 1251 */ 1252 @ServiceThreadOnly launchRoutingControl(boolean routingForBootup)1253 void launchRoutingControl(boolean routingForBootup) { 1254 assertRunOnServiceThread(); 1255 // Seq #24 1256 if (getActivePortId() != Constants.INVALID_PORT_ID 1257 && getActivePortId() != Constants.CEC_SWITCH_HOME) { 1258 if (!routingForBootup && !isProhibitMode()) { 1259 int newPath = mService.portIdToPath(getActivePortId()); 1260 setActivePath(newPath); 1261 startRoutingControl(getActivePath(), newPath, null); 1262 } 1263 } else { 1264 int activePath = mService.getPhysicalAddress(); 1265 setActivePath(activePath); 1266 if (!routingForBootup 1267 && !mDelayedMessageBuffer.isBuffered(Constants.MESSAGE_ACTIVE_SOURCE)) { 1268 mService.sendCecCommand( 1269 HdmiCecMessageBuilder.buildActiveSource( 1270 getDeviceInfo().getLogicalAddress(), activePath)); 1271 } 1272 } 1273 } 1274 1275 @Override 1276 @ServiceThreadOnly onHotplug(int portId, boolean connected)1277 void onHotplug(int portId, boolean connected) { 1278 assertRunOnServiceThread(); 1279 1280 if (!connected) { 1281 mService.getHdmiCecNetwork().removeCecSwitches(portId); 1282 } 1283 1284 // Turning System Audio Mode off when the AVR is unlugged or standby. 1285 // When the device is not unplugged but reawaken from standby, we check if the System 1286 // Audio Control Feature is enabled or not then decide if turning SAM on/off accordingly. 1287 if (getAvrDeviceInfo() != null && portId == getAvrDeviceInfo().getPortId()) { 1288 HdmiLogger.debug("Port ID:%d, 5v=%b", portId, connected); 1289 if (!connected) { 1290 setSystemAudioMode(false); 1291 } else { 1292 onNewAvrAdded(getAvrDeviceInfo()); 1293 } 1294 } 1295 1296 // Tv device will have permanent HotplugDetectionAction. 1297 List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class); 1298 if (!hotplugActions.isEmpty()) { 1299 // Note that hotplug action is single action running on a machine. 1300 // "pollAllDevicesNow" cleans up timer and start poll action immediately. 1301 // It covers seq #40, #43. 1302 hotplugActions.get(0).pollAllDevicesNow(); 1303 } 1304 } 1305 1306 @ServiceThreadOnly getAutoWakeup()1307 boolean getAutoWakeup() { 1308 assertRunOnServiceThread(); 1309 return mService.getHdmiCecConfig().getIntValue( 1310 HdmiControlManager.CEC_SETTING_NAME_TV_WAKE_ON_ONE_TOUCH_PLAY) 1311 == HdmiControlManager.TV_WAKE_ON_ONE_TOUCH_PLAY_ENABLED; 1312 } 1313 1314 @Override 1315 @ServiceThreadOnly disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback)1316 protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) { 1317 assertRunOnServiceThread(); 1318 mService.unregisterTvInputCallback(mTvInputCallback); 1319 // Remove any repeated working actions. 1320 // HotplugDetectionAction will be reinstated during the wake up process. 1321 // HdmiControlService.onWakeUp() -> initializeLocalDevices() -> 1322 // LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery(). 1323 removeAction(DeviceDiscoveryAction.class); 1324 removeAction(HotplugDetectionAction.class); 1325 removeAction(PowerStatusMonitorAction.class); 1326 // Remove recording actions. 1327 removeAction(OneTouchRecordAction.class); 1328 removeAction(TimerRecordingAction.class); 1329 removeAction(NewDeviceAction.class); 1330 removeAction(AbsoluteVolumeAudioStatusAction.class); 1331 1332 disableSystemAudioIfExist(); 1333 disableArcIfExist(); 1334 1335 super.disableDevice(initiatedByCec, callback); 1336 clearDeviceInfoList(); 1337 getActiveSource().invalidate(); 1338 setActivePath(Constants.INVALID_PHYSICAL_ADDRESS); 1339 checkIfPendingActionsCleared(); 1340 } 1341 1342 @ServiceThreadOnly disableSystemAudioIfExist()1343 private void disableSystemAudioIfExist() { 1344 assertRunOnServiceThread(); 1345 if (getAvrDeviceInfo() == null) { 1346 return; 1347 } 1348 1349 // Seq #31. 1350 removeAction(SystemAudioActionFromAvr.class); 1351 removeAction(SystemAudioActionFromTv.class); 1352 removeAction(SystemAudioAutoInitiationAction.class); 1353 removeAction(VolumeControlAction.class); 1354 1355 if (!mService.isCecControlEnabled()) { 1356 setSystemAudioMode(false); 1357 } 1358 } 1359 1360 @ServiceThreadOnly forceDisableArcOnAllPins()1361 private void forceDisableArcOnAllPins() { 1362 List<HdmiPortInfo> ports = mService.getPortInfo(); 1363 for (HdmiPortInfo port : ports) { 1364 if (isArcFeatureEnabled(port.getId())) { 1365 mService.enableAudioReturnChannel(port.getId(), false); 1366 } 1367 } 1368 } 1369 1370 @ServiceThreadOnly disableArcIfExist()1371 private void disableArcIfExist() { 1372 assertRunOnServiceThread(); 1373 HdmiDeviceInfo avr = getAvrDeviceInfo(); 1374 if (avr == null) { 1375 return; 1376 } 1377 1378 // Seq #44. 1379 removeAllRunningArcAction(); 1380 if (!hasAction(RequestArcTerminationAction.class) && isArcEstablished()) { 1381 addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress())); 1382 } 1383 1384 // Disable ARC Pin earlier, prevent the case where AVR doesn't send <Terminate ARC> in time 1385 forceDisableArcOnAllPins(); 1386 } 1387 1388 @ServiceThreadOnly removeAllRunningArcAction()1389 private void removeAllRunningArcAction() { 1390 // Running or pending actions make TV fail to broadcast <Standby> to connected devices 1391 removeAction(RequestArcTerminationAction.class); 1392 removeAction(RequestArcInitiationAction.class); 1393 removeAction(SetArcTransmissionStateAction.class); 1394 } 1395 1396 @Override 1397 @ServiceThreadOnly onStandby(boolean initiatedByCec, int standbyAction)1398 protected void onStandby(boolean initiatedByCec, int standbyAction) { 1399 assertRunOnServiceThread(); 1400 // Seq #11 1401 if (!mService.isCecControlEnabled()) { 1402 return; 1403 } 1404 boolean sendStandbyOnSleep = 1405 mService.getHdmiCecConfig().getIntValue( 1406 HdmiControlManager.CEC_SETTING_NAME_TV_SEND_STANDBY_ON_SLEEP) 1407 == HdmiControlManager.TV_SEND_STANDBY_ON_SLEEP_ENABLED; 1408 if (!initiatedByCec && sendStandbyOnSleep) { 1409 mService.sendCecCommand( 1410 HdmiCecMessageBuilder.buildStandby( 1411 getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST)); 1412 } 1413 } 1414 isProhibitMode()1415 boolean isProhibitMode() { 1416 return mService.isProhibitMode(); 1417 } 1418 isPowerStandbyOrTransient()1419 boolean isPowerStandbyOrTransient() { 1420 return mService.isPowerStandbyOrTransient(); 1421 } 1422 1423 @ServiceThreadOnly displayOsd(int messageId)1424 void displayOsd(int messageId) { 1425 assertRunOnServiceThread(); 1426 mService.displayOsd(messageId); 1427 } 1428 1429 @ServiceThreadOnly displayOsd(int messageId, int extra)1430 void displayOsd(int messageId, int extra) { 1431 assertRunOnServiceThread(); 1432 mService.displayOsd(messageId, extra); 1433 } 1434 1435 // Seq #54 and #55 1436 @ServiceThreadOnly 1437 @Constants.HandleMessageResult startOneTouchRecord(int recorderAddress, byte[] recordSource)1438 int startOneTouchRecord(int recorderAddress, byte[] recordSource) { 1439 assertRunOnServiceThread(); 1440 if (!mService.isCecControlEnabled()) { 1441 Slog.w(TAG, "Can not start one touch record. CEC control is disabled."); 1442 announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED); 1443 return Constants.ABORT_NOT_IN_CORRECT_MODE; 1444 } 1445 1446 if (!checkRecorder(recorderAddress)) { 1447 Slog.w(TAG, "Invalid recorder address:" + recorderAddress); 1448 announceOneTouchRecordResult(recorderAddress, 1449 ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION); 1450 return Constants.ABORT_NOT_IN_CORRECT_MODE; 1451 } 1452 1453 if (!checkRecordSource(recordSource)) { 1454 Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource)); 1455 announceOneTouchRecordResult(recorderAddress, 1456 ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN); 1457 return Constants.ABORT_CANNOT_PROVIDE_SOURCE; 1458 } 1459 1460 addAndStartAction(new OneTouchRecordAction(this, recorderAddress, recordSource)); 1461 Slog.i(TAG, "Start new [One Touch Record]-Target:" + recorderAddress + ", recordSource:" 1462 + Arrays.toString(recordSource)); 1463 return Constants.HANDLED; 1464 } 1465 1466 @ServiceThreadOnly stopOneTouchRecord(int recorderAddress)1467 void stopOneTouchRecord(int recorderAddress) { 1468 assertRunOnServiceThread(); 1469 if (!mService.isCecControlEnabled()) { 1470 Slog.w(TAG, "Can not stop one touch record. CEC control is disabled."); 1471 announceOneTouchRecordResult(recorderAddress, ONE_TOUCH_RECORD_CEC_DISABLED); 1472 return; 1473 } 1474 1475 if (!checkRecorder(recorderAddress)) { 1476 Slog.w(TAG, "Invalid recorder address:" + recorderAddress); 1477 announceOneTouchRecordResult(recorderAddress, 1478 ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION); 1479 return; 1480 } 1481 1482 // Remove one touch record action so that other one touch record can be started. 1483 removeAction(OneTouchRecordAction.class); 1484 mService.sendCecCommand( 1485 HdmiCecMessageBuilder.buildRecordOff( 1486 getDeviceInfo().getLogicalAddress(), recorderAddress)); 1487 Slog.i(TAG, "Stop [One Touch Record]-Target:" + recorderAddress); 1488 } 1489 checkRecorder(int recorderAddress)1490 private boolean checkRecorder(int recorderAddress) { 1491 HdmiDeviceInfo device = mService.getHdmiCecNetwork().getCecDeviceInfo(recorderAddress); 1492 return (device != null) && (HdmiUtils.isEligibleAddressForDevice( 1493 HdmiDeviceInfo.DEVICE_RECORDER, recorderAddress)); 1494 } 1495 checkRecordSource(byte[] recordSource)1496 private boolean checkRecordSource(byte[] recordSource) { 1497 return (recordSource != null) && HdmiRecordSources.checkRecordSource(recordSource); 1498 } 1499 1500 @ServiceThreadOnly startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource)1501 void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) { 1502 assertRunOnServiceThread(); 1503 if (!mService.isCecControlEnabled()) { 1504 Slog.w(TAG, "Can not start one touch record. CEC control is disabled."); 1505 announceTimerRecordingResult(recorderAddress, 1506 TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED); 1507 return; 1508 } 1509 1510 if (!checkRecorder(recorderAddress)) { 1511 Slog.w(TAG, "Invalid recorder address:" + recorderAddress); 1512 announceTimerRecordingResult(recorderAddress, 1513 TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION); 1514 return; 1515 } 1516 1517 if (!checkTimerRecordingSource(sourceType, recordSource)) { 1518 Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource)); 1519 announceTimerRecordingResult( 1520 recorderAddress, 1521 TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE); 1522 return; 1523 } 1524 1525 addAndStartAction( 1526 new TimerRecordingAction(this, recorderAddress, sourceType, recordSource)); 1527 Slog.i(TAG, "Start [Timer Recording]-Target:" + recorderAddress + ", SourceType:" 1528 + sourceType + ", RecordSource:" + Arrays.toString(recordSource)); 1529 } 1530 checkTimerRecordingSource(int sourceType, byte[] recordSource)1531 private boolean checkTimerRecordingSource(int sourceType, byte[] recordSource) { 1532 return (recordSource != null) 1533 && HdmiTimerRecordSources.checkTimerRecordSource(sourceType, recordSource); 1534 } 1535 1536 @ServiceThreadOnly clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource)1537 void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) { 1538 assertRunOnServiceThread(); 1539 if (!mService.isCecControlEnabled()) { 1540 Slog.w(TAG, "Can not start one touch record. CEC control is disabled."); 1541 announceClearTimerRecordingResult(recorderAddress, CLEAR_TIMER_STATUS_CEC_DISABLE); 1542 return; 1543 } 1544 1545 if (!checkRecorder(recorderAddress)) { 1546 Slog.w(TAG, "Invalid recorder address:" + recorderAddress); 1547 announceClearTimerRecordingResult(recorderAddress, 1548 CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION); 1549 return; 1550 } 1551 1552 if (!checkTimerRecordingSource(sourceType, recordSource)) { 1553 Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource)); 1554 announceClearTimerRecordingResult(recorderAddress, 1555 CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE); 1556 return; 1557 } 1558 1559 sendClearTimerMessage(recorderAddress, sourceType, recordSource); 1560 } 1561 sendClearTimerMessage(final int recorderAddress, int sourceType, byte[] recordSource)1562 private void sendClearTimerMessage(final int recorderAddress, int sourceType, 1563 byte[] recordSource) { 1564 HdmiCecMessage message = null; 1565 switch (sourceType) { 1566 case TIMER_RECORDING_TYPE_DIGITAL: 1567 message = 1568 HdmiCecMessageBuilder.buildClearDigitalTimer( 1569 getDeviceInfo().getLogicalAddress(), recorderAddress, recordSource); 1570 break; 1571 case TIMER_RECORDING_TYPE_ANALOGUE: 1572 message = 1573 HdmiCecMessageBuilder.buildClearAnalogueTimer( 1574 getDeviceInfo().getLogicalAddress(), recorderAddress, recordSource); 1575 break; 1576 case TIMER_RECORDING_TYPE_EXTERNAL: 1577 message = 1578 HdmiCecMessageBuilder.buildClearExternalTimer( 1579 getDeviceInfo().getLogicalAddress(), recorderAddress, recordSource); 1580 break; 1581 default: 1582 Slog.w(TAG, "Invalid source type:" + recorderAddress); 1583 announceClearTimerRecordingResult(recorderAddress, 1584 CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE); 1585 return; 1586 1587 } 1588 mService.sendCecCommand(message, new SendMessageCallback() { 1589 @Override 1590 public void onSendCompleted(int error) { 1591 if (error != SendMessageResult.SUCCESS) { 1592 announceClearTimerRecordingResult(recorderAddress, 1593 CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE); 1594 } 1595 } 1596 }); 1597 } 1598 1599 @Override 1600 @Constants.HandleMessageResult handleMenuStatus(HdmiCecMessage message)1601 protected int handleMenuStatus(HdmiCecMessage message) { 1602 // Do nothing and just return true not to prevent from responding <Feature Abort>. 1603 return Constants.HANDLED; 1604 } 1605 1606 @Constants.RcProfile 1607 @Override getRcProfile()1608 protected int getRcProfile() { 1609 return Constants.RC_PROFILE_TV; 1610 } 1611 1612 @Override getRcFeatures()1613 protected List<Integer> getRcFeatures() { 1614 List<Integer> features = new ArrayList<>(); 1615 @HdmiControlManager.RcProfileTv int profile = mService.getHdmiCecConfig().getIntValue( 1616 HdmiControlManager.CEC_SETTING_NAME_RC_PROFILE_TV); 1617 features.add(profile); 1618 return features; 1619 } 1620 1621 @Override computeDeviceFeatures()1622 protected DeviceFeatures computeDeviceFeatures() { 1623 boolean hasArcPort = false; 1624 List<HdmiPortInfo> ports = mService.getPortInfo(); 1625 for (HdmiPortInfo port : ports) { 1626 if (isArcFeatureEnabled(port.getId())) { 1627 hasArcPort = true; 1628 break; 1629 } 1630 } 1631 1632 return DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder() 1633 .setRecordTvScreenSupport(FEATURE_SUPPORTED) 1634 .setArcTxSupport(hasArcPort ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED) 1635 .setSetAudioVolumeLevelSupport(FEATURE_SUPPORTED) 1636 .build(); 1637 } 1638 1639 @Override sendStandby(int deviceId)1640 protected void sendStandby(int deviceId) { 1641 HdmiDeviceInfo targetDevice = mService.getHdmiCecNetwork().getDeviceInfo(deviceId); 1642 if (targetDevice == null) { 1643 return; 1644 } 1645 int targetAddress = targetDevice.getLogicalAddress(); 1646 mService.sendCecCommand( 1647 HdmiCecMessageBuilder.buildStandby( 1648 getDeviceInfo().getLogicalAddress(), targetAddress)); 1649 } 1650 1651 @ServiceThreadOnly processAllDelayedMessages()1652 void processAllDelayedMessages() { 1653 assertRunOnServiceThread(); 1654 mDelayedMessageBuffer.processAllMessages(); 1655 } 1656 1657 @ServiceThreadOnly processDelayedMessages(int address)1658 void processDelayedMessages(int address) { 1659 assertRunOnServiceThread(); 1660 mDelayedMessageBuffer.processMessagesForDevice(address); 1661 } 1662 1663 @ServiceThreadOnly processDelayedActiveSource(int address)1664 void processDelayedActiveSource(int address) { 1665 assertRunOnServiceThread(); 1666 mDelayedMessageBuffer.processActiveSource(address); 1667 } 1668 1669 @Override dump(final IndentingPrintWriter pw)1670 protected void dump(final IndentingPrintWriter pw) { 1671 super.dump(pw); 1672 pw.println("mArcEstablished: " + mArcEstablished); 1673 pw.println("mArcFeatureEnabled: " + mArcFeatureEnabled); 1674 pw.println("mSystemAudioMute: " + mSystemAudioMute); 1675 pw.println("mSystemAudioControlFeatureEnabled: " + mSystemAudioControlFeatureEnabled); 1676 pw.println("mSkipRoutingControl: " + mSkipRoutingControl); 1677 pw.println("mPrevPortId: " + mPrevPortId); 1678 } 1679 } 1680