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