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