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