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