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