/*
|
* Copyright (C) 2018 The Android Open Source Project
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*/
|
package com.android.server.hdmi;
|
|
import static com.android.server.hdmi.Constants.ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON;
|
import static com.android.server.hdmi.Constants.PROPERTY_SYSTEM_AUDIO_CONTROL_ON_POWER_ON;
|
import static com.android.server.hdmi.Constants.USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON;
|
|
import android.annotation.Nullable;
|
import android.content.ActivityNotFoundException;
|
import android.content.Intent;
|
import android.hardware.hdmi.HdmiControlManager;
|
import android.hardware.hdmi.HdmiDeviceInfo;
|
import android.hardware.hdmi.HdmiPortInfo;
|
import android.hardware.hdmi.IHdmiControlCallback;
|
import android.media.AudioDeviceInfo;
|
import android.media.AudioFormat;
|
import android.media.AudioManager;
|
import android.media.AudioSystem;
|
import android.media.tv.TvContract;
|
import android.media.tv.TvInputInfo;
|
import android.media.tv.TvInputManager.TvInputCallback;
|
import android.os.SystemProperties;
|
import android.provider.Settings.Global;
|
import android.util.Slog;
|
import android.util.SparseArray;
|
|
import com.android.internal.annotations.GuardedBy;
|
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.util.IndentingPrintWriter;
|
import com.android.server.hdmi.Constants.AudioCodec;
|
import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
|
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
|
import com.android.server.hdmi.HdmiUtils.CodecSad;
|
import com.android.server.hdmi.HdmiUtils.DeviceConfig;
|
|
import org.xmlpull.v1.XmlPullParserException;
|
|
import java.io.File;
|
import java.io.FileInputStream;
|
import java.io.IOException;
|
import java.io.InputStream;
|
import java.io.UnsupportedEncodingException;
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.Collections;
|
import java.util.HashMap;
|
import java.util.List;
|
import java.util.stream.Collectors;
|
|
|
/**
|
* Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in Android
|
* system.
|
*/
|
public class HdmiCecLocalDeviceAudioSystem extends HdmiCecLocalDeviceSource {
|
|
private static final String TAG = "HdmiCecLocalDeviceAudioSystem";
|
|
// Whether the System Audio Control feature is enabled or not. True by default.
|
@GuardedBy("mLock")
|
private boolean mSystemAudioControlFeatureEnabled;
|
|
/**
|
* Indicates if the TV that the current device is connected to supports System Audio Mode or not
|
*
|
* <p>If the current device has no information on this, keep mTvSystemAudioModeSupport null
|
*
|
* <p>The boolean will be reset to null every time when the current device goes to standby
|
* or loses its physical address.
|
*/
|
private Boolean mTvSystemAudioModeSupport = null;
|
|
// Whether ARC is available or not. "true" means that ARC is established between TV and
|
// AVR as audio receiver.
|
@ServiceThreadOnly private boolean mArcEstablished = false;
|
|
// If the current device uses TvInput for ARC. We assume all other inputs also use TvInput
|
// when ARC is using TvInput.
|
private boolean mArcIntentUsed = SystemProperties
|
.get(Constants.PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT, "0").contains("tvinput");
|
|
// Keeps the mapping (HDMI port ID to TV input URI) to keep track of the TV inputs ready to
|
// accept input switching request from HDMI devices.
|
@GuardedBy("mLock")
|
private final HashMap<Integer, String> mPortIdToTvInputs = new HashMap<>();
|
|
// A map from TV input id to HDMI device info.
|
@GuardedBy("mLock")
|
private final HashMap<String, HdmiDeviceInfo> mTvInputsToDeviceInfo = new HashMap<>();
|
|
// Copy of mDeviceInfos to guarantee thread-safety.
|
@GuardedBy("mLock")
|
private List<HdmiDeviceInfo> mSafeAllDeviceInfos = Collections.emptyList();
|
|
// Map-like container of all cec devices.
|
// device id is used as key of container.
|
private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>();
|
|
protected HdmiCecLocalDeviceAudioSystem(HdmiControlService service) {
|
super(service, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
|
mRoutingControlFeatureEnabled =
|
mService.readBooleanSetting(Global.HDMI_CEC_SWITCH_ENABLED, false);
|
mSystemAudioControlFeatureEnabled =
|
mService.readBooleanSetting(Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED, true);
|
}
|
|
private static final String SHORT_AUDIO_DESCRIPTOR_CONFIG_PATH = "/vendor/etc/sadConfig.xml";
|
|
private final TvInputCallback mTvInputCallback = new TvInputCallback() {
|
@Override
|
public void onInputAdded(String inputId) {
|
addOrUpdateTvInput(inputId);
|
}
|
|
@Override
|
public void onInputRemoved(String inputId) {
|
removeTvInput(inputId);
|
}
|
|
@Override
|
public void onInputUpdated(String inputId) {
|
addOrUpdateTvInput(inputId);
|
}
|
};
|
|
@ServiceThreadOnly
|
private void addOrUpdateTvInput(String inputId) {
|
assertRunOnServiceThread();
|
synchronized (mLock) {
|
TvInputInfo tvInfo = mService.getTvInputManager().getTvInputInfo(inputId);
|
if (tvInfo == null) {
|
return;
|
}
|
HdmiDeviceInfo info = tvInfo.getHdmiDeviceInfo();
|
if (info == null) {
|
return;
|
}
|
mPortIdToTvInputs.put(info.getPortId(), inputId);
|
mTvInputsToDeviceInfo.put(inputId, info);
|
}
|
}
|
|
@ServiceThreadOnly
|
private void removeTvInput(String inputId) {
|
assertRunOnServiceThread();
|
synchronized (mLock) {
|
if (mTvInputsToDeviceInfo.get(inputId) == null) {
|
return;
|
}
|
int portId = mTvInputsToDeviceInfo.get(inputId).getPortId();
|
mPortIdToTvInputs.remove(portId);
|
mTvInputsToDeviceInfo.remove(inputId);
|
}
|
}
|
|
/**
|
* Called when a device is newly added or a new device is detected or
|
* an existing device is updated.
|
*
|
* @param info device info of a new device.
|
*/
|
@ServiceThreadOnly
|
final void addCecDevice(HdmiDeviceInfo info) {
|
assertRunOnServiceThread();
|
HdmiDeviceInfo old = addDeviceInfo(info);
|
if (info.getPhysicalAddress() == mService.getPhysicalAddress()) {
|
// The addition of the device itself should not be notified.
|
// Note that different logical address could still be the same local device.
|
return;
|
}
|
if (old == null) {
|
invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
|
} else if (!old.equals(info)) {
|
invokeDeviceEventListener(old, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
|
invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
|
}
|
}
|
|
/**
|
* Called when a device is removed or removal of device is detected.
|
*
|
* @param address a logical address of a device to be removed
|
*/
|
@ServiceThreadOnly
|
final void removeCecDevice(int address) {
|
assertRunOnServiceThread();
|
HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
|
|
mCecMessageCache.flushMessagesFrom(address);
|
invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
|
}
|
|
/**
|
* Called when a device is updated.
|
*
|
* @param info device info of the updating device.
|
*/
|
@ServiceThreadOnly
|
final void updateCecDevice(HdmiDeviceInfo info) {
|
assertRunOnServiceThread();
|
HdmiDeviceInfo old = addDeviceInfo(info);
|
|
if (old == null) {
|
invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
|
} else if (!old.equals(info)) {
|
invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
|
}
|
}
|
|
/**
|
* Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same
|
* logical address as new device info's.
|
*
|
* @param deviceInfo a new {@link HdmiDeviceInfo} to be added.
|
* @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo}
|
* that has the same logical address as new one has.
|
*/
|
@ServiceThreadOnly
|
@VisibleForTesting
|
protected HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) {
|
assertRunOnServiceThread();
|
HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress());
|
if (oldDeviceInfo != null) {
|
removeDeviceInfo(deviceInfo.getId());
|
}
|
mDeviceInfos.append(deviceInfo.getId(), deviceInfo);
|
updateSafeDeviceInfoList();
|
return oldDeviceInfo;
|
}
|
|
/**
|
* Remove a device info corresponding to the given {@code logicalAddress}.
|
* It returns removed {@link HdmiDeviceInfo} if exists.
|
*
|
* @param id id of device to be removed
|
* @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null}
|
*/
|
@ServiceThreadOnly
|
private HdmiDeviceInfo removeDeviceInfo(int id) {
|
assertRunOnServiceThread();
|
HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id);
|
if (deviceInfo != null) {
|
mDeviceInfos.remove(id);
|
}
|
updateSafeDeviceInfoList();
|
return deviceInfo;
|
}
|
|
/**
|
* Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}.
|
*
|
* @param logicalAddress logical address of the device to be retrieved
|
* @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
|
* Returns null if no logical address matched
|
*/
|
@ServiceThreadOnly
|
HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) {
|
assertRunOnServiceThread();
|
return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress));
|
}
|
|
@ServiceThreadOnly
|
private void updateSafeDeviceInfoList() {
|
assertRunOnServiceThread();
|
List<HdmiDeviceInfo> copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos);
|
synchronized (mLock) {
|
mSafeAllDeviceInfos = copiedDevices;
|
}
|
}
|
|
@GuardedBy("mLock")
|
List<HdmiDeviceInfo> getSafeCecDevicesLocked() {
|
ArrayList<HdmiDeviceInfo> infoList = new ArrayList<>();
|
for (HdmiDeviceInfo info : mSafeAllDeviceInfos) {
|
infoList.add(info);
|
}
|
return infoList;
|
}
|
|
private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) {
|
mService.invokeDeviceEventListeners(info, status);
|
}
|
|
@Override
|
@ServiceThreadOnly
|
void onHotplug(int portId, boolean connected) {
|
assertRunOnServiceThread();
|
if (connected) {
|
mService.wakeUp();
|
}
|
if (mService.getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) {
|
mCecMessageCache.flushAll();
|
} else if (!connected && mPortIdToTvInputs.get(portId) != null) {
|
String tvInputId = mPortIdToTvInputs.get(portId);
|
HdmiDeviceInfo info = mTvInputsToDeviceInfo.get(tvInputId);
|
if (info == null) {
|
return;
|
}
|
// Update with TIF on the device removal. TIF callback will update
|
// mPortIdToTvInputs and mPortIdToTvInputs.
|
removeCecDevice(info.getLogicalAddress());
|
}
|
}
|
|
@Override
|
@ServiceThreadOnly
|
protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
|
super.disableDevice(initiatedByCec, callback);
|
assertRunOnServiceThread();
|
mService.unregisterTvInputCallback(mTvInputCallback);
|
// TODO(b/129088603): check disableDevice and onStandby behaviors per spec
|
}
|
|
@Override
|
@ServiceThreadOnly
|
protected void onStandby(boolean initiatedByCec, int standbyAction) {
|
assertRunOnServiceThread();
|
mTvSystemAudioModeSupport = null;
|
// Record the last state of System Audio Control before going to standby
|
synchronized (mLock) {
|
mService.writeStringSystemProperty(
|
Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL,
|
isSystemAudioActivated() ? "true" : "false");
|
}
|
terminateSystemAudioMode();
|
}
|
|
@Override
|
@ServiceThreadOnly
|
protected void onAddressAllocated(int logicalAddress, int reason) {
|
assertRunOnServiceThread();
|
if (reason == mService.INITIATED_BY_ENABLE_CEC) {
|
mService.setAndBroadcastActiveSource(mService.getPhysicalAddress(),
|
getDeviceInfo().getDeviceType(), Constants.ADDR_BROADCAST);
|
}
|
mService.sendCecCommand(
|
HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
|
mAddress, mService.getPhysicalAddress(), mDeviceType));
|
mService.sendCecCommand(
|
HdmiCecMessageBuilder.buildDeviceVendorIdCommand(mAddress, mService.getVendorId()));
|
mService.registerTvInputCallback(mTvInputCallback);
|
int systemAudioControlOnPowerOnProp =
|
SystemProperties.getInt(
|
PROPERTY_SYSTEM_AUDIO_CONTROL_ON_POWER_ON,
|
ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON);
|
boolean lastSystemAudioControlStatus =
|
SystemProperties.getBoolean(Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL, true);
|
systemAudioControlOnPowerOn(systemAudioControlOnPowerOnProp, lastSystemAudioControlStatus);
|
clearDeviceInfoList();
|
launchDeviceDiscovery();
|
startQueuedActions();
|
}
|
|
@Override
|
protected int findKeyReceiverAddress() {
|
if (getActiveSource().isValid()) {
|
return getActiveSource().logicalAddress;
|
}
|
return Constants.ADDR_INVALID;
|
}
|
|
@VisibleForTesting
|
protected void systemAudioControlOnPowerOn(
|
int systemAudioOnPowerOnProp, boolean lastSystemAudioControlStatus) {
|
if ((systemAudioOnPowerOnProp == ALWAYS_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
|
|| ((systemAudioOnPowerOnProp == USE_LAST_STATE_SYSTEM_AUDIO_CONTROL_ON_POWER_ON)
|
&& lastSystemAudioControlStatus && isSystemAudioControlFeatureEnabled())) {
|
addAndStartAction(new SystemAudioInitiationActionFromAvr(this));
|
}
|
}
|
|
@Override
|
@ServiceThreadOnly
|
protected int getPreferredAddress() {
|
assertRunOnServiceThread();
|
return SystemProperties.getInt(
|
Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, Constants.ADDR_UNREGISTERED);
|
}
|
|
@Override
|
@ServiceThreadOnly
|
protected void setPreferredAddress(int addr) {
|
assertRunOnServiceThread();
|
mService.writeStringSystemProperty(
|
Constants.PROPERTY_PREFERRED_ADDRESS_AUDIO_SYSTEM, String.valueOf(addr));
|
}
|
|
@Override
|
@ServiceThreadOnly
|
protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
|
assertRunOnServiceThread();
|
int path = HdmiUtils.twoBytesToInt(message.getParams());
|
int address = message.getSource();
|
int type = message.getParams()[2];
|
|
// Ignore if [Device Discovery Action] is going on.
|
if (hasAction(DeviceDiscoveryAction.class)) {
|
Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message);
|
return true;
|
}
|
|
// Update the device info with TIF, note that the same device info could have added in
|
// device discovery and we do not want to override it with default OSD name. Therefore we
|
// need the following check to skip redundant device info updating.
|
HdmiDeviceInfo oldDevice = getCecDeviceInfo(address);
|
if (oldDevice == null || oldDevice.getPhysicalAddress() != path) {
|
addCecDevice(new HdmiDeviceInfo(
|
address, path, mService.pathToPortId(path), type,
|
Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(address)));
|
// if we are adding a new device info, send out a give osd name command
|
// to update the name of the device in TIF
|
mService.sendCecCommand(
|
HdmiCecMessageBuilder.buildGiveOsdNameCommand(mAddress, address));
|
return true;
|
}
|
|
Slog.w(TAG, "Device info exists. Not updating on Physical Address.");
|
return true;
|
}
|
|
@Override
|
protected boolean handleReportPowerStatus(HdmiCecMessage command) {
|
int newStatus = command.getParams()[0] & 0xFF;
|
updateDevicePowerStatus(command.getSource(), newStatus);
|
return true;
|
}
|
|
@Override
|
@ServiceThreadOnly
|
protected boolean handleSetOsdName(HdmiCecMessage message) {
|
int source = message.getSource();
|
String osdName;
|
HdmiDeviceInfo deviceInfo = getCecDeviceInfo(source);
|
// If the device is not in device list, ignore it.
|
if (deviceInfo == null) {
|
Slog.i(TAG, "No source device info for <Set Osd Name>." + message);
|
return true;
|
}
|
try {
|
osdName = new String(message.getParams(), "US-ASCII");
|
} catch (UnsupportedEncodingException e) {
|
Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e);
|
return true;
|
}
|
|
if (deviceInfo.getDisplayName().equals(osdName)) {
|
Slog.d(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message);
|
return true;
|
}
|
|
Slog.d(TAG, "Updating device OSD name from "
|
+ deviceInfo.getDisplayName()
|
+ " to " + osdName);
|
updateCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
|
deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(),
|
deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName));
|
return true;
|
}
|
|
@Override
|
@ServiceThreadOnly
|
protected boolean handleInitiateArc(HdmiCecMessage message) {
|
assertRunOnServiceThread();
|
// TODO(amyjojo): implement initiate arc handler
|
HdmiLogger.debug(TAG + "Stub handleInitiateArc");
|
return true;
|
}
|
|
@Override
|
@ServiceThreadOnly
|
protected boolean handleReportArcInitiate(HdmiCecMessage message) {
|
assertRunOnServiceThread();
|
// TODO(amyjojo): implement report arc initiate handler
|
HdmiLogger.debug(TAG + "Stub handleReportArcInitiate");
|
return true;
|
}
|
|
@Override
|
@ServiceThreadOnly
|
protected boolean handleReportArcTermination(HdmiCecMessage message) {
|
assertRunOnServiceThread();
|
// TODO(amyjojo): implement report arc terminate handler
|
HdmiLogger.debug(TAG + "Stub handleReportArcTermination");
|
return true;
|
}
|
|
@Override
|
@ServiceThreadOnly
|
protected boolean handleGiveAudioStatus(HdmiCecMessage message) {
|
assertRunOnServiceThread();
|
if (isSystemAudioControlFeatureEnabled()) {
|
reportAudioStatus(message.getSource());
|
} else {
|
mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
|
}
|
return true;
|
}
|
|
@Override
|
@ServiceThreadOnly
|
protected boolean handleGiveSystemAudioModeStatus(HdmiCecMessage message) {
|
assertRunOnServiceThread();
|
// If the audio system is initiating the system audio mode on and TV asks the sam status at
|
// the same time, respond with true. Since we know TV supports sam in this situation.
|
// If the query comes from STB, we should respond with the current sam status and the STB
|
// should listen to the <Set System Audio Mode> broadcasting.
|
boolean isSystemAudioModeOnOrTurningOn = isSystemAudioActivated();
|
if (!isSystemAudioModeOnOrTurningOn
|
&& message.getSource() == Constants.ADDR_TV
|
&& hasAction(SystemAudioInitiationActionFromAvr.class)) {
|
isSystemAudioModeOnOrTurningOn = true;
|
}
|
mService.sendCecCommand(
|
HdmiCecMessageBuilder.buildReportSystemAudioMode(
|
mAddress, message.getSource(), isSystemAudioModeOnOrTurningOn));
|
return true;
|
}
|
|
@Override
|
@ServiceThreadOnly
|
protected boolean handleRequestArcInitiate(HdmiCecMessage message) {
|
assertRunOnServiceThread();
|
removeAction(ArcInitiationActionFromAvr.class);
|
if (!mService.readBooleanSystemProperty(Constants.PROPERTY_ARC_SUPPORT, true)) {
|
mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
|
} else if (!isDirectConnectToTv()) {
|
HdmiLogger.debug("AVR device is not directly connected with TV");
|
mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE);
|
} else {
|
addAndStartAction(new ArcInitiationActionFromAvr(this));
|
}
|
return true;
|
}
|
|
@Override
|
@ServiceThreadOnly
|
protected boolean handleRequestArcTermination(HdmiCecMessage message) {
|
assertRunOnServiceThread();
|
if (!SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) {
|
mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNRECOGNIZED_OPCODE);
|
} else if (!isArcEnabled()) {
|
HdmiLogger.debug("ARC is not established between TV and AVR device");
|
mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE);
|
} else {
|
removeAction(ArcTerminationActionFromAvr.class);
|
addAndStartAction(new ArcTerminationActionFromAvr(this));
|
}
|
return true;
|
}
|
|
@ServiceThreadOnly
|
protected boolean handleRequestShortAudioDescriptor(HdmiCecMessage message) {
|
assertRunOnServiceThread();
|
HdmiLogger.debug(TAG + "Stub handleRequestShortAudioDescriptor");
|
if (!isSystemAudioControlFeatureEnabled()) {
|
mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
|
return true;
|
}
|
if (!isSystemAudioActivated()) {
|
mService.maySendFeatureAbortCommand(message, Constants.ABORT_NOT_IN_CORRECT_MODE);
|
return true;
|
}
|
|
List<DeviceConfig> config = null;
|
File file = new File(SHORT_AUDIO_DESCRIPTOR_CONFIG_PATH);
|
if (file.exists()) {
|
try {
|
InputStream in = new FileInputStream(file);
|
config = HdmiUtils.ShortAudioDescriptorXmlParser.parse(in);
|
in.close();
|
} catch (IOException e) {
|
Slog.e(TAG, "Error reading file: " + file, e);
|
} catch (XmlPullParserException e) {
|
Slog.e(TAG, "Unable to parse file: " + file, e);
|
}
|
}
|
|
@AudioCodec int[] audioFormatCodes = parseAudioFormatCodes(message.getParams());
|
byte[] sadBytes;
|
if (config != null && config.size() > 0) {
|
sadBytes = getSupportedShortAudioDescriptorsFromConfig(config, audioFormatCodes);
|
} else {
|
AudioDeviceInfo deviceInfo = getSystemAudioDeviceInfo();
|
if (deviceInfo == null) {
|
mService.maySendFeatureAbortCommand(message, Constants.ABORT_UNABLE_TO_DETERMINE);
|
return true;
|
}
|
|
sadBytes = getSupportedShortAudioDescriptors(deviceInfo, audioFormatCodes);
|
}
|
|
if (sadBytes.length == 0) {
|
mService.maySendFeatureAbortCommand(message, Constants.ABORT_INVALID_OPERAND);
|
} else {
|
mService.sendCecCommand(
|
HdmiCecMessageBuilder.buildReportShortAudioDescriptor(
|
mAddress, message.getSource(), sadBytes));
|
}
|
return true;
|
}
|
|
private byte[] getSupportedShortAudioDescriptors(
|
AudioDeviceInfo deviceInfo, @AudioCodec int[] audioFormatCodes) {
|
ArrayList<byte[]> sads = new ArrayList<>(audioFormatCodes.length);
|
for (@AudioCodec int audioFormatCode : audioFormatCodes) {
|
byte[] sad = getSupportedShortAudioDescriptor(deviceInfo, audioFormatCode);
|
if (sad != null) {
|
if (sad.length == 3) {
|
|
sads.add(sad);
|
} else {
|
HdmiLogger.warning(
|
"Dropping Short Audio Descriptor with length %d for requested codec %x",
|
sad.length, audioFormatCode);
|
}
|
}
|
}
|
return getShortAudioDescriptorBytes(sads);
|
}
|
|
private byte[] getSupportedShortAudioDescriptorsFromConfig(
|
List<DeviceConfig> deviceConfig, @AudioCodec int[] audioFormatCodes) {
|
DeviceConfig deviceConfigToUse = null;
|
for (DeviceConfig device : deviceConfig) {
|
// TODO(amyjojo) use PROPERTY_SYSTEM_AUDIO_MODE_AUDIO_PORT to get the audio device name
|
if (device.name.equals("VX_AUDIO_DEVICE_IN_HDMI_ARC")) {
|
deviceConfigToUse = device;
|
break;
|
}
|
}
|
if (deviceConfigToUse == null) {
|
// TODO(amyjojo) use PROPERTY_SYSTEM_AUDIO_MODE_AUDIO_PORT to get the audio device name
|
Slog.w(TAG, "sadConfig.xml does not have required device info for "
|
+ "VX_AUDIO_DEVICE_IN_HDMI_ARC");
|
return new byte[0];
|
}
|
HashMap<Integer, byte[]> map = new HashMap<>();
|
ArrayList<byte[]> sads = new ArrayList<>(audioFormatCodes.length);
|
for (CodecSad codecSad : deviceConfigToUse.supportedCodecs) {
|
map.put(codecSad.audioCodec, codecSad.sad);
|
}
|
for (int i = 0; i < audioFormatCodes.length; i++) {
|
if (map.containsKey(audioFormatCodes[i])) {
|
byte[] sad = map.get(audioFormatCodes[i]);
|
if (sad != null && sad.length == 3) {
|
sads.add(sad);
|
}
|
}
|
}
|
return getShortAudioDescriptorBytes(sads);
|
}
|
|
private byte[] getShortAudioDescriptorBytes(ArrayList<byte[]> sads) {
|
// Short Audio Descriptors are always 3 bytes long.
|
byte[] bytes = new byte[sads.size() * 3];
|
int index = 0;
|
for (byte[] sad : sads) {
|
System.arraycopy(sad, 0, bytes, index, 3);
|
index += 3;
|
}
|
return bytes;
|
}
|
|
/**
|
* Returns a 3 byte short audio descriptor as described in CEC 1.4 table 29 or null if the
|
* audioFormatCode is not supported.
|
*/
|
@Nullable
|
private byte[] getSupportedShortAudioDescriptor(
|
AudioDeviceInfo deviceInfo, @AudioCodec int audioFormatCode) {
|
switch (audioFormatCode) {
|
case Constants.AUDIO_CODEC_NONE: {
|
return null;
|
}
|
case Constants.AUDIO_CODEC_LPCM: {
|
return getLpcmShortAudioDescriptor(deviceInfo);
|
}
|
// TODO(b/80297701): implement the rest of the codecs
|
case Constants.AUDIO_CODEC_DD:
|
case Constants.AUDIO_CODEC_MPEG1:
|
case Constants.AUDIO_CODEC_MP3:
|
case Constants.AUDIO_CODEC_MPEG2:
|
case Constants.AUDIO_CODEC_AAC:
|
case Constants.AUDIO_CODEC_DTS:
|
case Constants.AUDIO_CODEC_ATRAC:
|
case Constants.AUDIO_CODEC_ONEBITAUDIO:
|
case Constants.AUDIO_CODEC_DDP:
|
case Constants.AUDIO_CODEC_DTSHD:
|
case Constants.AUDIO_CODEC_TRUEHD:
|
case Constants.AUDIO_CODEC_DST:
|
case Constants.AUDIO_CODEC_WMAPRO:
|
default: {
|
return null;
|
}
|
}
|
}
|
|
@Nullable
|
private byte[] getLpcmShortAudioDescriptor(AudioDeviceInfo deviceInfo) {
|
// TODO(b/80297701): implement
|
return null;
|
}
|
|
@Nullable
|
private AudioDeviceInfo getSystemAudioDeviceInfo() {
|
AudioManager audioManager = mService.getContext().getSystemService(AudioManager.class);
|
if (audioManager == null) {
|
HdmiLogger.error(
|
"Error getting system audio device because AudioManager not available.");
|
return null;
|
}
|
AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
|
HdmiLogger.debug("Found %d audio input devices", devices.length);
|
for (AudioDeviceInfo device : devices) {
|
HdmiLogger.debug("%s at port %s", device.getProductName(), device.getPort());
|
HdmiLogger.debug("Supported encodings are %s",
|
Arrays.stream(device.getEncodings()).mapToObj(
|
AudioFormat::toLogFriendlyEncoding
|
).collect(Collectors.joining(", ")));
|
// TODO(b/80297701) use the actual device type that system audio mode is connected to.
|
if (device.getType() == AudioDeviceInfo.TYPE_HDMI_ARC) {
|
return device;
|
}
|
}
|
return null;
|
}
|
|
@AudioCodec
|
private int[] parseAudioFormatCodes(byte[] params) {
|
@AudioCodec int[] audioFormatCodes = new int[params.length];
|
for (int i = 0; i < params.length; i++) {
|
byte val = params[i];
|
audioFormatCodes[i] =
|
val >= 1 && val <= Constants.AUDIO_CODEC_MAX ? val : Constants.AUDIO_CODEC_NONE;
|
}
|
return audioFormatCodes;
|
}
|
|
@Override
|
@ServiceThreadOnly
|
protected boolean handleSystemAudioModeRequest(HdmiCecMessage message) {
|
assertRunOnServiceThread();
|
boolean systemAudioStatusOn = message.getParams().length != 0;
|
// Check if the request comes from a non-TV device.
|
// Need to check if TV supports System Audio Control
|
// if non-TV device tries to turn on the feature
|
if (message.getSource() != Constants.ADDR_TV) {
|
if (systemAudioStatusOn) {
|
handleSystemAudioModeOnFromNonTvDevice(message);
|
return true;
|
}
|
} else {
|
// If TV request the feature on
|
// cache TV supporting System Audio Control
|
// until Audio System loses its physical address.
|
setTvSystemAudioModeSupport(true);
|
}
|
// If TV or Audio System does not support the feature,
|
// will send abort command.
|
if (!checkSupportAndSetSystemAudioMode(systemAudioStatusOn)) {
|
mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
|
return true;
|
}
|
|
mService.sendCecCommand(
|
HdmiCecMessageBuilder.buildSetSystemAudioMode(
|
mAddress, Constants.ADDR_BROADCAST, systemAudioStatusOn));
|
return true;
|
}
|
|
@Override
|
@ServiceThreadOnly
|
protected boolean handleSetSystemAudioMode(HdmiCecMessage message) {
|
assertRunOnServiceThread();
|
if (!checkSupportAndSetSystemAudioMode(
|
HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
|
mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
|
}
|
return true;
|
}
|
|
@Override
|
@ServiceThreadOnly
|
protected boolean handleSystemAudioModeStatus(HdmiCecMessage message) {
|
assertRunOnServiceThread();
|
if (!checkSupportAndSetSystemAudioMode(
|
HdmiUtils.parseCommandParamSystemAudioStatus(message))) {
|
mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
|
}
|
return true;
|
}
|
|
@ServiceThreadOnly
|
void setArcStatus(boolean enabled) {
|
// TODO(shubang): add tests
|
assertRunOnServiceThread();
|
|
HdmiLogger.debug("Set Arc Status[old:%b new:%b]", mArcEstablished, enabled);
|
// 1. Enable/disable ARC circuit.
|
enableAudioReturnChannel(enabled);
|
// 2. Notify arc status to audio service.
|
notifyArcStatusToAudioService(enabled);
|
// 3. Update arc status;
|
mArcEstablished = enabled;
|
}
|
|
/** Switch hardware ARC circuit in the system. */
|
@ServiceThreadOnly
|
private void enableAudioReturnChannel(boolean enabled) {
|
assertRunOnServiceThread();
|
mService.enableAudioReturnChannel(
|
SystemProperties.getInt(Constants.PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT, 0),
|
enabled);
|
}
|
|
private void notifyArcStatusToAudioService(boolean enabled) {
|
// Note that we don't set any name to ARC.
|
mService.getAudioManager()
|
.setWiredDeviceConnectionState(AudioSystem.DEVICE_IN_HDMI, enabled ? 1 : 0, "", "");
|
}
|
|
void reportAudioStatus(int source) {
|
assertRunOnServiceThread();
|
|
int volume = mService.getAudioManager().getStreamVolume(AudioManager.STREAM_MUSIC);
|
boolean mute = mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC);
|
int maxVolume = mService.getAudioManager().getStreamMaxVolume(AudioManager.STREAM_MUSIC);
|
int minVolume = mService.getAudioManager().getStreamMinVolume(AudioManager.STREAM_MUSIC);
|
int scaledVolume = VolumeControlAction.scaleToCecVolume(volume, maxVolume);
|
HdmiLogger.debug("Reporting volume %d (%d-%d) as CEC volume %d", volume,
|
minVolume, maxVolume, scaledVolume);
|
|
mService.sendCecCommand(
|
HdmiCecMessageBuilder.buildReportAudioStatus(
|
mAddress, source, scaledVolume, mute));
|
}
|
|
/**
|
* Method to check if device support System Audio Control. If so, wake up device if necessary.
|
*
|
* <p> then call {@link #setSystemAudioMode(boolean)} to turn on or off System Audio Mode
|
* @param newSystemAudioMode turning feature on or off. True is on. False is off.
|
* @return true or false.
|
*
|
* <p>False when device does not support the feature. Otherwise returns true.
|
*/
|
protected boolean checkSupportAndSetSystemAudioMode(boolean newSystemAudioMode) {
|
if (!isSystemAudioControlFeatureEnabled()) {
|
HdmiLogger.debug(
|
"Cannot turn "
|
+ (newSystemAudioMode ? "on" : "off")
|
+ "system audio mode "
|
+ "because the System Audio Control feature is disabled.");
|
return false;
|
}
|
HdmiLogger.debug(
|
"System Audio Mode change[old:%b new:%b]",
|
isSystemAudioActivated(), newSystemAudioMode);
|
// Wake up device if System Audio Control is turned on
|
if (newSystemAudioMode) {
|
mService.wakeUp();
|
}
|
setSystemAudioMode(newSystemAudioMode);
|
return true;
|
}
|
|
/**
|
* Real work to turn on or off System Audio Mode.
|
*
|
* Use {@link #checkSupportAndSetSystemAudioMode(boolean)}
|
* if trying to turn on or off the feature.
|
*/
|
private void setSystemAudioMode(boolean newSystemAudioMode) {
|
int targetPhysicalAddress = getActiveSource().physicalAddress;
|
int port = mService.pathToPortId(targetPhysicalAddress);
|
if (newSystemAudioMode && port >= 0) {
|
switchToAudioInput();
|
}
|
// Mute device when feature is turned off and unmute device when feature is turned on.
|
// PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE is false when device never needs to be muted.
|
boolean currentMuteStatus =
|
mService.getAudioManager().isStreamMute(AudioManager.STREAM_MUSIC);
|
if (currentMuteStatus == newSystemAudioMode) {
|
if (mService.readBooleanSystemProperty(
|
Constants.PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE, true)
|
|| newSystemAudioMode) {
|
mService.getAudioManager()
|
.adjustStreamVolume(
|
AudioManager.STREAM_MUSIC,
|
newSystemAudioMode
|
? AudioManager.ADJUST_UNMUTE
|
: AudioManager.ADJUST_MUTE,
|
0);
|
}
|
}
|
updateAudioManagerForSystemAudio(newSystemAudioMode);
|
synchronized (mLock) {
|
if (isSystemAudioActivated() != newSystemAudioMode) {
|
mService.setSystemAudioActivated(newSystemAudioMode);
|
mService.announceSystemAudioModeChange(newSystemAudioMode);
|
}
|
}
|
// Init arc whenever System Audio Mode is on
|
// Terminate arc when System Audio Mode is off
|
// Since some TVs don't request ARC on with System Audio Mode on request
|
if (SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)
|
&& isDirectConnectToTv()) {
|
if (newSystemAudioMode && !isArcEnabled()) {
|
removeAction(ArcInitiationActionFromAvr.class);
|
addAndStartAction(new ArcInitiationActionFromAvr(this));
|
} else if (!newSystemAudioMode && isArcEnabled()) {
|
removeAction(ArcTerminationActionFromAvr.class);
|
addAndStartAction(new ArcTerminationActionFromAvr(this));
|
}
|
}
|
}
|
|
protected void switchToAudioInput() {
|
// TODO(b/111396634): switch input according to PROPERTY_SYSTEM_AUDIO_MODE_AUDIO_PORT
|
}
|
|
protected boolean isDirectConnectToTv() {
|
int myPhysicalAddress = mService.getPhysicalAddress();
|
return (myPhysicalAddress & Constants.ROUTING_PATH_TOP_MASK) == myPhysicalAddress;
|
}
|
|
private void updateAudioManagerForSystemAudio(boolean on) {
|
int device = mService.getAudioManager().setHdmiSystemAudioSupported(on);
|
HdmiLogger.debug("[A]UpdateSystemAudio mode[on=%b] output=[%X]", on, device);
|
}
|
|
void onSystemAduioControlFeatureSupportChanged(boolean enabled) {
|
setSystemAudioControlFeatureEnabled(enabled);
|
if (enabled) {
|
addAndStartAction(new SystemAudioInitiationActionFromAvr(this));
|
}
|
}
|
|
@ServiceThreadOnly
|
void setSystemAudioControlFeatureEnabled(boolean enabled) {
|
assertRunOnServiceThread();
|
synchronized (mLock) {
|
mSystemAudioControlFeatureEnabled = enabled;
|
}
|
}
|
|
@ServiceThreadOnly
|
void setRoutingControlFeatureEnables(boolean enabled) {
|
assertRunOnServiceThread();
|
synchronized (mLock) {
|
mRoutingControlFeatureEnabled = enabled;
|
}
|
}
|
|
@ServiceThreadOnly
|
void doManualPortSwitching(int portId, IHdmiControlCallback callback) {
|
assertRunOnServiceThread();
|
if (!mService.isValidPortId(portId)) {
|
invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
|
return;
|
}
|
if (portId == getLocalActivePort()) {
|
invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
|
return;
|
}
|
if (!mService.isControlEnabled()) {
|
setRoutingPort(portId);
|
setLocalActivePort(portId);
|
invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
|
return;
|
}
|
int oldPath = getRoutingPort() != Constants.CEC_SWITCH_HOME
|
? mService.portIdToPath(getRoutingPort())
|
: getDeviceInfo().getPhysicalAddress();
|
int newPath = mService.portIdToPath(portId);
|
if (oldPath == newPath) {
|
return;
|
}
|
setRoutingPort(portId);
|
setLocalActivePort(portId);
|
HdmiCecMessage routingChange =
|
HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath);
|
mService.sendCecCommand(routingChange);
|
}
|
|
boolean isSystemAudioControlFeatureEnabled() {
|
synchronized (mLock) {
|
return mSystemAudioControlFeatureEnabled;
|
}
|
}
|
|
protected boolean isSystemAudioActivated() {
|
return mService.isSystemAudioActivated();
|
}
|
|
protected void terminateSystemAudioMode() {
|
// remove pending initiation actions
|
removeAction(SystemAudioInitiationActionFromAvr.class);
|
if (!isSystemAudioActivated()) {
|
return;
|
}
|
|
if (checkSupportAndSetSystemAudioMode(false)) {
|
// send <Set System Audio Mode> [“Off”]
|
mService.sendCecCommand(
|
HdmiCecMessageBuilder.buildSetSystemAudioMode(
|
mAddress, Constants.ADDR_BROADCAST, false));
|
}
|
}
|
|
/** Reports if System Audio Mode is supported by the connected TV */
|
interface TvSystemAudioModeSupportedCallback {
|
|
/** {@code supported} is true if the TV is connected and supports System Audio Mode. */
|
void onResult(boolean supported);
|
}
|
|
/**
|
* Queries the connected TV to detect if System Audio Mode is supported by the TV.
|
*
|
* <p>This query may take up to 2 seconds to complete.
|
*
|
* <p>The result of the query may be cached until Audio device type is put in standby or loses
|
* its physical address.
|
*/
|
void queryTvSystemAudioModeSupport(TvSystemAudioModeSupportedCallback callback) {
|
if (mTvSystemAudioModeSupport == null) {
|
addAndStartAction(new DetectTvSystemAudioModeSupportAction(this, callback));
|
} else {
|
callback.onResult(mTvSystemAudioModeSupport);
|
}
|
}
|
|
/**
|
* Handler of System Audio Mode Request on from non TV device
|
*/
|
void handleSystemAudioModeOnFromNonTvDevice(HdmiCecMessage message) {
|
if (!isSystemAudioControlFeatureEnabled()) {
|
HdmiLogger.debug(
|
"Cannot turn on" + "system audio mode "
|
+ "because the System Audio Control feature is disabled.");
|
mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
|
return;
|
}
|
// Wake up device
|
mService.wakeUp();
|
// If Audio device is the active source or is on the active path,
|
// enable system audio mode without querying TV's support on sam.
|
// This is per HDMI spec 1.4b CEC 13.15.4.2.
|
if (mService.pathToPortId(getActiveSource().physicalAddress)
|
!= Constants.INVALID_PORT_ID) {
|
setSystemAudioMode(true);
|
mService.sendCecCommand(
|
HdmiCecMessageBuilder.buildSetSystemAudioMode(
|
mAddress, Constants.ADDR_BROADCAST, true));
|
return;
|
}
|
// Check if TV supports System Audio Control.
|
// Handle broadcasting setSystemAudioMode on or aborting message on callback.
|
queryTvSystemAudioModeSupport(new TvSystemAudioModeSupportedCallback() {
|
public void onResult(boolean supported) {
|
if (supported) {
|
setSystemAudioMode(true);
|
mService.sendCecCommand(
|
HdmiCecMessageBuilder.buildSetSystemAudioMode(
|
mAddress, Constants.ADDR_BROADCAST, true));
|
} else {
|
mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
|
}
|
}
|
});
|
}
|
|
void setTvSystemAudioModeSupport(boolean supported) {
|
mTvSystemAudioModeSupport = supported;
|
}
|
|
@VisibleForTesting
|
protected boolean isArcEnabled() {
|
synchronized (mLock) {
|
return mArcEstablished;
|
}
|
}
|
|
@Override
|
protected void switchInputOnReceivingNewActivePath(int physicalAddress) {
|
int port = mService.pathToPortId(physicalAddress);
|
if (isSystemAudioActivated() && port < 0) {
|
// If system audio mode is on and the new active source is not under the current device,
|
// Will switch to ARC input.
|
// TODO(b/115637145): handle system aduio without ARC
|
routeToInputFromPortId(Constants.CEC_SWITCH_ARC);
|
} else if (mIsSwitchDevice && port >= 0) {
|
// If current device is a switch and the new active source is under it,
|
// will switch to the corresponding active path.
|
routeToInputFromPortId(port);
|
}
|
}
|
|
protected void routeToInputFromPortId(int portId) {
|
if (!isRoutingControlFeatureEnabled()) {
|
HdmiLogger.debug("Routing Control Feature is not enabled.");
|
return;
|
}
|
if (mArcIntentUsed) {
|
routeToTvInputFromPortId(portId);
|
} else {
|
// TODO(): implement input switching for devices not using TvInput.
|
}
|
}
|
|
protected void routeToTvInputFromPortId(int portId) {
|
if (portId < 0 || portId >= Constants.CEC_SWITCH_PORT_MAX) {
|
HdmiLogger.debug("Invalid port number for Tv Input switching.");
|
return;
|
}
|
// Wake up if the current device if ready to route.
|
mService.wakeUp();
|
if (portId == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) {
|
switchToHomeTvInput();
|
} else if (portId == Constants.CEC_SWITCH_ARC) {
|
switchToTvInput(SystemProperties.get(Constants.PROPERTY_SYSTEM_AUDIO_DEVICE_ARC_PORT));
|
setLocalActivePort(portId);
|
return;
|
} else {
|
String uri = mPortIdToTvInputs.get(portId);
|
if (uri != null) {
|
switchToTvInput(uri);
|
} else {
|
HdmiLogger.debug("Port number does not match any Tv Input.");
|
return;
|
}
|
}
|
|
setLocalActivePort(portId);
|
setRoutingPort(portId);
|
}
|
|
// For device to switch to specific TvInput with corresponding URI.
|
private void switchToTvInput(String uri) {
|
try {
|
mService.getContext().startActivity(new Intent(Intent.ACTION_VIEW,
|
TvContract.buildChannelUriForPassthroughInput(uri))
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
} catch (ActivityNotFoundException e) {
|
Slog.e(TAG, "Can't find activity to switch to " + uri, e);
|
}
|
}
|
|
// For device using TvInput to switch to Home.
|
private void switchToHomeTvInput() {
|
try {
|
Intent activityIntent = new Intent(Intent.ACTION_MAIN)
|
.addCategory(Intent.CATEGORY_HOME)
|
.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
|
| Intent.FLAG_ACTIVITY_SINGLE_TOP
|
| Intent.FLAG_ACTIVITY_NEW_TASK
|
| Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
mService.getContext().startActivity(activityIntent);
|
} catch (ActivityNotFoundException e) {
|
Slog.e(TAG, "Can't find activity to switch to HOME", e);
|
}
|
}
|
|
@Override
|
protected void handleRoutingChangeAndInformation(int physicalAddress, HdmiCecMessage message) {
|
int port = mService.pathToPortId(physicalAddress);
|
// Routing change or information sent from switches under the current device can be ignored.
|
if (port > 0) {
|
return;
|
}
|
// When other switches route to some other devices not under the current device,
|
// check system audio mode status and do ARC switch if needed.
|
if (port < 0 && isSystemAudioActivated()) {
|
handleRoutingChangeAndInformationForSystemAudio();
|
return;
|
}
|
// When other switches route to the current device
|
// and the current device is also a switch.
|
if (port == 0) {
|
handleRoutingChangeAndInformationForSwitch(message);
|
}
|
}
|
|
// Handle the system audio(ARC) part of the logic on receiving routing change or information.
|
private void handleRoutingChangeAndInformationForSystemAudio() {
|
// TODO(b/115637145): handle system aduio without ARC
|
routeToInputFromPortId(Constants.CEC_SWITCH_ARC);
|
}
|
|
// Handle the routing control part of the logic on receiving routing change or information.
|
private void handleRoutingChangeAndInformationForSwitch(HdmiCecMessage message) {
|
if (getRoutingPort() == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) {
|
routeToInputFromPortId(Constants.CEC_SWITCH_HOME);
|
mService.setAndBroadcastActiveSourceFromOneDeviceType(
|
message.getSource(), mService.getPhysicalAddress());
|
return;
|
}
|
|
int routingInformationPath = mService.portIdToPath(getRoutingPort());
|
// If current device is already the leaf of the whole HDMI system, will do nothing.
|
if (routingInformationPath == mService.getPhysicalAddress()) {
|
HdmiLogger.debug("Current device can't assign valid physical address"
|
+ "to devices under it any more. "
|
+ "It's physical address is "
|
+ routingInformationPath);
|
return;
|
}
|
// Otherwise will switch to the current active port and broadcast routing information.
|
mService.sendCecCommand(HdmiCecMessageBuilder.buildRoutingInformation(
|
mAddress, routingInformationPath));
|
routeToInputFromPortId(getRoutingPort());
|
}
|
|
protected void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) {
|
HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
|
if (info == null) {
|
Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress);
|
return;
|
}
|
|
if (info.getDevicePowerStatus() == newPowerStatus) {
|
return;
|
}
|
|
HdmiDeviceInfo newInfo = HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus);
|
// addDeviceInfo replaces old device info with new one if exists.
|
addDeviceInfo(newInfo);
|
|
invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
|
}
|
|
@ServiceThreadOnly
|
private void launchDeviceDiscovery() {
|
assertRunOnServiceThread();
|
if (hasAction(DeviceDiscoveryAction.class)) {
|
Slog.i(TAG, "Device Discovery Action is in progress. Restarting.");
|
removeAction(DeviceDiscoveryAction.class);
|
}
|
DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
|
new DeviceDiscoveryCallback() {
|
@Override
|
public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
|
for (HdmiDeviceInfo info : deviceInfos) {
|
addCecDevice(info);
|
}
|
}
|
});
|
addAndStartAction(action);
|
}
|
|
// Clear all device info.
|
@ServiceThreadOnly
|
private void clearDeviceInfoList() {
|
assertRunOnServiceThread();
|
for (HdmiDeviceInfo info : HdmiUtils.sparseArrayToList(mDeviceInfos)) {
|
if (info.getPhysicalAddress() == mService.getPhysicalAddress()) {
|
continue;
|
}
|
invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
|
}
|
mDeviceInfos.clear();
|
updateSafeDeviceInfoList();
|
}
|
|
@Override
|
protected void dump(IndentingPrintWriter pw) {
|
pw.println("HdmiCecLocalDeviceAudioSystem:");
|
pw.increaseIndent();
|
pw.println("isRoutingFeatureEnabled " + isRoutingControlFeatureEnabled());
|
pw.println("mSystemAudioControlFeatureEnabled: " + mSystemAudioControlFeatureEnabled);
|
pw.println("mTvSystemAudioModeSupport: " + mTvSystemAudioModeSupport);
|
pw.println("mArcEstablished: " + mArcEstablished);
|
pw.println("mArcIntentUsed: " + mArcIntentUsed);
|
pw.println("mRoutingPort: " + getRoutingPort());
|
pw.println("mLocalActivePort: " + getLocalActivePort());
|
HdmiUtils.dumpMap(pw, "mPortIdToTvInputs:", mPortIdToTvInputs);
|
HdmiUtils.dumpMap(pw, "mTvInputsToDeviceInfo:", mTvInputsToDeviceInfo);
|
HdmiUtils.dumpSparseArray(pw, "mDeviceInfos:", mDeviceInfos);
|
pw.decreaseIndent();
|
super.dump(pw);
|
}
|
}
|