| /* |
| * 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); |
| } |
| } |