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