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