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