HdmiCecLocalDeviceTv.java revision 7e74206693f4ee93afb902d5b3446e2384f2a13d
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 int portId = mService.pathToPortId(path); 242 setActivePath(path); 243 setPrevPortId(portId); 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 mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange( 444 mAddress, getActivePath(), newPath)); 445 addAndStartAction(new RoutingControlAction(this, getActivePortId(), false, null)); 446 } 447 } 448 449 /** 450 * Whether the given path is located in the tail of current active path. 451 * 452 * @param path to be tested 453 * @param activePath current active path 454 * @return true if the given path is located in the tail of current active path; otherwise, 455 * false 456 */ 457 static boolean isTailOfActivePath(int path, int activePath) { 458 // If active routing path is internal source, return false. 459 if (activePath == 0) { 460 return false; 461 } 462 for (int i = 12; i >= 0; i -= 4) { 463 int curActivePath = (activePath >> i) & 0xF; 464 if (curActivePath == 0) { 465 return true; 466 } else { 467 int curPath = (path >> i) & 0xF; 468 if (curPath != curActivePath) { 469 return false; 470 } 471 } 472 } 473 return false; 474 } 475 476 @Override 477 @ServiceThreadOnly 478 protected boolean handleRoutingChange(HdmiCecMessage message) { 479 assertRunOnServiceThread(); 480 // Seq #21 481 byte[] params = message.getParams(); 482 int currentPath = HdmiUtils.twoBytesToInt(params); 483 if (HdmiUtils.isAffectingActiveRoutingPath(getActivePath(), currentPath)) { 484 mActiveSource.invalidate(); 485 removeAction(RoutingControlAction.class); 486 int newPath = HdmiUtils.twoBytesToInt(params, 2); 487 addAndStartAction(new RoutingControlAction(this, newPath, true, null)); 488 } 489 return true; 490 } 491 492 @Override 493 @ServiceThreadOnly 494 protected boolean handleReportAudioStatus(HdmiCecMessage message) { 495 assertRunOnServiceThread(); 496 497 byte params[] = message.getParams(); 498 int mute = params[0] & 0x80; 499 int volume = params[0] & 0x7F; 500 setAudioStatus(mute == 0x80, volume); 501 return true; 502 } 503 504 @Override 505 @ServiceThreadOnly 506 protected boolean handleTextViewOn(HdmiCecMessage message) { 507 assertRunOnServiceThread(); 508 if (mService.isPowerStandbyOrTransient() && mAutoWakeup) { 509 mService.wakeUp(); 510 } 511 // TODO: Connect to Hardware input manager to invoke TV App with the appropriate channel 512 // that represents the source device. 513 return true; 514 } 515 516 @Override 517 @ServiceThreadOnly 518 protected boolean handleImageViewOn(HdmiCecMessage message) { 519 assertRunOnServiceThread(); 520 // Currently, it's the same as <Text View On>. 521 return handleTextViewOn(message); 522 } 523 524 @Override 525 @ServiceThreadOnly 526 protected boolean handleSetOsdName(HdmiCecMessage message) { 527 int source = message.getSource(); 528 HdmiCecDeviceInfo deviceInfo = getDeviceInfo(source); 529 // If the device is not in device list, ignore it. 530 if (deviceInfo == null) { 531 Slog.e(TAG, "No source device info for <Set Osd Name>." + message); 532 return true; 533 } 534 String osdName = null; 535 try { 536 osdName = new String(message.getParams(), "US-ASCII"); 537 } catch (UnsupportedEncodingException e) { 538 Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e); 539 return true; 540 } 541 542 if (deviceInfo.getDisplayName().equals(osdName)) { 543 Slog.i(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message); 544 return true; 545 } 546 547 addCecDevice(new HdmiCecDeviceInfo(deviceInfo.getLogicalAddress(), 548 deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(), 549 deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName)); 550 return true; 551 } 552 553 @ServiceThreadOnly 554 private void launchDeviceDiscovery() { 555 assertRunOnServiceThread(); 556 clearDeviceInfoList(); 557 DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, 558 new DeviceDiscoveryCallback() { 559 @Override 560 public void onDeviceDiscoveryDone(List<HdmiCecDeviceInfo> deviceInfos) { 561 for (HdmiCecDeviceInfo info : deviceInfos) { 562 addCecDevice(info); 563 } 564 565 // Since we removed all devices when it's start and 566 // device discovery action does not poll local devices, 567 // we should put device info of local device manually here 568 for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) { 569 addCecDevice(device.getDeviceInfo()); 570 } 571 572 addAndStartAction(new HotplugDetectionAction(HdmiCecLocalDeviceTv.this)); 573 574 // If there is AVR, initiate System Audio Auto initiation action, 575 // which turns on and off system audio according to last system 576 // audio setting. 577 if (mSystemAudioActivated && getAvrDeviceInfo() != null) { 578 addAndStartAction(new SystemAudioAutoInitiationAction( 579 HdmiCecLocalDeviceTv.this, 580 getAvrDeviceInfo().getLogicalAddress())); 581 if (mArcEstablished) { 582 startArcAction(true); 583 } 584 } 585 } 586 }); 587 addAndStartAction(action); 588 } 589 590 // Clear all device info. 591 @ServiceThreadOnly 592 private void clearDeviceInfoList() { 593 assertRunOnServiceThread(); 594 for (HdmiCecDeviceInfo info : mSafeExternalInputs) { 595 mService.invokeDeviceEventListeners(info, false); 596 } 597 mDeviceInfos.clear(); 598 updateSafeDeviceInfoList(); 599 } 600 601 @ServiceThreadOnly 602 // Seq #32 603 void changeSystemAudioMode(boolean enabled, IHdmiControlCallback callback) { 604 assertRunOnServiceThread(); 605 if (!mService.isControlEnabled() || hasAction(DeviceDiscoveryAction.class)) { 606 setSystemAudioMode(false, true); 607 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); 608 return; 609 } 610 HdmiCecDeviceInfo avr = getAvrDeviceInfo(); 611 if (avr == null) { 612 setSystemAudioMode(false, true); 613 invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); 614 return; 615 } 616 617 addAndStartAction( 618 new SystemAudioActionFromTv(this, avr.getLogicalAddress(), enabled, callback)); 619 } 620 621 // # Seq 25 622 void setSystemAudioMode(boolean on, boolean updateSetting) { 623 if (updateSetting) { 624 mService.writeBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, on); 625 } 626 updateAudioManagerForSystemAudio(on); 627 synchronized (mLock) { 628 if (mSystemAudioActivated != on) { 629 mSystemAudioActivated = on; 630 mService.announceSystemAudioModeChange(on); 631 } 632 } 633 } 634 635 private void updateAudioManagerForSystemAudio(boolean on) { 636 // TODO: remove output device, once update AudioManager api. 637 mService.getAudioManager().setHdmiSystemAudioSupported(on); 638 } 639 640 boolean isSystemAudioActivated() { 641 if (getAvrDeviceInfo() == null) { 642 return false; 643 } 644 synchronized (mLock) { 645 return mSystemAudioActivated; 646 } 647 } 648 649 boolean getSystemAudioModeSetting() { 650 return mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_ENABLED, false); 651 } 652 653 /** 654 * Change ARC status into the given {@code enabled} status. 655 * 656 * @return {@code true} if ARC was in "Enabled" status 657 */ 658 @ServiceThreadOnly 659 boolean setArcStatus(boolean enabled) { 660 assertRunOnServiceThread(); 661 boolean oldStatus = mArcEstablished; 662 // 1. Enable/disable ARC circuit. 663 mService.setAudioReturnChannel(enabled); 664 // 2. Notify arc status to audio service. 665 notifyArcStatusToAudioService(enabled); 666 // 3. Update arc status; 667 mArcEstablished = enabled; 668 return oldStatus; 669 } 670 671 private void notifyArcStatusToAudioService(boolean enabled) { 672 // Note that we don't set any name to ARC. 673 mService.getAudioManager().setWiredDeviceConnectionState( 674 AudioSystem.DEVICE_OUT_HDMI_ARC, 675 enabled ? 1 : 0, ""); 676 } 677 678 /** 679 * Returns whether ARC is enabled or not. 680 */ 681 @ServiceThreadOnly 682 boolean isArcEstabilished() { 683 assertRunOnServiceThread(); 684 return mArcFeatureEnabled && mArcEstablished; 685 } 686 687 @ServiceThreadOnly 688 void changeArcFeatureEnabled(boolean enabled) { 689 assertRunOnServiceThread(); 690 691 if (mArcFeatureEnabled != enabled) { 692 if (enabled) { 693 if (!mArcEstablished) { 694 startArcAction(true); 695 } 696 } else { 697 if (mArcEstablished) { 698 startArcAction(false); 699 } 700 } 701 mArcFeatureEnabled = enabled; 702 } 703 } 704 705 @ServiceThreadOnly 706 boolean isArcFeatureEnabled() { 707 assertRunOnServiceThread(); 708 return mArcFeatureEnabled; 709 } 710 711 @ServiceThreadOnly 712 private void startArcAction(boolean enabled) { 713 assertRunOnServiceThread(); 714 HdmiCecDeviceInfo info = getAvrDeviceInfo(); 715 if (info == null) { 716 return; 717 } 718 if (!isConnectedToArcPort(info.getPhysicalAddress())) { 719 return; 720 } 721 722 // Terminate opposite action and start action if not exist. 723 if (enabled) { 724 removeAction(RequestArcTerminationAction.class); 725 if (!hasAction(RequestArcInitiationAction.class)) { 726 addAndStartAction(new RequestArcInitiationAction(this, info.getLogicalAddress())); 727 } 728 } else { 729 removeAction(RequestArcInitiationAction.class); 730 if (!hasAction(RequestArcTerminationAction.class)) { 731 addAndStartAction(new RequestArcTerminationAction(this, info.getLogicalAddress())); 732 } 733 } 734 } 735 736 void setAudioStatus(boolean mute, int volume) { 737 synchronized (mLock) { 738 mSystemAudioMute = mute; 739 mSystemAudioVolume = volume; 740 int maxVolume = mService.getAudioManager().getStreamMaxVolume( 741 AudioManager.STREAM_MUSIC); 742 mService.setAudioStatus(mute, 743 VolumeControlAction.scaleToCustomVolume(volume, maxVolume)); 744 } 745 } 746 747 @ServiceThreadOnly 748 void changeVolume(int curVolume, int delta, int maxVolume) { 749 assertRunOnServiceThread(); 750 if (delta == 0 || !isSystemAudioActivated()) { 751 return; 752 } 753 754 int targetVolume = curVolume + delta; 755 int cecVolume = VolumeControlAction.scaleToCecVolume(targetVolume, maxVolume); 756 synchronized (mLock) { 757 // If new volume is the same as current system audio volume, just ignore it. 758 // Note that UNKNOWN_VOLUME is not in range of cec volume scale. 759 if (cecVolume == mSystemAudioVolume) { 760 // Update tv volume with system volume value. 761 mService.setAudioStatus(false, 762 VolumeControlAction.scaleToCustomVolume(mSystemAudioVolume, maxVolume)); 763 return; 764 } 765 } 766 767 // Remove existing volume action. 768 removeAction(VolumeControlAction.class); 769 770 HdmiCecDeviceInfo avr = getAvrDeviceInfo(); 771 addAndStartAction(VolumeControlAction.ofVolumeChange(this, avr.getLogicalAddress(), 772 cecVolume, delta > 0)); 773 } 774 775 @ServiceThreadOnly 776 void changeMute(boolean mute) { 777 assertRunOnServiceThread(); 778 if (!isSystemAudioActivated()) { 779 return; 780 } 781 782 // Remove existing volume action. 783 removeAction(VolumeControlAction.class); 784 HdmiCecDeviceInfo avr = getAvrDeviceInfo(); 785 addAndStartAction(VolumeControlAction.ofMute(this, avr.getLogicalAddress(), mute)); 786 } 787 788 private boolean isSystemAudioOn() { 789 790 791 synchronized (mLock) { 792 return mSystemAudioActivated; 793 } 794 } 795 796 @Override 797 @ServiceThreadOnly 798 protected boolean handleInitiateArc(HdmiCecMessage message) { 799 assertRunOnServiceThread(); 800 // In case where <Initiate Arc> is started by <Request ARC Initiation> 801 // need to clean up RequestArcInitiationAction. 802 removeAction(RequestArcInitiationAction.class); 803 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 804 message.getSource(), true); 805 addAndStartAction(action); 806 return true; 807 } 808 809 @Override 810 @ServiceThreadOnly 811 protected boolean handleTerminateArc(HdmiCecMessage message) { 812 assertRunOnServiceThread(); 813 // In case where <Terminate Arc> is started by <Request ARC Termination> 814 // need to clean up RequestArcInitiationAction. 815 removeAction(RequestArcTerminationAction.class); 816 SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this, 817 message.getSource(), false); 818 addAndStartAction(action); 819 return true; 820 } 821 822 @Override 823 @ServiceThreadOnly 824 protected boolean handleSetSystemAudioMode(HdmiCecMessage message) { 825 assertRunOnServiceThread(); 826 if (!isMessageForSystemAudio(message)) { 827 return false; 828 } 829 SystemAudioActionFromAvr action = new SystemAudioActionFromAvr(this, 830 message.getSource(), HdmiUtils.parseCommandParamSystemAudioStatus(message), null); 831 addAndStartAction(action); 832 return true; 833 } 834 835 @Override 836 @ServiceThreadOnly 837 protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) { 838 assertRunOnServiceThread(); 839 if (!isMessageForSystemAudio(message)) { 840 return false; 841 } 842 setSystemAudioMode(HdmiUtils.parseCommandParamSystemAudioStatus(message), true); 843 return true; 844 } 845 846 // Seq #53 847 @Override 848 @ServiceThreadOnly 849 protected boolean handleRecordTvScreen(HdmiCecMessage message) { 850 List<OneTouchRecordAction> actions = getActions(OneTouchRecordAction.class); 851 if (!actions.isEmpty()) { 852 // Assumes only one OneTouchRecordAction. 853 OneTouchRecordAction action = actions.get(0); 854 if (action.getRecorderAddress() != message.getSource()) { 855 announceOneTouchRecordResult( 856 HdmiControlManager.ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS); 857 } 858 return super.handleRecordTvScreen(message); 859 } 860 861 int recorderAddress = message.getSource(); 862 byte[] recordSource = mService.invokeRecordRequestListener(recorderAddress); 863 startOneTouchRecord(recorderAddress, recordSource); 864 return true; 865 } 866 867 void announceOneTouchRecordResult(int result) { 868 mService.invokeOneTouchRecordResult(result); 869 } 870 871 void announceTimerRecordingResult(int result) { 872 mService.invokeTimerRecordingResult(result); 873 } 874 875 private boolean isMessageForSystemAudio(HdmiCecMessage message) { 876 if (message.getSource() != Constants.ADDR_AUDIO_SYSTEM 877 || message.getDestination() != Constants.ADDR_TV 878 || getAvrDeviceInfo() == null) { 879 Slog.w(TAG, "Skip abnormal CecMessage: " + message); 880 return false; 881 } 882 return true; 883 } 884 885 /** 886 * Add a new {@link HdmiCecDeviceInfo}. It returns old device info which has the same 887 * logical address as new device info's. 888 * 889 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 890 * 891 * @param deviceInfo a new {@link HdmiCecDeviceInfo} to be added. 892 * @return {@code null} if it is new device. Otherwise, returns old {@HdmiCecDeviceInfo} 893 * that has the same logical address as new one has. 894 */ 895 @ServiceThreadOnly 896 private HdmiCecDeviceInfo addDeviceInfo(HdmiCecDeviceInfo deviceInfo) { 897 assertRunOnServiceThread(); 898 HdmiCecDeviceInfo oldDeviceInfo = getDeviceInfo(deviceInfo.getLogicalAddress()); 899 if (oldDeviceInfo != null) { 900 removeDeviceInfo(deviceInfo.getLogicalAddress()); 901 } 902 mDeviceInfos.append(deviceInfo.getLogicalAddress(), deviceInfo); 903 updateSafeDeviceInfoList(); 904 return oldDeviceInfo; 905 } 906 907 /** 908 * Remove a device info corresponding to the given {@code logicalAddress}. 909 * It returns removed {@link HdmiCecDeviceInfo} if exists. 910 * 911 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 912 * 913 * @param logicalAddress logical address of device to be removed 914 * @return removed {@link HdmiCecDeviceInfo} it exists. Otherwise, returns {@code null} 915 */ 916 @ServiceThreadOnly 917 private HdmiCecDeviceInfo removeDeviceInfo(int logicalAddress) { 918 assertRunOnServiceThread(); 919 HdmiCecDeviceInfo deviceInfo = mDeviceInfos.get(logicalAddress); 920 if (deviceInfo != null) { 921 mDeviceInfos.remove(logicalAddress); 922 } 923 updateSafeDeviceInfoList(); 924 return deviceInfo; 925 } 926 927 /** 928 * Return a list of all {@link HdmiCecDeviceInfo}. 929 * 930 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 931 * This is not thread-safe. For thread safety, call {@link #getSafeDeviceInfoList(boolean)}. 932 */ 933 @ServiceThreadOnly 934 List<HdmiCecDeviceInfo> getDeviceInfoList(boolean includelLocalDevice) { 935 assertRunOnServiceThread(); 936 if (includelLocalDevice) { 937 return HdmiUtils.sparseArrayToList(mDeviceInfos); 938 } else { 939 ArrayList<HdmiCecDeviceInfo> infoList = new ArrayList<>(); 940 for (int i = 0; i < mDeviceInfos.size(); ++i) { 941 HdmiCecDeviceInfo info = mDeviceInfos.valueAt(i); 942 if (!isLocalDeviceAddress(info.getLogicalAddress())) { 943 infoList.add(info); 944 } 945 } 946 return infoList; 947 } 948 } 949 950 /** 951 * Return external input devices. 952 */ 953 List<HdmiCecDeviceInfo> getSafeExternalInputs() { 954 synchronized (mLock) { 955 return mSafeExternalInputs; 956 } 957 } 958 959 @ServiceThreadOnly 960 private void updateSafeDeviceInfoList() { 961 assertRunOnServiceThread(); 962 List<HdmiCecDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos); 963 List<HdmiCecDeviceInfo> externalInputs = getInputDevices(); 964 synchronized (mLock) { 965 mSafeAllDeviceInfos = copiedDevices; 966 mSafeExternalInputs = externalInputs; 967 } 968 } 969 970 /** 971 * Return a list of external cec input (source) devices. 972 * 973 * <p>Note that this effectively excludes non-source devices like system audio, 974 * secondary TV. 975 */ 976 private List<HdmiCecDeviceInfo> getInputDevices() { 977 ArrayList<HdmiCecDeviceInfo> infoList = new ArrayList<>(); 978 for (int i = 0; i < mDeviceInfos.size(); ++i) { 979 HdmiCecDeviceInfo info = mDeviceInfos.valueAt(i); 980 if (isLocalDeviceAddress(i)) { 981 continue; 982 } 983 if (info.isSourceType()) { 984 infoList.add(info); 985 } 986 } 987 return infoList; 988 } 989 990 @ServiceThreadOnly 991 private boolean isLocalDeviceAddress(int address) { 992 assertRunOnServiceThread(); 993 for (HdmiCecLocalDevice device : mService.getAllLocalDevices()) { 994 if (device.isAddressOf(address)) { 995 return true; 996 } 997 } 998 return false; 999 } 1000 1001 @ServiceThreadOnly 1002 HdmiCecDeviceInfo getAvrDeviceInfo() { 1003 assertRunOnServiceThread(); 1004 return getDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); 1005 } 1006 1007 /** 1008 * Return a {@link HdmiCecDeviceInfo} corresponding to the given {@code logicalAddress}. 1009 * 1010 * <p>Declared as package-private. accessed by {@link HdmiControlService} only. 1011 * This is not thread-safe. For thread safety, call {@link #getSafeDeviceInfo(int)}. 1012 * 1013 * @param logicalAddress logical address to be retrieved 1014 * @return {@link HdmiCecDeviceInfo} matched with the given {@code logicalAddress}. 1015 * Returns null if no logical address matched 1016 */ 1017 @ServiceThreadOnly 1018 HdmiCecDeviceInfo getDeviceInfo(int logicalAddress) { 1019 assertRunOnServiceThread(); 1020 return mDeviceInfos.get(logicalAddress); 1021 } 1022 1023 boolean hasSystemAudioDevice() { 1024 return getSafeAvrDeviceInfo() != null; 1025 } 1026 1027 HdmiCecDeviceInfo getSafeAvrDeviceInfo() { 1028 return getSafeDeviceInfo(Constants.ADDR_AUDIO_SYSTEM); 1029 } 1030 1031 /** 1032 * Thread safe version of {@link #getDeviceInfo(int)}. 1033 * 1034 * @param logicalAddress logical address to be retrieved 1035 * @return {@link HdmiCecDeviceInfo} matched with the given {@code logicalAddress}. 1036 * Returns null if no logical address matched 1037 */ 1038 HdmiCecDeviceInfo getSafeDeviceInfo(int logicalAddress) { 1039 synchronized (mLock) { 1040 return mSafeAllDeviceInfos.get(logicalAddress); 1041 } 1042 } 1043 1044 /** 1045 * Called when a device is newly added or a new device is detected or 1046 * existing device is updated. 1047 * 1048 * @param info device info of a new device. 1049 */ 1050 @ServiceThreadOnly 1051 final void addCecDevice(HdmiCecDeviceInfo info) { 1052 assertRunOnServiceThread(); 1053 addDeviceInfo(info); 1054 if (info.getLogicalAddress() == mAddress) { 1055 // The addition of TV device itself should not be notified. 1056 return; 1057 } 1058 mService.invokeDeviceEventListeners(info, true); 1059 } 1060 1061 /** 1062 * Called when a device is removed or removal of device is detected. 1063 * 1064 * @param address a logical address of a device to be removed 1065 */ 1066 @ServiceThreadOnly 1067 final void removeCecDevice(int address) { 1068 assertRunOnServiceThread(); 1069 HdmiCecDeviceInfo info = removeDeviceInfo(address); 1070 1071 mCecMessageCache.flushMessagesFrom(address); 1072 mService.invokeDeviceEventListeners(info, false); 1073 } 1074 1075 @ServiceThreadOnly 1076 void handleRemoveActiveRoutingPath(int path) { 1077 assertRunOnServiceThread(); 1078 // Seq #23 1079 if (isTailOfActivePath(path, getActivePath())) { 1080 removeAction(RoutingControlAction.class); 1081 int newPath = mService.portIdToPath(getActivePortId()); 1082 mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange( 1083 mAddress, getActivePath(), newPath)); 1084 addAndStartAction(new RoutingControlAction(this, getActivePortId(), true, null)); 1085 } 1086 } 1087 1088 /** 1089 * Launch routing control process. 1090 * 1091 * @param routingForBootup true if routing control is initiated due to One Touch Play 1092 * or TV power on 1093 */ 1094 @ServiceThreadOnly 1095 void launchRoutingControl(boolean routingForBootup) { 1096 assertRunOnServiceThread(); 1097 // Seq #24 1098 if (getActivePortId() != Constants.INVALID_PORT_ID) { 1099 if (!routingForBootup && !isProhibitMode()) { 1100 removeAction(RoutingControlAction.class); 1101 int newPath = mService.portIdToPath(getActivePortId()); 1102 setActivePath(newPath); 1103 mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingChange(mAddress, 1104 getActivePath(), newPath)); 1105 addAndStartAction(new RoutingControlAction(this, getActivePortId(), 1106 routingForBootup, null)); 1107 } 1108 } else { 1109 int activePath = mService.getPhysicalAddress(); 1110 setActivePath(activePath); 1111 if (!routingForBootup) { 1112 mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(mAddress, 1113 activePath)); 1114 } 1115 } 1116 } 1117 1118 /** 1119 * Returns the {@link HdmiCecDeviceInfo} instance whose physical address matches 1120 * the given routing path. CEC devices use routing path for its physical address to 1121 * describe the hierarchy of the devices in the network. 1122 * 1123 * @param path routing path or physical address 1124 * @return {@link HdmiCecDeviceInfo} if the matched info is found; otherwise null 1125 */ 1126 @ServiceThreadOnly 1127 final HdmiCecDeviceInfo getDeviceInfoByPath(int path) { 1128 assertRunOnServiceThread(); 1129 for (HdmiCecDeviceInfo info : getDeviceInfoList(false)) { 1130 if (info.getPhysicalAddress() == path) { 1131 return info; 1132 } 1133 } 1134 return null; 1135 } 1136 1137 /** 1138 * Whether a device of the specified physical address and logical address exists 1139 * in a device info list. However, both are minimal condition and it could 1140 * be different device from the original one. 1141 * 1142 * @param logicalAddress logical address of a device to be searched 1143 * @param physicalAddress physical address of a device to be searched 1144 * @return true if exist; otherwise false 1145 */ 1146 @ServiceThreadOnly 1147 boolean isInDeviceList(int logicalAddress, int physicalAddress) { 1148 assertRunOnServiceThread(); 1149 HdmiCecDeviceInfo device = getDeviceInfo(logicalAddress); 1150 if (device == null) { 1151 return false; 1152 } 1153 return device.getPhysicalAddress() == physicalAddress; 1154 } 1155 1156 @Override 1157 @ServiceThreadOnly 1158 void onHotplug(int portId, boolean connected) { 1159 assertRunOnServiceThread(); 1160 1161 // Tv device will have permanent HotplugDetectionAction. 1162 List<HotplugDetectionAction> hotplugActions = getActions(HotplugDetectionAction.class); 1163 if (!hotplugActions.isEmpty()) { 1164 // Note that hotplug action is single action running on a machine. 1165 // "pollAllDevicesNow" cleans up timer and start poll action immediately. 1166 // It covers seq #40, #43. 1167 hotplugActions.get(0).pollAllDevicesNow(); 1168 } 1169 } 1170 1171 @ServiceThreadOnly 1172 void setAutoDeviceOff(boolean enabled) { 1173 assertRunOnServiceThread(); 1174 mAutoDeviceOff = enabled; 1175 mService.writeBooleanSetting(Global.HDMI_CONTROL_AUTO_DEVICE_OFF_ENABLED, enabled); 1176 } 1177 1178 @ServiceThreadOnly 1179 void setAutoWakeup(boolean enabled) { 1180 assertRunOnServiceThread(); 1181 mAutoWakeup = enabled; 1182 mService.writeBooleanSetting(Global.HDMI_CONTROL_AUTO_WAKEUP_ENABLED, enabled); 1183 } 1184 1185 @Override 1186 @ServiceThreadOnly 1187 protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) { 1188 super.disableDevice(initiatedByCec, callback); 1189 assertRunOnServiceThread(); 1190 // Remove any repeated working actions. 1191 // HotplugDetectionAction will be reinstated during the wake up process. 1192 // HdmiControlService.onWakeUp() -> initializeLocalDevices() -> 1193 // LocalDeviceTv.onAddressAllocated() -> launchDeviceDiscovery(). 1194 removeAction(DeviceDiscoveryAction.class); 1195 removeAction(HotplugDetectionAction.class); 1196 // Remove one touch record action. 1197 removeAction(OneTouchRecordAction.class); 1198 1199 disableSystemAudioIfExist(); 1200 disableArcIfExist(); 1201 clearDeviceInfoList(); 1202 checkIfPendingActionsCleared(); 1203 } 1204 1205 @ServiceThreadOnly 1206 private void disableSystemAudioIfExist() { 1207 assertRunOnServiceThread(); 1208 if (getAvrDeviceInfo() == null) { 1209 return; 1210 } 1211 1212 // Seq #31. 1213 removeAction(SystemAudioActionFromAvr.class); 1214 removeAction(SystemAudioActionFromTv.class); 1215 removeAction(SystemAudioAutoInitiationAction.class); 1216 removeAction(SystemAudioStatusAction.class); 1217 removeAction(VolumeControlAction.class); 1218 1219 // Turn off the mode but do not write it the settings, so that the next time TV powers on 1220 // the system audio mode setting can be restored automatically. 1221 setSystemAudioMode(false, false); 1222 } 1223 1224 @ServiceThreadOnly 1225 private void disableArcIfExist() { 1226 assertRunOnServiceThread(); 1227 HdmiCecDeviceInfo avr = getAvrDeviceInfo(); 1228 if (avr == null) { 1229 return; 1230 } 1231 1232 // Seq #44. 1233 removeAction(RequestArcInitiationAction.class); 1234 if (!hasAction(RequestArcTerminationAction.class) && isArcEstabilished()) { 1235 addAndStartAction(new RequestArcTerminationAction(this, avr.getLogicalAddress())); 1236 } 1237 } 1238 1239 @Override 1240 @ServiceThreadOnly 1241 protected void onStandby(boolean initiatedByCec) { 1242 assertRunOnServiceThread(); 1243 // Seq #11 1244 if (!mService.isControlEnabled()) { 1245 return; 1246 } 1247 if (!initiatedByCec && mAutoDeviceOff) { 1248 mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby( 1249 mAddress, Constants.ADDR_BROADCAST)); 1250 } 1251 } 1252 1253 boolean isProhibitMode() { 1254 return mService.isProhibitMode(); 1255 } 1256 1257 boolean isPowerStandbyOrTransient() { 1258 return mService.isPowerStandbyOrTransient(); 1259 } 1260 1261 void displayOsd(int messageId) { 1262 Intent intent = new Intent(HdmiControlManager.ACTION_OSD_MESSAGE); 1263 intent.putExtra(HdmiControlManager.EXTRA_MESSAGE_ID, messageId); 1264 mService.getContext().sendBroadcastAsUser(intent, UserHandle.ALL, 1265 HdmiControlService.PERMISSION); 1266 } 1267 1268 // Seq #54 and #55 1269 @ServiceThreadOnly 1270 void startOneTouchRecord(int recorderAddress, byte[] recordSource) { 1271 assertRunOnServiceThread(); 1272 if (!mService.isControlEnabled()) { 1273 Slog.w(TAG, "Can not start one touch record. CEC control is disabled."); 1274 announceOneTouchRecordResult(ONE_TOUCH_RECORD_CEC_DISABLED); 1275 return; 1276 } 1277 1278 if (!checkRecorder(recorderAddress)) { 1279 Slog.w(TAG, "Invalid recorder address:" + recorderAddress); 1280 announceOneTouchRecordResult(ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION); 1281 return; 1282 } 1283 1284 if (!checkRecordSource(recordSource)) { 1285 Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource)); 1286 announceOneTouchRecordResult(ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN); 1287 return; 1288 } 1289 1290 addAndStartAction(new OneTouchRecordAction(this, recorderAddress, recordSource)); 1291 Slog.i(TAG, "Start new [One Touch Record]-Target:" + recorderAddress + ", recordSource:" 1292 + Arrays.toString(recordSource)); 1293 } 1294 1295 @ServiceThreadOnly 1296 void stopOneTouchRecord(int recorderAddress) { 1297 assertRunOnServiceThread(); 1298 if (!mService.isControlEnabled()) { 1299 Slog.w(TAG, "Can not stop one touch record. CEC control is disabled."); 1300 announceOneTouchRecordResult(ONE_TOUCH_RECORD_CEC_DISABLED); 1301 return; 1302 } 1303 1304 if (!checkRecorder(recorderAddress)) { 1305 Slog.w(TAG, "Invalid recorder address:" + recorderAddress); 1306 announceOneTouchRecordResult(ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION); 1307 return; 1308 } 1309 1310 // Remove one touch record action so that other one touch record can be started. 1311 removeAction(OneTouchRecordAction.class); 1312 mService.sendCecCommand(HdmiCecMessageBuilder.buildRecordOff(mAddress, recorderAddress)); 1313 Slog.i(TAG, "Stop [One Touch Record]-Target:" + recorderAddress); 1314 } 1315 1316 private boolean checkRecorder(int recorderAddress) { 1317 HdmiCecDeviceInfo device = getDeviceInfo(recorderAddress); 1318 return (device != null) 1319 && (HdmiUtils.getTypeFromAddress(recorderAddress) 1320 == HdmiCecDeviceInfo.DEVICE_RECORDER); 1321 } 1322 1323 private boolean checkRecordSource(byte[] recordSource) { 1324 return (recordSource != null) && HdmiRecordSources.checkRecordSource(recordSource); 1325 } 1326 1327 @ServiceThreadOnly 1328 void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) { 1329 assertRunOnServiceThread(); 1330 if (!mService.isControlEnabled()) { 1331 Slog.w(TAG, "Can not start one touch record. CEC control is disabled."); 1332 announceTimerRecordingResult(TIME_RECORDING_RESULT_EXTRA_CEC_DISABLED); 1333 return; 1334 } 1335 1336 if (!checkRecorder(recorderAddress)) { 1337 Slog.w(TAG, "Invalid recorder address:" + recorderAddress); 1338 announceTimerRecordingResult( 1339 TIME_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION); 1340 return; 1341 } 1342 1343 if (!checkTimerRecordingSource(sourceType, recordSource)) { 1344 Slog.w(TAG, "Invalid record source." + Arrays.toString(recordSource)); 1345 announceTimerRecordingResult( 1346 TIME_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE); 1347 return; 1348 } 1349 1350 addAndStartAction( 1351 new TimerRecordingAction(this, recorderAddress, sourceType, recordSource)); 1352 Slog.i(TAG, "Start [Timer Recording]-Target:" + recorderAddress + ", SourceType:" 1353 + sourceType + ", RecordSource:" + Arrays.toString(recordSource)); 1354 } 1355 1356 private boolean checkTimerRecordingSource(int sourceType, byte[] recordSource) { 1357 return (recordSource != null) 1358 && HdmiTimerRecordSources.checkTimerRecordSource(sourceType, recordSource); 1359 } 1360 1361 @ServiceThreadOnly 1362 void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) { 1363 assertRunOnServiceThread(); 1364 1365 // TODO: implement this. 1366 } 1367} 1368