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