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