1 /* 2 * Copyright (C) 2018 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 package com.android.server.hdmi; 17 18 import static android.hardware.hdmi.DeviceFeatures.FEATURE_NOT_SUPPORTED; 19 import static android.hardware.hdmi.DeviceFeatures.FEATURE_SUPPORTED; 20 21 import static com.android.server.hdmi.Constants.ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON; 22 import static com.android.server.hdmi.Constants.PROPERTY_SYSTEM_AUDIO_CONTROL_ON_POWER_ON; 23 import static com.android.server.hdmi.Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON; 24 25 import android.annotation.Nullable; 26 import android.content.ActivityNotFoundException; 27 import android.content.Intent; 28 import android.hardware.hdmi.DeviceFeatures; 29 import android.hardware.hdmi.HdmiControlManager; 30 import android.hardware.hdmi.HdmiDeviceInfo; 31 import android.hardware.hdmi.HdmiPortInfo; 32 import android.hardware.hdmi.IHdmiControlCallback; 33 import android.media.AudioDeviceInfo; 34 import android.media.AudioFormat; 35 import android.media.AudioManager; 36 import android.media.AudioSystem; 37 import android.media.tv.TvContract; 38 import android.media.tv.TvInputInfo; 39 import android.media.tv.TvInputManager.TvInputCallback; 40 import android.os.SystemProperties; 41 import android.sysprop.HdmiProperties; 42 import android.util.Slog; 43 44 import com.android.internal.annotations.GuardedBy; 45 import com.android.internal.annotations.VisibleForTesting; 46 import com.android.internal.util.IndentingPrintWriter; 47 import com.android.server.hdmi.Constants.AudioCodec; 48 import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback; 49 import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly; 50 import com.android.server.hdmi.HdmiUtils.CodecSad; 51 import com.android.server.hdmi.HdmiUtils.DeviceConfig; 52 53 import org.xmlpull.v1.XmlPullParserException; 54 55 import java.io.File; 56 import java.io.FileInputStream; 57 import java.io.IOException; 58 import java.io.InputStream; 59 import java.util.ArrayList; 60 import java.util.Arrays; 61 import java.util.HashMap; 62 import java.util.List; 63 import java.util.stream.Collectors; 64 65 /** 66 * Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in Android 67 * system. 68 */ 69 public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource { 70 71 private static final String TAG = "HdmiCecLocalDeviceAudioSystem"; 72 73 private static final boolean WAKE_ON_HOTPLUG = false; 74 75 // Whether the System Audio Control feature is enabled or not. True by default. 76 @GuardedBy("mLock") 77 private boolean mSystemAudioControlFeatureEnabled; 78 79 /** 80 * Indicates if the TV that the current device is connected to supports System Audio Mode or not 81 * 82 * <p>If the current device has no information on this, keep mTvSystemAudioModeSupport null 83 * 84 * <p>The boolean will be reset to null every time when the current device goes to standby 85 * or loses its physical address. 86 */ 87 private Boolean mTvSystemAudioModeSupport = null; 88 89 // Whether ARC is available or not. "true" means that ARC is established between TV and 90 // AVR as audio receiver. 91 @ServiceThreadOnly private boolean mArcEstablished = false; 92 93 // If the current device uses TvInput for ARC. We assume all other inputs also use TvInput 94 // when ARC is using TvInput. 95 private boolean mArcIntentUsed = HdmiProperties.arc_port().orElse("0").contains("tvinput"); 96 97 // Keeps the mapping (HDMI port ID to TV input URI) to keep track of the TV inputs ready to 98 // accept input switching request from HDMI devices. 99 @GuardedBy("mLock") 100 private final HashMap<Integer, String> mPortIdToTvInputs = new HashMap<>(); 101 102 // A map from TV input id to HDMI device info. 103 @GuardedBy("mLock") 104 private final HashMap<String, HdmiDeviceInfo> mTvInputsToDeviceInfo = new HashMap<>(); 105 106 // Message buffer used to buffer selected messages to process later. <Active Source> 107 // from a source device, for instance, needs to be buffered if the device is not 108 // discovered yet. The buffered commands are taken out and when they are ready to 109 // handle. 110 private final DelayedMessageBuffer mDelayedMessageBuffer = new DelayedMessageBuffer(this); 111 HdmiCecLocalDeviceAudioSystem(HdmiControlService service)112 protected HdmiCecLocalDeviceAudioSystem(HdmiControlService service) { 113 super(service, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM); 114 mRoutingControlFeatureEnabled = mService.getHdmiCecConfig().getIntValue( 115 HdmiControlManager.CEC_SETTING_NAME_ROUTING_CONTROL) 116 == HdmiControlManager.ROUTING_CONTROL_ENABLED; 117 mSystemAudioControlFeatureEnabled = mService.getHdmiCecConfig().getIntValue( 118 HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL) 119 == HdmiControlManager.SYSTEM_AUDIO_CONTROL_ENABLED; 120 mStandbyHandler = new HdmiCecStandbyModeHandler(service, this); 121 } 122 123 private static final String SHORT_AUDIO_DESCRIPTOR_CONFIG_PATH = "/vendor/etc/sadConfig.xml"; 124 125 private final TvInputCallback mTvInputCallback = new TvInputCallback() { 126 @Override 127 public void onInputAdded(String inputId) { 128 addOrUpdateTvInput(inputId); 129 } 130 131 @Override 132 public void onInputRemoved(String inputId) { 133 removeTvInput(inputId); 134 } 135 136 @Override 137 public void onInputUpdated(String inputId) { 138 addOrUpdateTvInput(inputId); 139 } 140 }; 141 142 @ServiceThreadOnly addOrUpdateTvInput(String inputId)143 private void addOrUpdateTvInput(String inputId) { 144 assertRunOnServiceThread(); 145 synchronized (mLock) { 146 TvInputInfo tvInfo = mService.getTvInputManager().getTvInputInfo(inputId); 147 if (tvInfo == null) { 148 return; 149 } 150 HdmiDeviceInfo info = tvInfo.getHdmiDeviceInfo(); 151 if (info == null) { 152 return; 153 } 154 mPortIdToTvInputs.put(info.getPortId(), inputId); 155 mTvInputsToDeviceInfo.put(inputId, info); 156 if (info.isCecDevice()) { 157 processDelayedActiveSource(info.getLogicalAddress()); 158 } 159 } 160 } 161 162 @ServiceThreadOnly removeTvInput(String inputId)163 private void removeTvInput(String inputId) { 164 assertRunOnServiceThread(); 165 synchronized (mLock) { 166 if (mTvInputsToDeviceInfo.get(inputId) == null) { 167 return; 168 } 169 int portId = mTvInputsToDeviceInfo.get(inputId).getPortId(); 170 mPortIdToTvInputs.remove(portId); 171 mTvInputsToDeviceInfo.remove(inputId); 172 } 173 } 174 175 @Override 176 @ServiceThreadOnly isInputReady(int portId)177 protected boolean isInputReady(int portId) { 178 assertRunOnServiceThread(); 179 String tvInputId = mPortIdToTvInputs.get(portId); 180 HdmiDeviceInfo info = mTvInputsToDeviceInfo.get(tvInputId); 181 return info != null; 182 } 183 184 @Override computeDeviceFeatures()185 protected DeviceFeatures computeDeviceFeatures() { 186 boolean arcSupport = SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true); 187 188 return DeviceFeatures.NO_FEATURES_SUPPORTED.toBuilder() 189 .setArcRxSupport(arcSupport ? FEATURE_SUPPORTED : FEATURE_NOT_SUPPORTED) 190 .build(); 191 } 192 193 @Override 194 @ServiceThreadOnly onHotplug(int portId, boolean connected)195 void onHotplug(int portId, boolean connected) { 196 assertRunOnServiceThread(); 197 if (WAKE_ON_HOTPLUG && connected) { 198 mService.wakeUp(); 199 } 200 if (mService.getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) { 201 mCecMessageCache.flushAll(); 202 if (!connected) { 203 if (isSystemAudioActivated()) { 204 mTvSystemAudioModeSupport = null; 205 checkSupportAndSetSystemAudioMode(false); 206 } 207 if (isArcEnabled()) { 208 setArcStatus(false); 209 } 210 } 211 } else if (!connected && mPortIdToTvInputs.get(portId) != null) { 212 String tvInputId = mPortIdToTvInputs.get(portId); 213 HdmiDeviceInfo info = mTvInputsToDeviceInfo.get(tvInputId); 214 if (info == null) { 215 return; 216 } 217 // Update with TIF on the device removal. TIF callback will update 218 // mPortIdToTvInputs and mPortIdToTvInputs. 219 mService.getHdmiCecNetwork().removeCecDevice(this, info.getLogicalAddress()); 220 } 221 } 222 223 @Override 224 @ServiceThreadOnly disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback)225 protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) { 226 super.disableDevice(initiatedByCec, callback); 227 assertRunOnServiceThread(); 228 mService.unregisterTvInputCallback(mTvInputCallback); 229 } 230 231 @Override 232 @ServiceThreadOnly onStandby(boolean initiatedByCec, int standbyAction)233 protected void onStandby(boolean initiatedByCec, int standbyAction) { 234 assertRunOnServiceThread(); 235 // Invalidate the internal active source record when goes to standby 236 // This set will also update mIsActiveSource 237 mService.setActiveSource(Constants.ADDR_INVALID, Constants.INVALID_PHYSICAL_ADDRESS, 238 "HdmiCecLocalDeviceAudioSystem#onStandby()"); 239 mTvSystemAudioModeSupport = null; 240 // Record the last state of System Audio Control before going to standby 241 synchronized (mLock) { 242 mService.writeStringSystemProperty( 243 Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL, 244 isSystemAudioActivated() ? "true" : "false"); 245 } 246 terminateSystemAudioMode(); 247 } 248 249 @Override 250 @ServiceThreadOnly onAddressAllocated(int logicalAddress, int reason)251 protected void onAddressAllocated(int logicalAddress, int reason) { 252 assertRunOnServiceThread(); 253 if (reason == mService.INITIATED_BY_ENABLE_CEC) { 254 mService.setAndBroadcastActiveSource(mService.getPhysicalAddress(), 255 getDeviceInfo().getDeviceType(), Constants.ADDR_BROADCAST, 256 "HdmiCecLocalDeviceAudioSystem#onAddressAllocated()"); 257 } 258 mService.sendCecCommand( 259 HdmiCecMessageBuilder.buildReportPhysicalAddressCommand( 260 getDeviceInfo().getLogicalAddress(), 261 mService.getPhysicalAddress(), 262 mDeviceType)); 263 mService.sendCecCommand( 264 HdmiCecMessageBuilder.buildDeviceVendorIdCommand( 265 getDeviceInfo().getLogicalAddress(), mService.getVendorId())); 266 mService.registerTvInputCallback(mTvInputCallback); 267 // Some TVs, for example Mi TV, need ARC on before turning System Audio Mode on 268 // to request Short Audio Descriptor. Since ARC and SAM are independent, 269 // we can turn on ARC anyways when audio system device just boots up. 270 initArcOnFromAvr(); 271 272 // This prevents turning on of System Audio Mode during a quiescent boot. If the quiescent 273 // boot is exited just after this check, this code will be executed only at the next 274 // wake-up. 275 if (!mService.isScreenOff()) { 276 int systemAudioControlOnPowerOnProp = 277 SystemProperties.getInt( 278 PROPERTY_SYSTEM_AUDIO_CONTROL_ON_POWER_ON, 279 ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON); 280 boolean lastSystemAudioControlStatus = 281 SystemProperties.getBoolean(Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL, true); 282 systemAudioControlOnPowerOn( 283 systemAudioControlOnPowerOnProp, lastSystemAudioControlStatus); 284 } 285 mService.getHdmiCecNetwork().clearDeviceList(); 286 launchDeviceDiscovery(); 287 startQueuedActions(); 288 } 289 290 @Override findKeyReceiverAddress()291 protected int findKeyReceiverAddress() { 292 if (getActiveSource().isValid()) { 293 return getActiveSource().logicalAddress; 294 } 295 return Constants.ADDR_INVALID; 296 } 297 298 @VisibleForTesting systemAudioControlOnPowerOn( int systemAudioOnPowerOnProp, boolean lastSystemAudioControlStatus)299 protected void systemAudioControlOnPowerOn( 300 int systemAudioOnPowerOnProp, boolean lastSystemAudioControlStatus) { 301 if ((systemAudioOnPowerOnProp == ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON) 302 || ((systemAudioOnPowerOnProp == USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON) 303 && lastSystemAudioControlStatus && isSystemAudioControlFeatureEnabled())) { 304 addAndStartAction(new SystemAudioInitiationActionFromAvr(this)); 305 } 306 } 307 308 @Override 309 @ServiceThreadOnly getPreferredAddress()310 protected int getPreferredAddress() { 311 assertRunOnServiceThread(); 312 return SystemProperties.getInt( 313 Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, Constants.ADDR_UNREGISTERED); 314 } 315 316 @Override 317 @ServiceThreadOnly setPreferredAddress(int addr)318 protected void setPreferredAddress(int addr) { 319 assertRunOnServiceThread(); 320 mService.writeStringSystemProperty( 321 Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, String.valueOf(addr)); 322 } 323 324 @ServiceThreadOnly processDelayedActiveSource(int address)325 void processDelayedActiveSource(int address) { 326 assertRunOnServiceThread(); 327 mDelayedMessageBuffer.processActiveSource(address); 328 } 329 330 @Override 331 @ServiceThreadOnly 332 @Constants.HandleMessageResult handleActiveSource(HdmiCecMessage message)333 protected int handleActiveSource(HdmiCecMessage message) { 334 assertRunOnServiceThread(); 335 int logicalAddress = message.getSource(); 336 int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); 337 if (HdmiUtils.getLocalPortFromPhysicalAddress( 338 physicalAddress, mService.getPhysicalAddress()) 339 == HdmiUtils.TARGET_NOT_UNDER_LOCAL_DEVICE) { 340 return super.handleActiveSource(message); 341 } 342 // If the new Active Source is under the current device, check if the device info and the TV 343 // input is ready to switch to the new Active Source. If not ready, buffer the cec command 344 // to handle later when the device is ready. 345 HdmiDeviceInfo info = mService.getHdmiCecNetwork().getCecDeviceInfo(logicalAddress); 346 if (info == null) { 347 HdmiLogger.debug("Device info %X not found; buffering the command", logicalAddress); 348 mDelayedMessageBuffer.add(message); 349 } else if (!isInputReady(info.getPortId())){ 350 HdmiLogger.debug("Input not ready for device: %X; buffering the command", info.getId()); 351 mDelayedMessageBuffer.add(message); 352 } else { 353 mDelayedMessageBuffer.removeActiveSource(); 354 return super.handleActiveSource(message); 355 } 356 return Constants.HANDLED; 357 } 358 359 @Override 360 @ServiceThreadOnly 361 @Constants.HandleMessageResult handleInitiateArc(HdmiCecMessage message)362 protected int handleInitiateArc(HdmiCecMessage message) { 363 assertRunOnServiceThread(); 364 // TODO(amyjojo): implement initiate arc handler 365 HdmiLogger.debug(TAG + "Stub handleInitiateArc"); 366 return Constants.HANDLED; 367 } 368 369 @Override 370 @ServiceThreadOnly 371 @Constants.HandleMessageResult handleReportArcInitiate(HdmiCecMessage message)372 protected int handleReportArcInitiate(HdmiCecMessage message) { 373 assertRunOnServiceThread(); 374 /* 375 * Ideally, we should have got this response before the {@link ArcInitiationActionFromAvr} 376 * has timed out. Even if the response is late, {@link ArcInitiationActionFromAvr 377 * #handleInitiateArcTimeout()} would not have disabled ARC. So nothing needs to be done 378 * here. 379 */ 380 return Constants.HANDLED; 381 } 382 383 @Override 384 @ServiceThreadOnly 385 @Constants.HandleMessageResult handleReportArcTermination(HdmiCecMessage message)386 protected int handleReportArcTermination(HdmiCecMessage message) { 387 assertRunOnServiceThread(); 388 processArcTermination(); 389 return Constants.HANDLED; 390 } 391 392 @Override 393 @ServiceThreadOnly 394 @Constants.HandleMessageResult handleGiveAudioStatus(HdmiCecMessage message)395 protected int handleGiveAudioStatus(HdmiCecMessage message) { 396 assertRunOnServiceThread(); 397 if (isSystemAudioControlFeatureEnabled() && mService.getHdmiCecVolumeControl() 398 == HdmiControlManager.VOLUME_CONTROL_ENABLED) { 399 reportAudioStatus(message.getSource()); 400 return Constants.HANDLED; 401 } 402 return Constants.ABORT_REFUSED; 403 } 404 405 @Override 406 @ServiceThreadOnly 407 @Constants.HandleMessageResult handleGiveSystemAudioModeStatus(HdmiCecMessage message)408 protected int handleGiveSystemAudioModeStatus(HdmiCecMessage message) { 409 assertRunOnServiceThread(); 410 // If the audio system is initiating the system audio mode on and TV asks the sam status at 411 // the same time, respond with true. Since we know TV supports sam in this situation. 412 // If the query comes from STB, we should respond with the current sam status and the STB 413 // should listen to the <Set System Audio Mode> broadcasting. 414 boolean isSystemAudioModeOnOrTurningOn = isSystemAudioActivated(); 415 if (!isSystemAudioModeOnOrTurningOn 416 && message.getSource() == Constants.ADDR_TV 417 && hasAction(SystemAudioInitiationActionFromAvr.class)) { 418 isSystemAudioModeOnOrTurningOn = true; 419 } 420 mService.sendCecCommand( 421 HdmiCecMessageBuilder.buildReportSystemAudioMode( 422 getDeviceInfo().getLogicalAddress(), 423 message.getSource(), 424 isSystemAudioModeOnOrTurningOn)); 425 return Constants.HANDLED; 426 } 427 428 @Override 429 @ServiceThreadOnly 430 @Constants.HandleMessageResult handleRequestArcInitiate(HdmiCecMessage message)431 protected int handleRequestArcInitiate(HdmiCecMessage message) { 432 assertRunOnServiceThread(); 433 removeAction(ArcInitiationActionFromAvr.class); 434 if (!mService.readBooleanSystemProperty(Constants.PROPERTY_ARC_SUPPORT, true)) { 435 return Constants.ABORT_UNRECOGNIZED_OPCODE; 436 } else if (!isDirectConnectToTv()) { 437 HdmiLogger.debug("AVR device is not directly connected with TV"); 438 return Constants.ABORT_NOT_IN_CORRECT_MODE; 439 } else { 440 addAndStartAction(new ArcInitiationActionFromAvr(this)); 441 return Constants.HANDLED; 442 } 443 } 444 445 @Override 446 @ServiceThreadOnly 447 @Constants.HandleMessageResult handleRequestArcTermination(HdmiCecMessage message)448 protected int handleRequestArcTermination(HdmiCecMessage message) { 449 assertRunOnServiceThread(); 450 if (!SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) { 451 return Constants.ABORT_UNRECOGNIZED_OPCODE; 452 } else if (!isArcEnabled()) { 453 HdmiLogger.debug("ARC is not established between TV and AVR device"); 454 return Constants.ABORT_NOT_IN_CORRECT_MODE; 455 } else { 456 removeAction(ArcTerminationActionFromAvr.class); 457 addAndStartAction(new ArcTerminationActionFromAvr(this)); 458 return Constants.HANDLED; 459 } 460 } 461 462 @ServiceThreadOnly 463 @Constants.HandleMessageResult handleRequestShortAudioDescriptor(HdmiCecMessage message)464 protected int handleRequestShortAudioDescriptor(HdmiCecMessage message) { 465 assertRunOnServiceThread(); 466 HdmiLogger.debug(TAG + "Stub handleRequestShortAudioDescriptor"); 467 if (!isSystemAudioControlFeatureEnabled()) { 468 return Constants.ABORT_REFUSED; 469 } 470 if (!isSystemAudioActivated()) { 471 return Constants.ABORT_NOT_IN_CORRECT_MODE; 472 } 473 474 List<DeviceConfig> config = null; 475 File file = new File(SHORT_AUDIO_DESCRIPTOR_CONFIG_PATH); 476 if (file.exists()) { 477 try { 478 InputStream in = new FileInputStream(file); 479 config = HdmiUtils.ShortAudioDescriptorXmlParser.parse(in); 480 in.close(); 481 } catch (IOException e) { 482 Slog.e(TAG, "Error reading file: " + file, e); 483 } catch (XmlPullParserException e) { 484 Slog.e(TAG, "Unable to parse file: " + file, e); 485 } 486 } 487 488 @AudioCodec int[] audioFormatCodes = parseAudioFormatCodes(message.getParams()); 489 byte[] sadBytes; 490 if (config != null && config.size() > 0) { 491 sadBytes = getSupportedShortAudioDescriptorsFromConfig(config, audioFormatCodes); 492 } else { 493 AudioDeviceInfo deviceInfo = getSystemAudioDeviceInfo(); 494 if (deviceInfo == null) { 495 return Constants.ABORT_UNABLE_TO_DETERMINE; 496 } 497 498 sadBytes = getSupportedShortAudioDescriptors(deviceInfo, audioFormatCodes); 499 } 500 501 if (sadBytes.length == 0) { 502 return Constants.ABORT_INVALID_OPERAND; 503 } else { 504 mService.sendCecCommand( 505 HdmiCecMessageBuilder.buildReportShortAudioDescriptor( 506 getDeviceInfo().getLogicalAddress(), message.getSource(), sadBytes)); 507 return Constants.HANDLED; 508 } 509 } 510 getSupportedShortAudioDescriptors( AudioDeviceInfo deviceInfo, @AudioCodec int[] audioFormatCodes)511 private byte[] getSupportedShortAudioDescriptors( 512 AudioDeviceInfo deviceInfo, @AudioCodec int[] audioFormatCodes) { 513 ArrayList<byte[]> sads = new ArrayList<>(audioFormatCodes.length); 514 for (@AudioCodec int audioFormatCode : audioFormatCodes) { 515 byte[] sad = getSupportedShortAudioDescriptor(deviceInfo, audioFormatCode); 516 if (sad != null) { 517 if (sad.length == 3) { 518 519 sads.add(sad); 520 } else { 521 HdmiLogger.warning( 522 "Dropping Short Audio Descriptor with length %d for requested codec %x", 523 sad.length, audioFormatCode); 524 } 525 } 526 } 527 return getShortAudioDescriptorBytes(sads); 528 } 529 getSupportedShortAudioDescriptorsFromConfig( List<DeviceConfig> deviceConfig, @AudioCodec int[] audioFormatCodes)530 private byte[] getSupportedShortAudioDescriptorsFromConfig( 531 List<DeviceConfig> deviceConfig, @AudioCodec int[] audioFormatCodes) { 532 DeviceConfig deviceConfigToUse = null; 533 String audioDeviceName = SystemProperties.get( 534 Constants.PROPERTY_SYSTEM_AUDIO_MODE_AUDIO_PORT, 535 "VX_AUDIO_DEVICE_IN_HDMI_ARC"); 536 for (DeviceConfig device : deviceConfig) { 537 if (device.name.equals(audioDeviceName)) { 538 deviceConfigToUse = device; 539 break; 540 } 541 } 542 if (deviceConfigToUse == null) { 543 Slog.w(TAG, "sadConfig.xml does not have required device info for " + audioDeviceName); 544 return new byte[0]; 545 } 546 HashMap<Integer, byte[]> map = new HashMap<>(); 547 ArrayList<byte[]> sads = new ArrayList<>(audioFormatCodes.length); 548 for (CodecSad codecSad : deviceConfigToUse.supportedCodecs) { 549 map.put(codecSad.audioCodec, codecSad.sad); 550 } 551 for (int i = 0; i < audioFormatCodes.length; i++) { 552 if (map.containsKey(audioFormatCodes[i])) { 553 byte[] sad = map.get(audioFormatCodes[i]); 554 if (sad != null && sad.length == 3) { 555 sads.add(sad); 556 } 557 } 558 } 559 return getShortAudioDescriptorBytes(sads); 560 } 561 getShortAudioDescriptorBytes(ArrayList<byte[]> sads)562 private byte[] getShortAudioDescriptorBytes(ArrayList<byte[]> sads) { 563 // Short Audio Descriptors are always 3 bytes long. 564 byte[] bytes = new byte[sads.size() * 3]; 565 int index = 0; 566 for (byte[] sad : sads) { 567 System.arraycopy(sad, 0, bytes, index, 3); 568 index += 3; 569 } 570 return bytes; 571 } 572 573 /** 574 * Returns a 3 byte short audio descriptor as described in CEC 1.4 table 29 or null if the 575 * audioFormatCode is not supported. 576 */ 577 @Nullable getSupportedShortAudioDescriptor( AudioDeviceInfo deviceInfo, @AudioCodec int audioFormatCode)578 private byte[] getSupportedShortAudioDescriptor( 579 AudioDeviceInfo deviceInfo, @AudioCodec int audioFormatCode) { 580 switch (audioFormatCode) { 581 case Constants.AUDIO_CODEC_NONE: { 582 return null; 583 } 584 case Constants.AUDIO_CODEC_LPCM: { 585 return getLpcmShortAudioDescriptor(deviceInfo); 586 } 587 // TODO(b/80297701): implement the rest of the codecs 588 case Constants.AUDIO_CODEC_DD: 589 case Constants.AUDIO_CODEC_MPEG1: 590 case Constants.AUDIO_CODEC_MP3: 591 case Constants.AUDIO_CODEC_MPEG2: 592 case Constants.AUDIO_CODEC_AAC: 593 case Constants.AUDIO_CODEC_DTS: 594 case Constants.AUDIO_CODEC_ATRAC: 595 case Constants.AUDIO_CODEC_ONEBITAUDIO: 596 case Constants.AUDIO_CODEC_DDP: 597 case Constants.AUDIO_CODEC_DTSHD: 598 case Constants.AUDIO_CODEC_TRUEHD: 599 case Constants.AUDIO_CODEC_DST: 600 case Constants.AUDIO_CODEC_WMAPRO: 601 default: { 602 return null; 603 } 604 } 605 } 606 607 @Nullable getLpcmShortAudioDescriptor(AudioDeviceInfo deviceInfo)608 private byte[] getLpcmShortAudioDescriptor(AudioDeviceInfo deviceInfo) { 609 // TODO(b/80297701): implement 610 return null; 611 } 612 613 @Nullable getSystemAudioDeviceInfo()614 private AudioDeviceInfo getSystemAudioDeviceInfo() { 615 AudioManager audioManager = mService.getContext().getSystemService(AudioManager.class); 616 if (audioManager == null) { 617 HdmiLogger.error( 618 "Error getting system audio device because AudioManager not available."); 619 return null; 620 } 621 AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS); 622 HdmiLogger.debug("Found %d audio input devices", devices.length); 623 for (AudioDeviceInfo device : devices) { 624 HdmiLogger.debug("%s at port %s", device.getProductName(), device.getPort()); 625 HdmiLogger.debug("Supported encodings are %s", 626 Arrays.stream(device.getEncodings()).mapToObj( 627 AudioFormat::toLogFriendlyEncoding 628 ).collect(Collectors.joining(", "))); 629 if (device.getType() == AudioDeviceInfo.TYPE_HDMI_ARC) { 630 return device; 631 } 632 } 633 return null; 634 } 635 636 @AudioCodec parseAudioFormatCodes(byte[] params)637 private int[] parseAudioFormatCodes(byte[] params) { 638 @AudioCodec int[] audioFormatCodes = new int[params.length]; 639 for (int i = 0; i < params.length; i++) { 640 byte val = params[i]; 641 audioFormatCodes[i] = 642 val >= 1 && val <= Constants.AUDIO_CODEC_MAX ? val : Constants.AUDIO_CODEC_NONE; 643 } 644 return audioFormatCodes; 645 } 646 647 @Override 648 @ServiceThreadOnly 649 @Constants.HandleMessageResult handleSystemAudioModeRequest(HdmiCecMessage message)650 protected int handleSystemAudioModeRequest(HdmiCecMessage message) { 651 assertRunOnServiceThread(); 652 boolean systemAudioStatusOn = message.getParams().length != 0; 653 // Check if the request comes from a non-TV device. 654 // Need to check if TV supports System Audio Control 655 // if non-TV device tries to turn on the feature 656 if (message.getSource() != Constants.ADDR_TV) { 657 if (systemAudioStatusOn) { 658 return handleSystemAudioModeOnFromNonTvDevice(message); 659 } 660 } else { 661 // If TV request the feature on 662 // cache TV supporting System Audio Control 663 // until Audio System loses its physical address. 664 setTvSystemAudioModeSupport(true); 665 } 666 // If TV or Audio System does not support the feature, 667 // will send abort command. 668 if (!checkSupportAndSetSystemAudioMode(systemAudioStatusOn)) { 669 return Constants.ABORT_REFUSED; 670 } 671 672 mService.sendCecCommand( 673 HdmiCecMessageBuilder.buildSetSystemAudioMode( 674 getDeviceInfo().getLogicalAddress(), 675 Constants.ADDR_BROADCAST, 676 systemAudioStatusOn)); 677 678 if (systemAudioStatusOn) { 679 // If TV sends out SAM Request with a path of a non-CEC device, which should not show 680 // up in the CEC device list and not under the current AVR device, the AVR would switch 681 // to ARC. 682 int sourcePhysicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); 683 if (HdmiUtils.getLocalPortFromPhysicalAddress( 684 sourcePhysicalAddress, getDeviceInfo().getPhysicalAddress()) 685 != HdmiUtils.TARGET_NOT_UNDER_LOCAL_DEVICE) { 686 return Constants.HANDLED; 687 } 688 HdmiDeviceInfo safeDeviceInfoByPath = 689 mService.getHdmiCecNetwork().getSafeDeviceInfoByPath(sourcePhysicalAddress); 690 if (safeDeviceInfoByPath == null) { 691 switchInputOnReceivingNewActivePath(sourcePhysicalAddress); 692 } 693 } 694 return Constants.HANDLED; 695 } 696 697 @Override 698 @ServiceThreadOnly 699 @Constants.HandleMessageResult handleSetSystemAudioMode(HdmiCecMessage message)700 protected int handleSetSystemAudioMode(HdmiCecMessage message) { 701 assertRunOnServiceThread(); 702 if (!checkSupportAndSetSystemAudioMode( 703 HdmiUtils.parseCommandParamSystemAudioStatus(message))) { 704 return Constants.ABORT_REFUSED; 705 } 706 return Constants.HANDLED; 707 } 708 709 @Override 710 @ServiceThreadOnly 711 @Constants.HandleMessageResult handleSystemAudioModeStatus(HdmiCecMessage message)712 protected int handleSystemAudioModeStatus(HdmiCecMessage message) { 713 assertRunOnServiceThread(); 714 if (!checkSupportAndSetSystemAudioMode( 715 HdmiUtils.parseCommandParamSystemAudioStatus(message))) { 716 return Constants.ABORT_REFUSED; 717 } 718 return Constants.HANDLED; 719 } 720 721 @ServiceThreadOnly setArcStatus(boolean enabled)722 void setArcStatus(boolean enabled) { 723 assertRunOnServiceThread(); 724 725 HdmiLogger.debug("Set Arc Status[old:%b new:%b]", mArcEstablished, enabled); 726 // 1. Enable/disable ARC circuit. 727 enableAudioReturnChannel(enabled); 728 // 2. Notify arc status to audio service. 729 notifyArcStatusToAudioService(enabled); 730 // 3. Update arc status; 731 mArcEstablished = enabled; 732 } 733 processArcTermination()734 void processArcTermination() { 735 setArcStatus(false); 736 // Switch away from ARC input when ARC is terminated. 737 if (getLocalActivePort() == Constants.CEC_SWITCH_ARC) { 738 routeToInputFromPortId(getRoutingPort()); 739 } 740 } 741 742 /** Switch hardware ARC circuit in the system. */ 743 @ServiceThreadOnly enableAudioReturnChannel(boolean enabled)744 private void enableAudioReturnChannel(boolean enabled) { 745 assertRunOnServiceThread(); 746 mService.enableAudioReturnChannel( 747 Integer.parseInt(HdmiProperties.arc_port().orElse("0")), 748 enabled); 749 } 750 notifyArcStatusToAudioService(boolean enabled)751 private void notifyArcStatusToAudioService(boolean enabled) { 752 // Note that we don't set any name to ARC. 753 mService.getAudioManager() 754 .setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI, enabled ? 1 : 0, "", ""); 755 } 756 reportAudioStatus(int source)757 void reportAudioStatus(int source) { 758 assertRunOnServiceThread(); 759 if (mService.getHdmiCecVolumeControl() 760 == HdmiControlManager.VOLUME_CONTROL_DISABLED) { 761 return; 762 } 763 764 int volume = mService.getAudioManager().getStreamVolume(AudioManager.STREAM_MUSIC); 765 boolean mute = mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC); 766 int maxVolume = mService.getAudioManager().getStreamMaxVolume(AudioManager.STREAM_MUSIC); 767 int minVolume = mService.getAudioManager().getStreamMinVolume(AudioManager.STREAM_MUSIC); 768 int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume); 769 HdmiLogger.debug("Reporting volume %d (%d-%d) as CEC volume %d", volume, 770 minVolume, maxVolume, scaledVolume); 771 772 mService.sendCecCommand( 773 HdmiCecMessageBuilder.buildReportAudioStatus( 774 getDeviceInfo().getLogicalAddress(), source, scaledVolume, mute)); 775 } 776 777 /** 778 * Method to check if device support System Audio Control. If so, wake up device if necessary. 779 * 780 * <p> then call {@link #setSystemAudioMode(boolean)} to turn on or off System Audio Mode 781 * @param newSystemAudioMode turning feature on or off. True is on. False is off. 782 * @return true or false. 783 * 784 * <p>False when device does not support the feature. Otherwise returns true. 785 */ checkSupportAndSetSystemAudioMode(boolean newSystemAudioMode)786 protected boolean checkSupportAndSetSystemAudioMode(boolean newSystemAudioMode) { 787 if (!isSystemAudioControlFeatureEnabled()) { 788 HdmiLogger.debug( 789 "Cannot turn " 790 + (newSystemAudioMode ? "on" : "off") 791 + "system audio mode " 792 + "because the System Audio Control feature is disabled."); 793 return false; 794 } 795 HdmiLogger.debug( 796 "System Audio Mode change[old:%b new:%b]", 797 isSystemAudioActivated(), newSystemAudioMode); 798 // Wake up device if System Audio Control is turned on 799 if (newSystemAudioMode) { 800 mService.wakeUp(); 801 } 802 setSystemAudioMode(newSystemAudioMode); 803 return true; 804 } 805 806 /** 807 * Real work to turn on or off System Audio Mode. 808 * 809 * Use {@link #checkSupportAndSetSystemAudioMode(boolean)} 810 * if trying to turn on or off the feature. 811 */ setSystemAudioMode(boolean newSystemAudioMode)812 private void setSystemAudioMode(boolean newSystemAudioMode) { 813 int targetPhysicalAddress = getActiveSource().physicalAddress; 814 int port = mService.pathToPortId(targetPhysicalAddress); 815 if (newSystemAudioMode && port >= 0) { 816 switchToAudioInput(); 817 } 818 // Mute device when feature is turned off and unmute device when feature is turned on. 819 // CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING is false when device never needs to be muted. 820 boolean systemAudioModeMutingEnabled = mService.getHdmiCecConfig().getIntValue( 821 HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_MODE_MUTING) 822 == HdmiControlManager.SYSTEM_AUDIO_MODE_MUTING_ENABLED; 823 boolean currentMuteStatus = 824 mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC); 825 if (currentMuteStatus == newSystemAudioMode) { 826 if (systemAudioModeMutingEnabled || newSystemAudioMode) { 827 mService.getAudioManager() 828 .adjustStreamVolume( 829 AudioManager.STREAM_MUSIC, 830 newSystemAudioMode 831 ? AudioManager.ADJUST_UNMUTE 832 : AudioManager.ADJUST_MUTE, 833 0); 834 } 835 } 836 updateAudioManagerForSystemAudio(newSystemAudioMode); 837 synchronized (mLock) { 838 if (isSystemAudioActivated() != newSystemAudioMode) { 839 mService.setSystemAudioActivated(newSystemAudioMode); 840 mService.announceSystemAudioModeChange(newSystemAudioMode); 841 } 842 } 843 // Since ARC is independent from System Audio Mode control, when the TV requests 844 // System Audio Mode off, it does not need to terminate ARC at the same time. 845 // When the current audio device is using ARC as a TV input and disables muting, 846 // it needs to automatically switch to the previous active input source when System 847 // Audio Mode is off even without terminating the ARC. This can stop the current 848 // audio device from playing audio when system audio mode is off. 849 if (mArcIntentUsed 850 && !systemAudioModeMutingEnabled 851 && !newSystemAudioMode 852 && getLocalActivePort() == Constants.CEC_SWITCH_ARC) { 853 routeToInputFromPortId(getRoutingPort()); 854 } 855 // Init arc whenever System Audio Mode is on 856 // Since some TVs don't request ARC on with System Audio Mode on request 857 if (SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true) 858 && isDirectConnectToTv() && mService.isSystemAudioActivated()) { 859 if (!hasAction(ArcInitiationActionFromAvr.class)) { 860 addAndStartAction(new ArcInitiationActionFromAvr(this)); 861 } 862 } 863 } 864 switchToAudioInput()865 protected void switchToAudioInput() { 866 } 867 isDirectConnectToTv()868 protected boolean isDirectConnectToTv() { 869 int myPhysicalAddress = mService.getPhysicalAddress(); 870 return (myPhysicalAddress & Constants.ROUTING_PATH_TOP_MASK) == myPhysicalAddress; 871 } 872 updateAudioManagerForSystemAudio(boolean on)873 private void updateAudioManagerForSystemAudio(boolean on) { 874 int device = mService.getAudioManager().setHdmiSystemAudioSupported(on); 875 HdmiLogger.debug("[A]UpdateSystemAudio mode[on=%b] output=[%X]", on, device); 876 } 877 onSystemAudioControlFeatureSupportChanged(boolean enabled)878 void onSystemAudioControlFeatureSupportChanged(boolean enabled) { 879 setSystemAudioControlFeatureEnabled(enabled); 880 if (enabled) { 881 addAndStartAction(new SystemAudioInitiationActionFromAvr(this)); 882 } 883 } 884 885 @ServiceThreadOnly setSystemAudioControlFeatureEnabled(boolean enabled)886 void setSystemAudioControlFeatureEnabled(boolean enabled) { 887 assertRunOnServiceThread(); 888 synchronized (mLock) { 889 mSystemAudioControlFeatureEnabled = enabled; 890 } 891 } 892 893 @ServiceThreadOnly setRoutingControlFeatureEnabled(boolean enabled)894 void setRoutingControlFeatureEnabled(boolean enabled) { 895 assertRunOnServiceThread(); 896 synchronized (mLock) { 897 mRoutingControlFeatureEnabled = enabled; 898 } 899 } 900 901 @ServiceThreadOnly doManualPortSwitching(int portId, IHdmiControlCallback callback)902 void doManualPortSwitching(int portId, IHdmiControlCallback callback) { 903 assertRunOnServiceThread(); 904 if (!mService.isValidPortId(portId)) { 905 invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE); 906 return; 907 } 908 if (portId == getLocalActivePort()) { 909 invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS); 910 return; 911 } 912 if (!mService.isControlEnabled()) { 913 setRoutingPort(portId); 914 setLocalActivePort(portId); 915 invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE); 916 return; 917 } 918 int oldPath = getRoutingPort() != Constants.CEC_SWITCH_HOME 919 ? mService.portIdToPath(getRoutingPort()) 920 : getDeviceInfo().getPhysicalAddress(); 921 int newPath = mService.portIdToPath(portId); 922 if (oldPath == newPath) { 923 return; 924 } 925 setRoutingPort(portId); 926 setLocalActivePort(portId); 927 HdmiCecMessage routingChange = 928 HdmiCecMessageBuilder.buildRoutingChange( 929 getDeviceInfo().getLogicalAddress(), oldPath, newPath); 930 mService.sendCecCommand(routingChange); 931 } 932 isSystemAudioControlFeatureEnabled()933 boolean isSystemAudioControlFeatureEnabled() { 934 synchronized (mLock) { 935 return mSystemAudioControlFeatureEnabled; 936 } 937 } 938 isSystemAudioActivated()939 protected boolean isSystemAudioActivated() { 940 return mService.isSystemAudioActivated(); 941 } 942 terminateSystemAudioMode()943 protected void terminateSystemAudioMode() { 944 // remove pending initiation actions 945 removeAction(SystemAudioInitiationActionFromAvr.class); 946 if (!isSystemAudioActivated()) { 947 return; 948 } 949 950 if (checkSupportAndSetSystemAudioMode(false)) { 951 // send <Set System Audio Mode> [“Off”] 952 mService.sendCecCommand( 953 HdmiCecMessageBuilder.buildSetSystemAudioMode( 954 getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST, false)); 955 } 956 } 957 958 /** Reports if System Audio Mode is supported by the connected TV */ 959 interface TvSystemAudioModeSupportedCallback { 960 961 /** {@code supported} is true if the TV is connected and supports System Audio Mode. */ onResult(boolean supported)962 void onResult(boolean supported); 963 } 964 965 /** 966 * Queries the connected TV to detect if System Audio Mode is supported by the TV. 967 * 968 * <p>This query may take up to 2 seconds to complete. 969 * 970 * <p>The result of the query may be cached until Audio device type is put in standby or loses 971 * its physical address. 972 */ queryTvSystemAudioModeSupport(TvSystemAudioModeSupportedCallback callback)973 void queryTvSystemAudioModeSupport(TvSystemAudioModeSupportedCallback callback) { 974 if (mTvSystemAudioModeSupport == null) { 975 addAndStartAction(new DetectTvSystemAudioModeSupportAction(this, callback)); 976 } else { 977 callback.onResult(mTvSystemAudioModeSupport); 978 } 979 } 980 981 /** 982 * Handler of System Audio Mode Request on from non TV device 983 */ 984 @Constants.HandleMessageResult handleSystemAudioModeOnFromNonTvDevice(HdmiCecMessage message)985 int handleSystemAudioModeOnFromNonTvDevice(HdmiCecMessage message) { 986 if (!isSystemAudioControlFeatureEnabled()) { 987 HdmiLogger.debug( 988 "Cannot turn on" + "system audio mode " 989 + "because the System Audio Control feature is disabled."); 990 return Constants.ABORT_REFUSED; 991 } 992 // Wake up device 993 mService.wakeUp(); 994 // If Audio device is the active source or is on the active path, 995 // enable system audio mode without querying TV's support on sam. 996 // This is per HDMI spec 1.4b CEC 13.15.4.2. 997 if (mService.pathToPortId(getActiveSource().physicalAddress) 998 != Constants.INVALID_PORT_ID) { 999 setSystemAudioMode(true); 1000 mService.sendCecCommand( 1001 HdmiCecMessageBuilder.buildSetSystemAudioMode( 1002 getDeviceInfo().getLogicalAddress(), Constants.ADDR_BROADCAST, true)); 1003 return Constants.HANDLED; 1004 } 1005 // Check if TV supports System Audio Control. 1006 // Handle broadcasting setSystemAudioMode on or aborting message on callback. 1007 queryTvSystemAudioModeSupport(new TvSystemAudioModeSupportedCallback() { 1008 public void onResult(boolean supported) { 1009 if (supported) { 1010 setSystemAudioMode(true); 1011 mService.sendCecCommand( 1012 HdmiCecMessageBuilder.buildSetSystemAudioMode( 1013 getDeviceInfo().getLogicalAddress(), 1014 Constants.ADDR_BROADCAST, 1015 true)); 1016 } else { 1017 mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED); 1018 } 1019 } 1020 }); 1021 return Constants.HANDLED; 1022 } 1023 setTvSystemAudioModeSupport(boolean supported)1024 void setTvSystemAudioModeSupport(boolean supported) { 1025 mTvSystemAudioModeSupport = supported; 1026 } 1027 1028 @VisibleForTesting isArcEnabled()1029 protected boolean isArcEnabled() { 1030 synchronized (mLock) { 1031 return mArcEstablished; 1032 } 1033 } 1034 initArcOnFromAvr()1035 private void initArcOnFromAvr() { 1036 removeAction(ArcTerminationActionFromAvr.class); 1037 if (SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true) 1038 && isDirectConnectToTv() && !isArcEnabled()) { 1039 removeAction(ArcInitiationActionFromAvr.class); 1040 addAndStartAction(new ArcInitiationActionFromAvr(this)); 1041 } 1042 } 1043 1044 @Override switchInputOnReceivingNewActivePath(int physicalAddress)1045 protected void switchInputOnReceivingNewActivePath(int physicalAddress) { 1046 int port = mService.pathToPortId(physicalAddress); 1047 if (isSystemAudioActivated() && port < 0) { 1048 // If system audio mode is on and the new active source is not under the current device, 1049 // Will switch to ARC input. 1050 routeToInputFromPortId(Constants.CEC_SWITCH_ARC); 1051 } else if (mIsSwitchDevice && port >= 0) { 1052 // If current device is a switch and the new active source is under it, 1053 // will switch to the corresponding active path. 1054 routeToInputFromPortId(port); 1055 } 1056 } 1057 routeToInputFromPortId(int portId)1058 protected void routeToInputFromPortId(int portId) { 1059 if (!isRoutingControlFeatureEnabled()) { 1060 HdmiLogger.debug("Routing Control Feature is not enabled."); 1061 return; 1062 } 1063 if (mArcIntentUsed) { 1064 routeToTvInputFromPortId(portId); 1065 } else { 1066 // TODO(): implement input switching for devices not using TvInput. 1067 } 1068 } 1069 routeToTvInputFromPortId(int portId)1070 protected void routeToTvInputFromPortId(int portId) { 1071 if (portId < 0 || portId >= Constants.CEC_SWITCH_PORT_MAX) { 1072 HdmiLogger.debug("Invalid port number for Tv Input switching."); 1073 return; 1074 } 1075 // Wake up if the current device if ready to route. 1076 mService.wakeUp(); 1077 if ((getLocalActivePort() == portId) && (portId != Constants.CEC_SWITCH_ARC)) { 1078 HdmiLogger.debug("Not switching to the same port " + portId + " except for arc"); 1079 return; 1080 } 1081 // Switch to HOME if the current active port is not HOME yet 1082 if (portId == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) { 1083 switchToHomeTvInput(); 1084 } else if (portId == Constants.CEC_SWITCH_ARC) { 1085 switchToTvInput(HdmiProperties.arc_port().orElse("0")); 1086 setLocalActivePort(portId); 1087 return; 1088 } else { 1089 String uri = mPortIdToTvInputs.get(portId); 1090 if (uri != null) { 1091 switchToTvInput(uri); 1092 } else { 1093 HdmiLogger.debug("Port number does not match any Tv Input."); 1094 return; 1095 } 1096 } 1097 1098 setLocalActivePort(portId); 1099 setRoutingPort(portId); 1100 } 1101 1102 // For device to switch to specific TvInput with corresponding URI. switchToTvInput(String uri)1103 private void switchToTvInput(String uri) { 1104 try { 1105 mService.getContext().startActivity(new Intent(Intent.ACTION_VIEW, 1106 TvContract.buildChannelUriForPassthroughInput(uri)) 1107 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); 1108 } catch (ActivityNotFoundException e) { 1109 Slog.e(TAG, "Can't find activity to switch to " + uri, e); 1110 } 1111 } 1112 1113 // For device using TvInput to switch to Home. switchToHomeTvInput()1114 private void switchToHomeTvInput() { 1115 try { 1116 Intent activityIntent = new Intent(Intent.ACTION_MAIN) 1117 .addCategory(Intent.CATEGORY_HOME) 1118 .setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP 1119 | Intent.FLAG_ACTIVITY_SINGLE_TOP 1120 | Intent.FLAG_ACTIVITY_NEW_TASK 1121 | Intent.FLAG_ACTIVITY_NO_ANIMATION); 1122 mService.getContext().startActivity(activityIntent); 1123 } catch (ActivityNotFoundException e) { 1124 Slog.e(TAG, "Can't find activity to switch to HOME", e); 1125 } 1126 } 1127 1128 @Override handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message)1129 protected void handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message) { 1130 int port = mService.pathToPortId(physicalAddress); 1131 // Routing change or information sent from switches under the current device can be ignored. 1132 if (port > 0) { 1133 return; 1134 } 1135 // When other switches route to some other devices not under the current device, 1136 // check system audio mode status and do ARC switch if needed. 1137 if (port < 0 && isSystemAudioActivated()) { 1138 handleRoutingChangeAndInformationForSystemAudio(); 1139 return; 1140 } 1141 // When other switches route to the current device 1142 // and the current device is also a switch. 1143 if (port == 0) { 1144 handleRoutingChangeAndInformationForSwitch(message); 1145 } 1146 } 1147 1148 // Handle the system audio(ARC) part of the logic on receiving routing change or information. handleRoutingChangeAndInformationForSystemAudio()1149 private void handleRoutingChangeAndInformationForSystemAudio() { 1150 routeToInputFromPortId(Constants.CEC_SWITCH_ARC); 1151 } 1152 1153 // Handle the routing control part of the logic on receiving routing change or information. handleRoutingChangeAndInformationForSwitch(HdmiCecMessage message)1154 private void handleRoutingChangeAndInformationForSwitch(HdmiCecMessage message) { 1155 if (getRoutingPort() == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) { 1156 routeToInputFromPortId(Constants.CEC_SWITCH_HOME); 1157 mService.setAndBroadcastActiveSourceFromOneDeviceType( 1158 message.getSource(), mService.getPhysicalAddress(), 1159 "HdmiCecLocalDeviceAudioSystem#handleRoutingChangeAndInformationForSwitch()"); 1160 return; 1161 } 1162 1163 int routingInformationPath = mService.portIdToPath(getRoutingPort()); 1164 // If current device is already the leaf of the whole HDMI system, will do nothing. 1165 if (routingInformationPath == mService.getPhysicalAddress()) { 1166 HdmiLogger.debug("Current device can't assign valid physical address" 1167 + "to devices under it any more. " 1168 + "It's physical address is " 1169 + routingInformationPath); 1170 return; 1171 } 1172 // Otherwise will switch to the current active port and broadcast routing information. 1173 mService.sendCecCommand( 1174 HdmiCecMessageBuilder.buildRoutingInformation( 1175 getDeviceInfo().getLogicalAddress(), routingInformationPath)); 1176 routeToInputFromPortId(getRoutingPort()); 1177 } 1178 1179 @ServiceThreadOnly launchDeviceDiscovery()1180 private void launchDeviceDiscovery() { 1181 assertRunOnServiceThread(); 1182 if (hasAction(DeviceDiscoveryAction.class)) { 1183 Slog.i(TAG, "Device Discovery Action is in progress. Restarting."); 1184 removeAction(DeviceDiscoveryAction.class); 1185 } 1186 DeviceDiscoveryAction action = new DeviceDiscoveryAction(this, 1187 new DeviceDiscoveryCallback() { 1188 @Override 1189 public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) { 1190 for (HdmiDeviceInfo info : deviceInfos) { 1191 mService.getHdmiCecNetwork().addCecDevice(info); 1192 } 1193 } 1194 }); 1195 addAndStartAction(action); 1196 } 1197 1198 @Override dump(IndentingPrintWriter pw)1199 protected void dump(IndentingPrintWriter pw) { 1200 pw.println("HdmiCecLocalDeviceAudioSystem:"); 1201 pw.increaseIndent(); 1202 pw.println("isRoutingFeatureEnabled " + isRoutingControlFeatureEnabled()); 1203 pw.println("mSystemAudioControlFeatureEnabled: " + mSystemAudioControlFeatureEnabled); 1204 pw.println("mTvSystemAudioModeSupport: " + mTvSystemAudioModeSupport); 1205 pw.println("mArcEstablished: " + mArcEstablished); 1206 pw.println("mArcIntentUsed: " + mArcIntentUsed); 1207 pw.println("mRoutingPort: " + getRoutingPort()); 1208 pw.println("mLocalActivePort: " + getLocalActivePort()); 1209 HdmiUtils.dumpMap(pw, "mPortIdToTvInputs:", mPortIdToTvInputs); 1210 HdmiUtils.dumpMap(pw, "mTvInputsToDeviceInfo:", mTvInputsToDeviceInfo); 1211 pw.decreaseIndent(); 1212 super.dump(pw); 1213 } 1214 } 1215