HdmiCecLocalDeviceTv.java revision fc44e4e03c5f6486efb7457965dcf7eaf36bc971
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 17package com.android.server.hdmi; 18 19import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CEC_DISABLE; 20import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION; 21import static android.hardware.hdmi.HdmiControlManager.CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE; 22import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CEC_DISABLED; 23import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION; 24import static android.hardware.hdmi.HdmiControlManager.ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN; 25import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED; 26import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION; 27import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE; 28import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_ANALOGUE; 29import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_DIGITAL; 30import static android.hardware.hdmi.HdmiControlManager.TIMER_RECORDING_TYPE_EXTERNAL; 31 32import android.content.Intent; 33import android.hardware.hdmi.HdmiCecDeviceInfo; 34import android.hardware.hdmi.HdmiControlManager; 35import android.hardware.hdmi.HdmiRecordSources; 36import android.hardware.hdmi.HdmiTimerRecordSources; 37import android.hardware.hdmi.IHdmiControlCallback; 38import android.media.AudioManager; 39import android.media.AudioSystem; 40import android.os.RemoteException; 41import android.os.SystemProperties; 42import android.os.UserHandle; 43import android.provider.Settings.Global; 44import android.util.Slog; 45import android.util.SparseArray; 46 47import com.android.internal.annotations.GuardedBy; 48import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback; 49import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; 50import com.android.server.hdmi.HdmiControlService.SendMessageCallback; 51 52import java.io.UnsupportedEncodingException; 53import java.util.ArrayList; 54import java.util.Arrays; 55import java.util.Collections; 56import java.util.List; 57import java.util.Locale; 58 59/** 60 * Represent a logical device of type TV residing in Android system. 61 */ 62final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { 63 private static final String TAG = "HdmiCecLocalDeviceTv"; 64 65 // Whether ARC is available or not. "true" means that ARC is established between TV and 66 // AVR as audio receiver. 67 @ServiceThreadOnly 68 private boolean mArcEstablished = false; 69 70 // Whether ARC feature is enabled or not. 71 private boolean mArcFeatureEnabled = false; 72 73 // Whether System audio mode is activated or not. 74 // This becomes true only when all system audio sequences are finished. 75 @GuardedBy("mLock") 76 private boolean mSystemAudioActivated = false; 77 78 // The previous port id (input) before switching to the new one. This is remembered in order to 79 // be able to switch to it upon receiving <Inactive Source> from currently active source. 80 // This remains valid only when the active source was switched via one touch play operation 81 // (either by TV or source device). Manual port switching invalidates this value to 82 // Constants.PORT_INVALID, for which case <Inactive Source> does not do anything. 83 @GuardedBy("mLock") 84 private int mPrevPortId; 85 86 @GuardedBy("mLock") 87 private int mSystemAudioVolume = Constants.UNKNOWN_VOLUME; 88 89 @GuardedBy("mLock") 90 private boolean mSystemAudioMute = false; 91 92 // Copy of mDeviceInfos to guarantee thread-safety. 93 @GuardedBy("mLock") 94 private List<HdmiCecDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList(); 95 // All external cec input(source) devices. Does not include system audio device. 96 @GuardedBy("mLock") 97 private List<HdmiCecDeviceInfo> mSafeExternalInputs = Collections.emptyList(); 98 99 // Map-like container of all cec devices including local ones. 100 // A logical address of device is used as key of container. 101 // This is not thread-safe. For external purpose use mSafeDeviceInfos. 102 private final SparseArray<HdmiCecDeviceInfo> mDeviceInfos = new SparseArray<>(); 103 104 // If true, TV going to standby mode puts other devices also to standby. 105 private boolean mAutoDeviceOff; 106 107 // If true, TV wakes itself up when receiving <Text/Image View On>. 108 private boolean mAutoWakeup; 109 110 private final HdmiCecStandbyModeHandler mStandbyHandler; 111 112 HdmiCecLocalDeviceTv(HdmiControlService service) { 113 super(service, HdmiCecDeviceInfo.DEVICE_TV); 114 mPrevPortId = Constants.INVALID_PORT_ID; 115 mAutoDeviceOff = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, 116 true); 117 mAutoWakeup = mService.readBooleanSetting(Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, true); 118 mStandbyHandler = new HdmiCecStandbyModeHandler(service, this); 119 } 120 121 @Override 122 @ServiceThreadOnly 123 protected void onAddressAllocated(int logicalAddress, int reason) { 124 assertRunOnServiceThread(); 125 mService.sendCecCommand(HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( 126 mAddress, mService.getPhysicalAddress(), mDeviceType)); 127 mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand( 128 mAddress, mService.getVendorId())); 129 launchRoutingControl(reason != HdmiControlService.INITIATED_BY_ENABLE_CEC && 130 reason != HdmiControlService.INITIATED_BY_BOOT_UP); 131 launchDeviceDiscovery(); 132 } 133 134 @Override 135 @ServiceThreadOnly 136 protected int getPreferredAddress() { 137 assertRunOnServiceThread(); 138 return SystemProperties.getInt(Constants.PROPERTY_PREFERRED_ADDRESS_TV, 139 Constants.ADDR_UNREGISTERED); 140 } 141 142 @Override 143 @ServiceThreadOnly 144 protected void setPreferredAddress(int addr) { 145 assertRunOnServiceThread(); 146 SystemProperties.set(Constants.PROPERTY_PREFERRED_ADDRESS_TV, String.valueOf(addr)); 147 } 148 149 @Override 150 @ServiceThreadOnly 151 boolean dispatchMessage(HdmiCecMessage message) { 152 assertRunOnServiceThread(); 153 if (mService.isPowerStandby() && mStandbyHandler.handleCommand(message)) { 154 return true; 155 } 156 return super.onMessage(message); 157 } 158 159 /** 160 * Performs the action 'device select', or 'one touch play' initiated by TV. 161 * 162 * @param targetAddress logical address of the device to select 163 * @param callback callback object to report the result with 164 */ 165 @ServiceThreadOnly 166 void deviceSelect(int targetAddress, IHdmiControlCallback callback) { 167 assertRunOnServiceThread(); 168 if (targetAddress == Constants.ADDR_INTERNAL) { 169 handleSelectInternalSource(); 170 // Switching to internal source is always successful even when CEC control is disabled. 171 setActiveSource(targetAddress, mService.getPhysicalAddress()); 172 setActivePath(mService.getPhysicalAddress()); 173 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS); 174 return; 175 } 176 if (!mService.isControlEnabled()) { 177 HdmiCecDeviceInfo info = getDeviceInfo(targetAddress); 178 if (info != null) { 179 setActiveSource(info); 180 } 181 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); 182 return; 183 } 184 HdmiCecDeviceInfo targetDevice = getDeviceInfo(targetAddress); 185 if (targetDevice == null) { 186 invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); 187 return; 188 } 189 removeAction(DeviceSelectAction.class); 190 addAndStartAction(new DeviceSelectAction(this, targetDevice, callback)); 191 } 192 193 @ServiceThreadOnly 194 private void handleSelectInternalSource() { 195 assertRunOnServiceThread(); 196 // Seq #18 197 if (mService.isControlEnabled() && mActiveSource.logicalAddress != mAddress) { 198 updateActiveSource(mAddress, mService.getPhysicalAddress()); 199 // TODO: Check if this comes from <Text/Image View On> - if true, do nothing. 200 HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource( 201 mAddress, mService.getPhysicalAddress()); 202 mService.sendCecCommand(activeSource); 203 } 204 } 205 206 @ServiceThreadOnly 207 void updateActiveSource(int logicalAddress, int physicalAddress) { 208 assertRunOnServiceThread(); 209 updateActiveSource(ActiveSource.of(logicalAddress, physicalAddress)); 210 } 211 212 @ServiceThreadOnly 213 void updateActiveSource(ActiveSource newActive) { 214 assertRunOnServiceThread(); 215 // Seq #14 216 if (mActiveSource.equals(newActive)) { 217 return; 218 } 219 setActiveSource(newActive); 220 int logicalAddress = newActive.logicalAddress; 221 if (getDeviceInfo(logicalAddress) != null && logicalAddress != mAddress) { 222 if (mService.pathToPortId(newActive.physicalAddress) == getActivePortId()) { 223 setPrevPortId(getActivePortId()); 224 } 225 // TODO: Show the OSD banner related to the new active source device. 226 } else { 227 // TODO: If displayed, remove the OSD banner related to the previous 228 // active source device. 229 } 230 } 231 232 int getPortId(int physicalAddress) { 233 return mService.pathToPortId(physicalAddress); 234 } 235 236 /** 237 * Returns the previous port id kept to handle input switching on <Inactive Source>. 238 */ 239 int getPrevPortId() { 240 synchronized (mLock) { 241 return mPrevPortId; 242 } 243 } 244 245 /** 246 * Sets the previous port id. INVALID_PORT_ID invalidates it, hence no actions will be 247 * taken for <Inactive Source>. 248 */ 249 void setPrevPortId(int portId) { 250 synchronized (mLock) { 251 mPrevPortId = portId; 252 } 253 } 254 255 @ServiceThreadOnly 256 void updateActiveInput(int path, boolean notifyInputChange) { 257 assertRunOnServiceThread(); 258 // Seq #15 259 if (path == getActivePath()) { 260 return; 261 } 262 setPrevPortId(getActivePortId()); 263 int portId = mService.pathToPortId(path); 264 setActivePath(path); 265 // TODO: Handle PAP/PIP case. 266 // Show OSD port change banner 267 if (notifyInputChange) { 268 ActiveSource activeSource = getActiveSource(); 269 HdmiCecDeviceInfo info = getDeviceInfo(activeSource.logicalAddress); 270 if (info == null) { 271 info = new HdmiCecDeviceInfo(Constants.ADDR_INVALID, path, portId, 272 HdmiCecDeviceInfo.DEVICE_RESERVED, 0, null); 273 } 274 mService.invokeInputChangeListener(info); 275 } 276 } 277 278 @ServiceThreadOnly 279 void doManualPortSwitching(int portId, IHdmiControlCallback callback) { 280 assertRunOnServiceThread(); 281 // Seq #20 282 if (!mService.isValidPortId(portId)) { 283 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); 284 return; 285 } 286 if (portId == getActivePortId()) { 287 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS); 288 return; 289 } 290 mActiveSource.invalidate(); 291 if (!mService.isControlEnabled()) { 292 setActivePortId(portId); 293 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); 294 return; 295 } 296 // TODO: Return immediately if the operation is triggered by <Text/Image View On> 297 // and this is the first notification about the active input after power-on 298 // (switch to HDMI didn't happen so far but is expected to happen soon). 299 int oldPath = mService.portIdToPath(getActivePortId()); 300 int newPath = mService.portIdToPath(portId); 301 HdmiCecMessage routingChange = 302 HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath); 303 mService.sendCecCommand(routingChange); 304 removeAction(RoutingControlAction.class); 305 addAndStartAction(new RoutingControlAction(this, newPath, false, callback)); 306 } 307 308 int getPowerStatus() { 309 return mService.getPowerStatus(); 310 } 311 312 /** 313 * Sends key to a target CEC device. 314 * 315 * @param keyCode key code to send. Defined in {@link android.view.KeyEvent}. 316 * @param isPressed true if this is key press event 317 */ 318 @Override 319 @ServiceThreadOnly 320 protected void sendKeyEvent(int keyCode, boolean isPressed) { 321 assertRunOnServiceThread(); 322 List<SendKeyAction> action = getActions(SendKeyAction.class); 323 if (!action.isEmpty()) { 324 action.get(0).processKeyEvent(keyCode, isPressed); 325 } else { 326 if (isPressed) { 327 int logicalAddress = getActiveSource().logicalAddress; 328 addAndStartAction(new SendKeyAction(this, logicalAddress, keyCode)); 329 } else { 330 Slog.w(TAG, "Discard key release event"); 331 } 332 } 333 } 334 335 private static void invokeCallback(IHdmiControlCallback callback, int result) { 336 if (callback == null) { 337 return; 338 } 339 try { 340 callback.onComplete(result); 341 } catch (RemoteException e) { 342 Slog.e(TAG, "Invoking callback failed:" + e); 343 } 344 } 345 346 @Override 347 @ServiceThreadOnly 348 protected boolean handleActiveSource(HdmiCecMessage message) { 349 assertRunOnServiceThread(); 350 int logicalAddress = message.getSource(); 351 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); 352 if (getDeviceInfo(logicalAddress) == null) { 353 handleNewDeviceAtTheTailOfActivePath(physicalAddress); 354 } else { 355 ActiveSource activeSource = ActiveSource.of(logicalAddress, physicalAddress); 356 ActiveSourceHandler.create(this, null).process(activeSource); 357 } 358 return true; 359 } 360 361 @Override 362 @ServiceThreadOnly 363 protected boolean handleInactiveSource(HdmiCecMessage message) { 364 assertRunOnServiceThread(); 365 // Seq #10 366 367 // Ignore <Inactive Source> from non-active source device. 368 if (getActiveSource().logicalAddress != message.getSource()) { 369 return true; 370 } 371 if (isProhibitMode()) { 372 return true; 373 } 374 int portId = getPrevPortId(); 375 if (portId != Constants.INVALID_PORT_ID) { 376 // TODO: Do this only if TV is not showing multiview like PIP/PAP. 377 378 HdmiCecDeviceInfo inactiveSource = getDeviceInfo(message.getSource()); 379 if (inactiveSource == null) { 380 return true; 381 } 382 if (mService.pathToPortId(inactiveSource.getPhysicalAddress()) == portId) { 383 return true; 384 } 385 // TODO: Switch the TV freeze mode off 386 387 doManualPortSwitching(portId, null); 388 setPrevPortId(Constants.INVALID_PORT_ID); 389 } 390 return true; 391 } 392 393 @Override 394 @ServiceThreadOnly 395 protected boolean handleRequestActiveSource(HdmiCecMessage message) { 396 assertRunOnServiceThread(); 397 // Seq #19 398 if (mAddress == getActiveSource().logicalAddress) { 399 mService.sendCecCommand( 400 HdmiCecMessageBuilder.buildActiveSource(mAddress, getActivePath())); 401 } 402 return true; 403 } 404 405 @Override 406 @ServiceThreadOnly 407 protected boolean handleGetMenuLanguage(HdmiCecMessage message) { 408 assertRunOnServiceThread(); 409 HdmiCecMessage command = HdmiCecMessageBuilder.buildSetMenuLanguageCommand( 410 mAddress, Locale.getDefault().getISO3Language()); 411 // TODO: figure out how to handle failed to get language code. 412 if (command != null) { 413 mService.sendCecCommand(command); 414 } else { 415 Slog.w(TAG, "Failed to respond to <Get Menu Language>: " + message.toString()); 416 } 417 return true; 418 } 419 420 @Override 421 @ServiceThreadOnly 422 protected boolean handleReportPhysicalAddress(HdmiCecMessage message) { 423 assertRunOnServiceThread(); 424 // Ignore if [Device Discovery Action] is going on. 425 if (hasAction(DeviceDiscoveryAction.class)) { 426 Slog.i(TAG, "Ignore unrecognizable <Report Physical Address> " 427 + "because Device Discovery Action is on-going:" + message); 428 return true; 429 } 430 431 int path = HdmiUtils.twoBytesToInt(message.getParams()); 432 int address = message.getSource(); 433 if (!isInDeviceList(path, address)) { 434 handleNewDeviceAtTheTailOfActivePath(path); 435 } 436 startNewDeviceAction(ActiveSource.of(address, path)); 437 return true; 438 } 439 440 void startNewDeviceAction(ActiveSource activeSource) { 441 for (NewDeviceAction action : getActions(NewDeviceAction.class)) { 442 // If there is new device action which has the same logical address and path 443 // ignore new request. 444 // NewDeviceAction is created whenever it receives <Report Physical Address>. 445 // And there is a chance starting NewDeviceAction for the same source. 446 // Usually, new device sends <Report Physical Address> when it's plugged 447 // in. However, TV can detect a new device from HotPlugDetectionAction, 448 // which sends <Give Physical Address> to the source for newly detected 449 // device. 450 if (action.isActionOf(activeSource)) { 451 return; 452 } 453 } 454 455 addAndStartAction(new NewDeviceAction(this, activeSource.logicalAddress, 456 activeSource.physicalAddress)); 457 } 458 459 private void handleNewDeviceAtTheTailOfActivePath(int path) { 460 // Seq #22 461 if (isTailOfActivePath(path, getActivePath())) { 462 removeAction(RoutingControlAction.class); 463 int newPath = mService.portIdToPath(getActivePortId()); 464 setActivePath(newPath); 465 mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange( 466 mAddress, getActivePath(), newPath)); 467 addAndStartAction(new RoutingControlAction(this, newPath, false, null)); 468 } 469 } 470 471 /** 472 * Whether the given path is located in the tail of current active path. 473 * 474 * @param path to be tested 475 * @param activePath current active path 476 * @return true if the given path is located in the tail of current active path; otherwise, 477 * false 478 */ 479 static boolean isTailOfActivePath(int path, int activePath) { 480 // If active routing path is internal source, return false. 481 if (activePath == 0) { 482 return false; 483 } 484 for (int i = 12; i >= 0; i -= 4) { 485 int curActivePath = (activePath >> i) & 0xF; 486 if (curActivePath == 0) { 487 return true; 488 } else { 489 int curPath = (path >> i) & 0xF; 490 if (curPath != curActivePath) { 491 return false; 492 } 493 } 494 } 495 return false; 496 } 497 498 @Override 499 @ServiceThreadOnly 500 protected boolean handleRoutingChange(HdmiCecMessage message) { 501 assertRunOnServiceThread(); 502 // Seq #21 503 byte[] params = message.getParams(); 504 int currentPath = HdmiUtils.twoBytesToInt(params); 505 if (HdmiUtils.isAffectingActiveRoutingPath(getActivePath(), currentPath)) { 506 mActiveSource.invalidate(); 507 removeAction(RoutingControlAction.class); 508 int newPath = HdmiUtils.twoBytesToInt(params, 2); 509 addAndStartAction(new RoutingControlAction(this, newPath, true, null)); 510 } 511 return true; 512 } 513 514 @Override 515 @ServiceThreadOnly 516 protected boolean handleReportAudioStatus(HdmiCecMessage message) { 517 assertRunOnServiceThread(); 518 519 byte params[] = message.getParams(); 520 int mute = params[0] & 0x80; 521 int volume = params[0] & 0x7F; 522 setAudioStatus(mute == 0x80, volume); 523 return true; 524 } 525 526 @Override 527 @ServiceThreadOnly 528 protected boolean handleTextViewOn(HdmiCecMessage message) { 529 assertRunOnServiceThread(); 530 if (mService.isPowerStandbyOrTransient() && mAutoWakeup) { 531 mService.wakeUp(); 532 } 533 // TODO: Connect to Hardware input manager to invoke TV App with the appropriate channel 534 // that represents the source device. 535 return true; 536 } 537 538 @Override 539 @ServiceThreadOnly 540 protected boolean handleImageViewOn(HdmiCecMessage message) { 541 assertRunOnServiceThread(); 542 // Currently, it's the same as <Text View On>. 543 return handleTextViewOn(message); 544 } 545 546 @Override 547 @ServiceThreadOnly 548 protected boolean handleSetOsdName(HdmiCecMessage message) { 549 int source = message.getSource(); 550 HdmiCecDeviceInfo deviceInfo = getDeviceInfo(source); 551 // If the device is not in device list, ignore it. 552 if (deviceInfo == null) { 553 Slog.e(TAG, "No source device info for <Set Osd Name>." + message); 554 return true; 555 } 556 String osdName = null; 557 try { 558 osdName = new String(message.getParams(), "US-ASCII"); 559 } catch (UnsupportedEncodingException e) { 560 Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e); 561 return true; 562 } 563 564 if (deviceInfo.getDisplayName().equals(osdName)) { 565 Slog.i(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message); 566 return true; 567 } 568 569 addCecDevice(new HdmiCecDeviceInfo(deviceInfo.getLogicalAddress(), 570 deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(), 571 deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName)); 572 return true; 573 } 574 575 @ServiceThreadOnly 576 private void launchDeviceDiscovery() { 577 assertRunOnServiceThread(); 578 clearDeviceInfoList(); 579 DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, 580 new DeviceDiscoveryCallback() { 581 @Override 582 public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) { 583 for (HdmiCecDeviceInfo info : deviceInfos) { 584 addCecDevice(info); 585 } 586 587 // Since we removed all devices when it's start and 588 // device discovery action does not poll local devices, 589 // we should put device info of local device manually here 590 for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) { 591 addCecDevice(device.getDeviceInfo()); 592 } 593 594 addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this)); 595 596 // If there is AVR, initiate System Audio Auto initiation action, 597 // which turns on and off system audio according to last system 598 // audio setting. 599 if (mSystemAudioActivated && getAvrDeviceInfo() != null) { 600 addAndStartAction(new SystemAudioAutoInitiationAction( 601 HdmiCecLocalDeviceTv.this, 602 getAvrDeviceInfo().getLogicalAddress())); 603 if (mArcEstablished) { 604 startArcAction(true); 605 } 606 } 607 } 608 }); 609 addAndStartAction(action); 610 } 611 612 // Clear all device info. 613 @ServiceThreadOnly 614 private void clearDeviceInfoList() { 615 assertRunOnServiceThread(); 616 for (HdmiCecDeviceInfo info : mSafeExternalInputs) { 617 mService.invokeDeviceEventListeners(info, false); 618 } 619 mDeviceInfos.clear(); 620 updateSafeDeviceInfoList(); 621 } 622 623 @ServiceThreadOnly 624 // Seq #32 625 void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) { 626 assertRunOnServiceThread(); 627 if (!mService.isControlEnabled() || hasAction(DeviceDiscoveryAction.class)) { 628 setSystemAudioMode(false, true); 629 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); 630 return; 631 } 632 HdmiCecDeviceInfo avr = getAvrDeviceInfo(); 633 if (avr == null) { 634 setSystemAudioMode(false, true); 635 invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); 636 return; 637 } 638 639 addAndStartAction( 640 new SystemAudioActionFromTv(this, avr.getLogicalAddress(), enabled, callback)); 641 } 642 643 // # Seq 25 644 void setSystemAudioMode(boolean on, boolean updateSetting) { 645 if (updateSetting) { 646 mService.writeBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, on); 647 } 648 updateAudioManagerForSystemAudio(on); 649 synchronized (mLock) { 650 if (mSystemAudioActivated != on) { 651 mSystemAudioActivated = on; 652 mService.announceSystemAudioModeChange(on); 653 } 654 } 655 } 656 657 private void updateAudioManagerForSystemAudio(boolean on) { 658 // TODO: remove output device, once update AudioManager api. 659 mService.getAudioManager().setHdmiSystemAudioSupported(on); 660 } 661 662 boolean isSystemAudioActivated() { 663 if (getAvrDeviceInfo() == null) { 664 return false; 665 } 666 synchronized (mLock) { 667 return mSystemAudioActivated; 668 } 669 } 670 671 boolean getSystemAudioModeSetting() { 672 return mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, false); 673 } 674 675 /** 676 * Change ARC status into the given {@code enabled} status. 677 * 678 * @return {@code true} if ARC was in "Enabled" status 679 */ 680 @ServiceThreadOnly 681 boolean setArcStatus(boolean enabled) { 682 assertRunOnServiceThread(); 683 boolean oldStatus = mArcEstablished; 684 // 1. Enable/disable ARC circuit. 685 mService.setAudioReturnChannel(enabled); 686 // 2. Notify arc status to audio service. 687 notifyArcStatusToAudioService(enabled); 688 // 3. Update arc status; 689 mArcEstablished = enabled; 690 return oldStatus; 691 } 692 693 private void notifyArcStatusToAudioService(boolean enabled) { 694 // Note that we don't set any name to ARC. 695 mService.getAudioManager().setWiredDeviceConnectionState( 696 AudioSystem.DEVICE_OUT_HDMI_ARC, 697 enabled ? 1 : 0, ""); 698 } 699 700 /** 701 * Returns whether ARC is enabled or not. 702 */ 703 @ServiceThreadOnly 704 boolean isArcEstabilished() { 705 assertRunOnServiceThread(); 706 return mArcFeatureEnabled && mArcEstablished; 707 } 708 709 @ServiceThreadOnly 710 void changeArcFeatureEnabled(boolean enabled) { 711 assertRunOnServiceThread(); 712 713 if (mArcFeatureEnabled != enabled) { 714 if (enabled) { 715 if (!mArcEstablished) { 716 startArcAction(true); 717 } 718 } else { 719 if (mArcEstablished) { 720 startArcAction(false); 721 } 722 } 723 mArcFeatureEnabled = enabled; 724 } 725 } 726 727 @ServiceThreadOnly 728 boolean isArcFeatureEnabled() { 729 assertRunOnServiceThread(); 730 return mArcFeatureEnabled; 731 } 732 733 @ServiceThreadOnly 734 private void startArcAction(boolean enabled) { 735 assertRunOnServiceThread(); 736 HdmiCecDeviceInfo info = getAvrDeviceInfo(); 737 if (info == null) { 738 return; 739 } 740 if (!isConnectedToArcPort(info.getPhysicalAddress())) { 741 return; 742 } 743 744 // Terminate opposite action and start action if not exist. 745 if (enabled) { 746 removeAction(RequestArcTerminationAction.class); 747 if (!hasAction(RequestArcInitiationAction.class)) { 748 addAndStartAction(new RequestArcInitiationAction(this, info.getLogicalAddress())); 749 } 750 } else { 751 removeAction(RequestArcInitiationAction.class); 752 if (!hasAction(RequestArcTerminationAction.class)) { 753 addAndStartAction(new RequestArcTerminationAction(this, info.getLogicalAddress())); 754 } 755 } 756 } 757 758 void setAudioStatus(boolean mute, int volume) { 759 synchronized (mLock) { 760 mSystemAudioMute = mute; 761 mSystemAudioVolume = volume; 762 int maxVolume = mService.getAudioManager().getStreamMaxVolume( 763 AudioManager.STREAM_MUSIC); 764 mService.setAudioStatus(mute, 765 VolumeControlAction.scaleToCustomVolume(volume, maxVolume)); 766 } 767 } 768 769 @ServiceThreadOnly 770 void changeVolume(int curVolume, int delta, int maxVolume) { 771 assertRunOnServiceThread(); 772 if (delta == 0 || !isSystemAudioActivated()) { 773 return; 774 } 775 776 int targetVolume = curVolume + delta; 777 int cecVolume = VolumeControlAction.scaleToCecVolume(targetVolume, maxVolume); 778 synchronized (mLock) { 779 // If new volume is the same as current system audio volume, just ignore it. 780 // Note that UNKNOWN_VOLUME is not in range of cec volume scale. 781 if (cecVolume == mSystemAudioVolume) { 782 // Update tv volume with system volume value. 783 mService.setAudioStatus(false, 784 VolumeControlAction.scaleToCustomVolume(mSystemAudioVolume, maxVolume)); 785 return; 786 } 787 } 788 789 // Remove existing volume action. 790 removeAction(VolumeControlAction.class); 791 792 HdmiCecDeviceInfo avr = getAvrDeviceInfo(); 793 addAndStartAction(VolumeControlAction.ofVolumeChange(this, avr.getLogicalAddress(), 794 cecVolume, delta > 0)); 795 } 796 797 @ServiceThreadOnly 798 void changeMute(boolean mute) { 799 assertRunOnServiceThread(); 800 if (!isSystemAudioActivated()) { 801 return; 802 } 803 804 // Remove existing volume action. 805 removeAction(VolumeControlAction.class); 806 HdmiCecDeviceInfo avr = getAvrDeviceInfo(); 807 addAndStartAction(VolumeControlAction.ofMute(this, avr.getLogicalAddress(), mute)); 808 } 809 810 private boolean isSystemAudioOn() { 811 synchronized (mLock) { 812 return mSystemAudioActivated; 813 } 814 } 815 816 @Override 817 @ServiceThreadOnly 818 protected boolean handleInitiateArc(HdmiCecMessage message) { 819 assertRunOnServiceThread(); 820 // In case where <Initiate Arc> is started by <Request ARC Initiation> 821 // need to clean up RequestArcInitiationAction. 822 removeAction(RequestArcInitiationAction.class); 823 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 824 message.getSource(), true); 825 addAndStartAction(action); 826 return true; 827 } 828 829 @Override 830 @ServiceThreadOnly 831 protected boolean handleTerminateArc(HdmiCecMessage message) { 832 assertRunOnServiceThread(); 833 // In case where <Terminate Arc> is started by <Request ARC Termination> 834 // need to clean up RequestArcInitiationAction. 835 removeAction(RequestArcTerminationAction.class); 836 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 837 message.getSource(), false); 838 addAndStartAction(action); 839 return true; 840 } 841 842 @Override 843 @ServiceThreadOnly 844 protected boolean handleSetSystemAudioMode(HdmiCecMessage message) { 845 assertRunOnServiceThread(); 846 if (!isMessageForSystemAudio(message)) { 847 return false; 848 } 849 SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this, 850 message.getSource(), HdmiUtils.parseCommandParamSystemAudioStatus(message), null); 851 addAndStartAction(action); 852 return true; 853 } 854 855 @Override 856 @ServiceThreadOnly 857 protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) { 858 assertRunOnServiceThread(); 859 if (!isMessageForSystemAudio(message)) { 860 return false; 861 } 862 setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message), true); 863 return true; 864 } 865 866 // Seq #53 867 @Override 868 @ServiceThreadOnly 869 protected boolean handleRecordTvScreen(HdmiCecMessage message) { 870 List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class); 871 if (!actions.isEmpty()) { 872 // Assumes only one OneTouchRecordAction. 873 OneTouchRecordAction action = actions.get(0); 874 if (action.getRecorderAddress() != message.getSource()) { 875 announceOneTouchRecordResult( 876 HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS); 877 } 878 return super.handleRecordTvScreen(message); 879 } 880 881 int recorderAddress = message.getSource(); 882 byte[] recordSource = mService.invokeRecordRequestListener(recorderAddress); 883 startOneTouchRecord(recorderAddress, recordSource); 884 return true; 885 } 886 887 @Override 888 protected boolean handleTimerClearedStatus(HdmiCecMessage message) { 889 byte[] params = message.getParams(); 890 int timerClearedStatusData = params[0]; 891 announceTimerRecordingResult(timerClearedStatusData); 892 return true; 893 } 894 895 void announceOneTouchRecordResult(int result) { 896 mService.invokeOneTouchRecordResult(result); 897 } 898 899 void announceTimerRecordingResult(int result) { 900 mService.invokeTimerRecordingResult(result); 901 } 902 903 private boolean isMessageForSystemAudio(HdmiCecMessage message) { 904 if (message.getSource() != Constants.ADDR_AUDIO_SYSTEM 905 || message.getDestination() != Constants.ADDR_TV 906 || getAvrDeviceInfo() == null) { 907 Slog.w(TAG, "Skip abnormal CecMessage: " + message); 908 return false; 909 } 910 return true; 911 } 912 913 /** 914 * Add a new {@link HdmiCecDeviceInfo}. It returns old device info which has the same 915 * logical address as new device info's. 916 * 917 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 918 * 919 * @param deviceInfo a new {@link HdmiCecDeviceInfo} to be added. 920 * @return {@code null} if it is new device. Otherwise, returns old {@HdmiCecDeviceInfo} 921 * that has the same logical address as new one has. 922 */ 923 @ServiceThreadOnly 924 private HdmiCecDeviceInfo addDeviceInfo(HdmiCecDeviceInfo deviceInfo) { 925 assertRunOnServiceThread(); 926 HdmiCecDeviceInfo oldDeviceInfo = getDeviceInfo(deviceInfo.getLogicalAddress()); 927 if (oldDeviceInfo != null) { 928 removeDeviceInfo(deviceInfo.getLogicalAddress()); 929 } 930 mDeviceInfos.append(deviceInfo.getLogicalAddress(), deviceInfo); 931 updateSafeDeviceInfoList(); 932 return oldDeviceInfo; 933 } 934 935 /** 936 * Remove a device info corresponding to the given {@code logicalAddress}. 937 * It returns removed {@link HdmiCecDeviceInfo} if exists. 938 * 939 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 940 * 941 * @param logicalAddress logical address of device to be removed 942 * @return removed {@link HdmiCecDeviceInfo} it exists. Otherwise, returns {@code null} 943 */ 944 @ServiceThreadOnly 945 private HdmiCecDeviceInfo removeDeviceInfo(int logicalAddress) { 946 assertRunOnServiceThread(); 947 HdmiCecDeviceInfo deviceInfo = mDeviceInfos.get(logicalAddress); 948 if (deviceInfo != null) { 949 mDeviceInfos.remove(logicalAddress); 950 } 951 updateSafeDeviceInfoList(); 952 return deviceInfo; 953 } 954 955 /** 956 * Return a list of all {@link HdmiCecDeviceInfo}. 957 * 958 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 959 * This is not thread-safe. For thread safety, call {@link #getSafeDeviceInfoList(boolean)}. 960 */ 961 @ServiceThreadOnly 962 List<HdmiCecDeviceInfo> getDeviceInfoList(boolean includelLocalDevice) { 963 assertRunOnServiceThread(); 964 if (includelLocalDevice) { 965 return HdmiUtils.sparseArrayToList(mDeviceInfos); 966 } else { 967 ArrayList<HdmiCecDeviceInfo> infoList = new ArrayList<>(); 968 for (int i = 0; i < mDeviceInfos.size(); ++i) { 969 HdmiCecDeviceInfo info = mDeviceInfos.valueAt(i); 970 if (!isLocalDeviceAddress(info.getLogicalAddress())) { 971 infoList.add(info); 972 } 973 } 974 return infoList; 975 } 976 } 977 978 /** 979 * Return external input devices. 980 */ 981 List<HdmiCecDeviceInfo> getSafeExternalInputs() { 982 synchronized (mLock) { 983 return mSafeExternalInputs; 984 } 985 } 986 987 @ServiceThreadOnly 988 private void updateSafeDeviceInfoList() { 989 assertRunOnServiceThread(); 990 List<HdmiCecDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos); 991 List<HdmiCecDeviceInfo> externalInputs = getInputDevices(); 992 synchronized (mLock) { 993 mSafeAllDeviceInfos = copiedDevices; 994 mSafeExternalInputs = externalInputs; 995 } 996 } 997 998 /** 999 * Return a list of external cec input (source) devices. 1000 * 1001 * <p>Note that this effectively excludes non-source devices like system audio, 1002 * secondary TV. 1003 */ 1004 private List<HdmiCecDeviceInfo> getInputDevices() { 1005 ArrayList<HdmiCecDeviceInfo> infoList = new ArrayList<>(); 1006 for (int i = 0; i < mDeviceInfos.size(); ++i) { 1007 HdmiCecDeviceInfo info = mDeviceInfos.valueAt(i); 1008 if (isLocalDeviceAddress(i)) { 1009 continue; 1010 } 1011 if (info.isSourceType()) { 1012 infoList.add(info); 1013 } 1014 } 1015 return infoList; 1016 } 1017 1018 @ServiceThreadOnly 1019 private boolean isLocalDeviceAddress(int address) { 1020 assertRunOnServiceThread(); 1021 for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) { 1022 if (device.isAddressOf(address)) { 1023 return true; 1024 } 1025 } 1026 return false; 1027 } 1028 1029 @ServiceThreadOnly 1030 HdmiCecDeviceInfo getAvrDeviceInfo() { 1031 assertRunOnServiceThread(); 1032 return getDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); 1033 } 1034 1035 /** 1036 * Return a {@link HdmiCecDeviceInfo} corresponding to the given {@code logicalAddress}. 1037 * 1038 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 1039 * This is not thread-safe. For thread safety, call {@link #getSafeDeviceInfo(int)}. 1040 * 1041 * @param logicalAddress logical address to be retrieved 1042 * @return {@link HdmiCecDeviceInfo} matched with the given {@code logicalAddress}. 1043 * Returns null if no logical address matched 1044 */ 1045 @ServiceThreadOnly 1046 HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) { 1047 assertRunOnServiceThread(); 1048 return mDeviceInfos.get(logicalAddress); 1049 } 1050 1051 boolean hasSystemAudioDevice() { 1052 return getSafeAvrDeviceInfo() != null; 1053 } 1054 1055 HdmiCecDeviceInfo getSafeAvrDeviceInfo() { 1056 return getSafeDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); 1057 } 1058 1059 /** 1060 * Thread safe version of {@link #getDeviceInfo(int)}. 1061 * 1062 * @param logicalAddress logical address to be retrieved 1063 * @return {@link HdmiCecDeviceInfo} matched with the given {@code logicalAddress}. 1064 * Returns null if no logical address matched 1065 */ 1066 HdmiCecDeviceInfo getSafeDeviceInfo(int logicalAddress) { 1067 synchronized (mLock) { 1068 return mSafeAllDeviceInfos.get(logicalAddress); 1069 } 1070 } 1071 1072 /** 1073 * Called when a device is newly added or a new device is detected or 1074 * existing device is updated. 1075 * 1076 * @param info device info of a new device. 1077 */ 1078 @ServiceThreadOnly 1079 final void addCecDevice(HdmiCecDeviceInfo info) { 1080 assertRunOnServiceThread(); 1081 addDeviceInfo(info); 1082 if (info.getLogicalAddress() == mAddress) { 1083 // The addition of TV device itself should not be notified. 1084 return; 1085 } 1086 mService.invokeDeviceEventListeners(info, true); 1087 } 1088 1089 /** 1090 * Called when a device is removed or removal of device is detected. 1091 * 1092 * @param address a logical address of a device to be removed 1093 */ 1094 @ServiceThreadOnly 1095 final void removeCecDevice(int address) { 1096 assertRunOnServiceThread(); 1097 HdmiCecDeviceInfo info = removeDeviceInfo(address); 1098 1099 mCecMessageCache.flushMessagesFrom(address); 1100 mService.invokeDeviceEventListeners(info, false); 1101 } 1102 1103 @ServiceThreadOnly 1104 void handleRemoveActiveRoutingPath(int path) { 1105 assertRunOnServiceThread(); 1106 // Seq #23 1107 if (isTailOfActivePath(path, getActivePath())) { 1108 removeAction(RoutingControlAction.class); 1109 int newPath = mService.portIdToPath(getActivePortId()); 1110 mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange( 1111 mAddress, getActivePath(), newPath)); 1112 addAndStartAction(new RoutingControlAction(this, getActivePortId(), true, null)); 1113 } 1114 } 1115 1116 /** 1117 * Launch routing control process. 1118 * 1119 * @param routingForBootup true if routing control is initiated due to One Touch Play 1120 * or TV power on 1121 */ 1122 @ServiceThreadOnly 1123 void launchRoutingControl(boolean routingForBootup) { 1124 assertRunOnServiceThread(); 1125 // Seq #24 1126 if (getActivePortId() != Constants.INVALID_PORT_ID) { 1127 if (!routingForBootup && !isProhibitMode()) { 1128 removeAction(RoutingControlAction.class); 1129 int newPath = mService.portIdToPath(getActivePortId()); 1130 setActivePath(newPath); 1131 mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange(mAddress, 1132 getActivePath(), newPath)); 1133 addAndStartAction(new RoutingControlAction(this, getActivePortId(), 1134 routingForBootup, null)); 1135 } 1136 } else { 1137 int activePath = mService.getPhysicalAddress(); 1138 setActivePath(activePath); 1139 if (!routingForBootup) { 1140 mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(mAddress, 1141 activePath)); 1142 } 1143 } 1144 } 1145 1146 /** 1147 * Returns the {@link HdmiCecDeviceInfo} instance whose physical address matches 1148 * the given routing path. CEC devices use routing path for its physical address to 1149 * describe the hierarchy of the devices in the network. 1150 * 1151 * @param path routing path or physical address 1152 * @return {@link HdmiCecDeviceInfo} if the matched info is found; otherwise null 1153 */ 1154 @ServiceThreadOnly 1155 final HdmiCecDeviceInfo getDeviceInfoByPath(int path) { 1156 assertRunOnServiceThread(); 1157 for (HdmiCecDeviceInfo info : getDeviceInfoList(false)) { 1158 if (info.getPhysicalAddress() == path) { 1159 return info; 1160 } 1161 } 1162 return null; 1163 } 1164 1165 /** 1166 * Whether a device of the specified physical address and logical address exists 1167 * in a device info list. However, both are minimal condition and it could 1168 * be different device from the original one. 1169 * 1170 * @param logicalAddress logical address of a device to be searched 1171 * @param physicalAddress physical address of a device to be searched 1172 * @return true if exist; otherwise false 1173 */ 1174 @ServiceThreadOnly 1175 boolean isInDeviceList(int logicalAddress, int physicalAddress) { 1176 assertRunOnServiceThread(); 1177 HdmiCecDeviceInfo device = getDeviceInfo(logicalAddress); 1178 if (device == null) { 1179 return false; 1180 } 1181 return device.getPhysicalAddress() == physicalAddress; 1182 } 1183 1184 @Override 1185 @ServiceThreadOnly 1186 void onHotplug(int portId, boolean connected) { 1187 assertRunOnServiceThread(); 1188 1189 // Tv device will have permanent HotplugDetectionAction. 1190 List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class); 1191 if (!hotplugActions.isEmpty()) { 1192 // Note that hotplug action is single action running on a machine. 1193 // "pollAllDevicesNow" cleans up timer and start poll action immediately. 1194 // It covers seq #40, #43. 1195 hotplugActions.get(0).pollAllDevicesNow(); 1196 } 1197 } 1198 1199 @ServiceThreadOnly 1200 void setAutoDeviceOff(boolean enabled) { 1201 assertRunOnServiceThread(); 1202 mAutoDeviceOff = enabled; 1203 mService.writeBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, enabled); 1204 } 1205 1206 @ServiceThreadOnly 1207 void setAutoWakeup(boolean enabled) { 1208 assertRunOnServiceThread(); 1209 mAutoWakeup = enabled; 1210 mService.writeBooleanSetting(Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, enabled); 1211 } 1212 1213 @ServiceThreadOnly 1214 boolean getAutoWakeup() { 1215 assertRunOnServiceThread(); 1216 return mAutoWakeup; 1217 } 1218 1219 @Override 1220 @ServiceThreadOnly 1221 protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) { 1222 super.disableDevice(initiatedByCec, callback); 1223 assertRunOnServiceThread(); 1224 // Remove any repeated working actions. 1225 // HotplugDetectionAction will be reinstated during the wake up process. 1226 // HdmiControlService.onWakeUp() -> initializeLocalDevices() -> 1227 // LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery(). 1228 removeAction(DeviceDiscoveryAction.class); 1229 removeAction(HotplugDetectionAction.class); 1230 // Remove one touch record action. 1231 removeAction(OneTouchRecordAction.class); 1232 1233 disableSystemAudioIfExist(); 1234 disableArcIfExist(); 1235 clearDeviceInfoList(); 1236 checkIfPendingActionsCleared(); 1237 } 1238 1239 @ServiceThreadOnly 1240 private void disableSystemAudioIfExist() { 1241 assertRunOnServiceThread(); 1242 if (getAvrDeviceInfo() == null) { 1243 return; 1244 } 1245 1246 // Seq #31. 1247 removeAction(SystemAudioActionFromAvr.class); 1248 removeAction(SystemAudioActionFromTv.class); 1249 removeAction(SystemAudioAutoInitiationAction.class); 1250 removeAction(SystemAudioStatusAction.class); 1251 removeAction(VolumeControlAction.class); 1252 1253 // Turn off the mode but do not write it the settings, so that the next time TV powers on 1254 // the system audio mode setting can be restored automatically. 1255 setSystemAudioMode(false, false); 1256 } 1257 1258 @ServiceThreadOnly 1259 private void disableArcIfExist() { 1260 assertRunOnServiceThread(); 1261 HdmiCecDeviceInfo avr = getAvrDeviceInfo(); 1262 if (avr == null) { 1263 return; 1264 } 1265 1266 // Seq #44. 1267 removeAction(RequestArcInitiationAction.class); 1268 if (!hasAction(RequestArcTerminationAction.class) && isArcEstabilished()) { 1269 addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress())); 1270 } 1271 } 1272 1273 @Override 1274 @ServiceThreadOnly 1275 protected void onStandby(boolean initiatedByCec) { 1276 assertRunOnServiceThread(); 1277 // Seq #11 1278 if (!mService.isControlEnabled()) { 1279 return; 1280 } 1281 if (!initiatedByCec && mAutoDeviceOff) { 1282 mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby( 1283 mAddress, Constants.ADDR_BROADCAST)); 1284 } 1285 } 1286 1287 boolean isProhibitMode() { 1288 return mService.isProhibitMode(); 1289 } 1290 1291 boolean isPowerStandbyOrTransient() { 1292 return mService.isPowerStandbyOrTransient(); 1293 } 1294 1295 void displayOsd(int messageId) { 1296 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE); 1297 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId); 1298 mService.getContext().sendBroadcastAsUser(intent, UserHandle.ALL, 1299 HdmiControlService.PERMISSION); 1300 } 1301 1302 // Seq #54 and #55 1303 @ServiceThreadOnly 1304 void startOneTouchRecord(int recorderAddress, byte[] recordSource) { 1305 assertRunOnServiceThread(); 1306 if (!mService.isControlEnabled()) { 1307 Slog.w(TAG, "Can not start one touch record. CEC control is disabled."); 1308 announceOneTouchRecordResult(ONE_TOUCH_RECORD_CEC_DISABLED); 1309 return; 1310 } 1311 1312 if (!checkRecorder(recorderAddress)) { 1313 Slog.w(TAG, "Invalid recorder address:" + recorderAddress); 1314 announceOneTouchRecordResult(ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION); 1315 return; 1316 } 1317 1318 if (!checkRecordSource(recordSource)) { 1319 Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource)); 1320 announceOneTouchRecordResult(ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN); 1321 return; 1322 } 1323 1324 addAndStartAction(new OneTouchRecordAction(this, recorderAddress, recordSource)); 1325 Slog.i(TAG, "Start new [One Touch Record]-Target:" + recorderAddress + ", recordSource:" 1326 + Arrays.toString(recordSource)); 1327 } 1328 1329 @ServiceThreadOnly 1330 void stopOneTouchRecord(int recorderAddress) { 1331 assertRunOnServiceThread(); 1332 if (!mService.isControlEnabled()) { 1333 Slog.w(TAG, "Can not stop one touch record. CEC control is disabled."); 1334 announceOneTouchRecordResult(ONE_TOUCH_RECORD_CEC_DISABLED); 1335 return; 1336 } 1337 1338 if (!checkRecorder(recorderAddress)) { 1339 Slog.w(TAG, "Invalid recorder address:" + recorderAddress); 1340 announceOneTouchRecordResult(ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION); 1341 return; 1342 } 1343 1344 // Remove one touch record action so that other one touch record can be started. 1345 removeAction(OneTouchRecordAction.class); 1346 mService.sendCecCommand(HdmiCecMessageBuilder.buildRecordOff(mAddress, recorderAddress)); 1347 Slog.i(TAG, "Stop [One Touch Record]-Target:" + recorderAddress); 1348 } 1349 1350 private boolean checkRecorder(int recorderAddress) { 1351 HdmiCecDeviceInfo device = getDeviceInfo(recorderAddress); 1352 return (device != null) 1353 && (HdmiUtils.getTypeFromAddress(recorderAddress) 1354 == HdmiCecDeviceInfo.DEVICE_RECORDER); 1355 } 1356 1357 private boolean checkRecordSource(byte[] recordSource) { 1358 return (recordSource != null) && HdmiRecordSources.checkRecordSource(recordSource); 1359 } 1360 1361 @ServiceThreadOnly 1362 void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) { 1363 assertRunOnServiceThread(); 1364 if (!mService.isControlEnabled()) { 1365 Slog.w(TAG, "Can not start one touch record. CEC control is disabled."); 1366 announceTimerRecordingResult(TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED); 1367 return; 1368 } 1369 1370 if (!checkRecorder(recorderAddress)) { 1371 Slog.w(TAG, "Invalid recorder address:" + recorderAddress); 1372 announceTimerRecordingResult( 1373 TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION); 1374 return; 1375 } 1376 1377 if (!checkTimerRecordingSource(sourceType, recordSource)) { 1378 Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource)); 1379 announceTimerRecordingResult( 1380 TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE); 1381 return; 1382 } 1383 1384 addAndStartAction( 1385 new TimerRecordingAction(this, recorderAddress, sourceType, recordSource)); 1386 Slog.i(TAG, "Start [Timer Recording]-Target:" + recorderAddress + ", SourceType:" 1387 + sourceType + ", RecordSource:" + Arrays.toString(recordSource)); 1388 } 1389 1390 private boolean checkTimerRecordingSource(int sourceType, byte[] recordSource) { 1391 return (recordSource != null) 1392 && HdmiTimerRecordSources.checkTimerRecordSource(sourceType, recordSource); 1393 } 1394 1395 @ServiceThreadOnly 1396 void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) { 1397 assertRunOnServiceThread(); 1398 if (!mService.isControlEnabled()) { 1399 Slog.w(TAG, "Can not start one touch record. CEC control is disabled."); 1400 announceTimerRecordingResult(CLEAR_TIMER_STATUS_CEC_DISABLE); 1401 return; 1402 } 1403 1404 if (!checkRecorder(recorderAddress)) { 1405 Slog.w(TAG, "Invalid recorder address:" + recorderAddress); 1406 announceTimerRecordingResult(CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION); 1407 return; 1408 } 1409 1410 if (!checkTimerRecordingSource(sourceType, recordSource)) { 1411 Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource)); 1412 announceTimerRecordingResult(CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE); 1413 return; 1414 } 1415 1416 sendClearTimerMessage(recorderAddress, sourceType, recordSource); 1417 } 1418 1419 private void sendClearTimerMessage(int recorderAddress, int sourceType, byte[] recordSource) { 1420 HdmiCecMessage message = null; 1421 switch (sourceType) { 1422 case TIMER_RECORDING_TYPE_DIGITAL: 1423 message = HdmiCecMessageBuilder.buildClearDigitalTimer(mAddress, recorderAddress, 1424 recordSource); 1425 break; 1426 case TIMER_RECORDING_TYPE_ANALOGUE: 1427 message = HdmiCecMessageBuilder.buildClearAnalogueTimer(mAddress, recorderAddress, 1428 recordSource); 1429 break; 1430 case TIMER_RECORDING_TYPE_EXTERNAL: 1431 message = HdmiCecMessageBuilder.buildClearExternalTimer(mAddress, recorderAddress, 1432 recordSource); 1433 break; 1434 default: 1435 Slog.w(TAG, "Invalid source type:" + recorderAddress); 1436 announceTimerRecordingResult(CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE); 1437 return; 1438 1439 } 1440 mService.sendCecCommand(message, new SendMessageCallback() { 1441 @Override 1442 public void onSendCompleted(int error) { 1443 if (error != Constants.SEND_RESULT_SUCCESS) { 1444 announceTimerRecordingResult(CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE); 1445 } 1446 } 1447 }); 1448 } 1449} 1450