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