All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.server.hdmi.HdmiCecNetwork Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * Copyright (C) 2020 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.HdmiAnnotations.ServiceThreadOnly;

import android.annotation.Nullable;
import android.hardware.hdmi.DeviceFeatures;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
import android.hardware.hdmi.HdmiPortInfo;
import android.os.Handler;
import android.os.Looper;
import android.util.ArraySet;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseIntArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;

import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;

/**
 * Holds information about the current state of the HDMI CEC network. It is the sole source of
 * truth for device information in the CEC network.
 *
 * This information includes:
 * - All local devices
 * - All HDMI ports, their capabilities and status
 * - All devices connected to the CEC bus
 *
 * This class receives all incoming CEC messages and passively listens to device updates to fill
 * out the above information.
 * This class should not take any active action in sending CEC messages.
 *
 * Note that the information cached in this class is not guaranteed to be up-to-date, especially OSD
 * names, power states can be outdated. For local devices, more up-to-date information can be
 * accessed through {@link HdmiCecLocalDevice#getDeviceInfo()}.
 */
@VisibleForTesting
public class HdmiCecNetwork {
    private static final String TAG = "HdmiCecNetwork";

    protected final Object mLock;
    private final HdmiControlService mHdmiControlService;
    private final HdmiCecController mHdmiCecController;
    private final HdmiMhlControllerStub mHdmiMhlController;
    private final Handler mHandler;
    // Stores the local CEC devices in the system. Device type is used for key.
    private final SparseArray mLocalDevices = new SparseArray<>();

    // Map-like container of all cec devices including local ones.
    // device id is used as key of container.
    // This is not thread-safe. For external purpose use mSafeDeviceInfos.
    private final SparseArray mDeviceInfos = new SparseArray<>();
    // Set of physical addresses of CEC switches on the CEC bus. Managed independently from
    // other CEC devices since they might not have logical address.
    private final ArraySet mCecSwitches = new ArraySet<>();
    // Copy of mDeviceInfos to guarantee thread-safety.
    @GuardedBy("mLock")
    private List mSafeAllDeviceInfos = Collections.emptyList();
    // All external cec input(source) devices. Does not include system audio device.
    @GuardedBy("mLock")
    private List mSafeExternalInputs = Collections.emptyList();
    // HDMI port information. Stored in the unmodifiable list to keep the static information
    // from being modified.
    @GuardedBy("mLock")
    private List mPortInfo = Collections.emptyList();

    // Map from path(physical address) to port ID.
    private UnmodifiableSparseIntArray mPortIdMap;

    // Map from port ID to HdmiPortInfo.
    private UnmodifiableSparseArray mPortInfoMap;

    // Map from port ID to HdmiDeviceInfo.
    private UnmodifiableSparseArray mPortDeviceMap;

    HdmiCecNetwork(HdmiControlService hdmiControlService,
            HdmiCecController hdmiCecController,
            HdmiMhlControllerStub hdmiMhlController) {
        mHdmiControlService = hdmiControlService;
        mHdmiCecController = hdmiCecController;
        mHdmiMhlController = hdmiMhlController;
        mHandler = new Handler(mHdmiControlService.getServiceLooper());
        mLock = mHdmiControlService.getServiceLock();
    }

    private static boolean isConnectedToCecSwitch(int path, Collection switches) {
        for (int switchPath : switches) {
            if (isParentPath(switchPath, path)) {
                return true;
            }
        }
        return false;
    }

    private static boolean isParentPath(int parentPath, int childPath) {
        // (A000, AB00) (AB00, ABC0), (ABC0, ABCD)
        // If child's last non-zero nibble is removed, the result equals to the parent.
        for (int i = 0; i <= 12; i += 4) {
            int nibble = (childPath >> i) & 0xF;
            if (nibble != 0) {
                int parentNibble = (parentPath >> i) & 0xF;
                return parentNibble == 0 && (childPath >> i + 4) == (parentPath >> i + 4);
            }
        }
        return false;
    }

    public void addLocalDevice(int deviceType, HdmiCecLocalDevice device) {
        mLocalDevices.put(deviceType, device);
    }

    /**
     * Return the locally hosted logical device of a given type.
     *
     * @param deviceType logical device type
     * @return {@link HdmiCecLocalDevice} instance if the instance of the type is available;
     * otherwise null.
     */
    HdmiCecLocalDevice getLocalDevice(int deviceType) {
        return mLocalDevices.get(deviceType);
    }

    /**
     * Return a list of all {@link HdmiCecLocalDevice}s.
     *
     * 

Declared as package-private. accessed by {@link HdmiControlService} only. */ @ServiceThreadOnly List getLocalDeviceList() { assertRunOnServiceThread(); return HdmiUtils.sparseArrayToList(mLocalDevices); } @ServiceThreadOnly boolean isAllocatedLocalDeviceAddress(int address) { assertRunOnServiceThread(); for (int i = 0; i < mLocalDevices.size(); ++i) { if (mLocalDevices.valueAt(i).isAddressOf(address)) { return true; } } return false; } @ServiceThreadOnly void clearLocalDevices() { assertRunOnServiceThread(); mLocalDevices.clear(); } /** * Get the device info of a local device or a device in the CEC network by a device id. * @param id id of the device to get * @return the device with the given id, or {@code null} */ @Nullable public HdmiDeviceInfo getDeviceInfo(int id) { return mDeviceInfos.get(id); } /** * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same * logical address as new device info's. * *

Declared as package-private. accessed by {@link HdmiControlService} only. * * @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 private HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) { assertRunOnServiceThread(); HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress()); mHdmiControlService.checkLogicalAddressConflictAndReallocate( deviceInfo.getLogicalAddress(), deviceInfo.getPhysicalAddress()); 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. * *

Declared as package-private. accessed by {@link HdmiControlService} only. * * @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}. * * This is not thread-safe. For thread safety, call {@link #getSafeCecDeviceInfo(int)}. * * @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 @Nullable HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) { assertRunOnServiceThread(); return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress)); } /** * Called when a device is newly added or a new device is detected or * existing device is updated. * * @param info device info of a new device. */ @ServiceThreadOnly final void addCecDevice(HdmiDeviceInfo info) { assertRunOnServiceThread(); HdmiDeviceInfo old = addDeviceInfo(info); if (isLocalDeviceAddress(info.getLogicalAddress())) { // The addition of a local device should not notify listeners return; } mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState(); if (info.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) { // Don't notify listeners of devices that haven't reported their physical address yet return; } else if (old == null || old.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) { 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); } } private void invokeDeviceEventListener(HdmiDeviceInfo info, int event) { if (!hideDevicesBehindLegacySwitch(info)) { mHdmiControlService.invokeDeviceEventListeners(info, event); } } /** * 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 (info.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) { // Don't notify listeners of devices that haven't reported their physical address yet return; } else if (old == null || old.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) { invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE); } else if (!old.equals(info)) { invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE); } } @ServiceThreadOnly private void updateSafeDeviceInfoList() { assertRunOnServiceThread(); List copiedDevices = HdmiUtils.sparseArrayToList(mDeviceInfos); List externalInputs = getInputDevices(); mSafeAllDeviceInfos = copiedDevices; mSafeExternalInputs = externalInputs; } /** * Return a list of all {@link HdmiDeviceInfo}. * *

Declared as package-private. accessed by {@link HdmiControlService} only. * This is not thread-safe. For thread safety, call {@link #getSafeExternalInputsLocked} which * does not include local device. */ @ServiceThreadOnly List getDeviceInfoList(boolean includeLocalDevice) { assertRunOnServiceThread(); if (includeLocalDevice) { return HdmiUtils.sparseArrayToList(mDeviceInfos); } else { ArrayList infoList = new ArrayList<>(); for (int i = 0; i < mDeviceInfos.size(); ++i) { HdmiDeviceInfo info = mDeviceInfos.valueAt(i); if (!isLocalDeviceAddress(info.getLogicalAddress())) { infoList.add(info); } } return infoList; } } /** * Return external input devices. */ @GuardedBy("mLock") List getSafeExternalInputsLocked() { return mSafeExternalInputs; } /** * Return a list of external cec input (source) devices. * *

Note that this effectively excludes non-source devices like system audio, * secondary TV. */ private List getInputDevices() { ArrayList infoList = new ArrayList<>(); for (int i = 0; i < mDeviceInfos.size(); ++i) { HdmiDeviceInfo info = mDeviceInfos.valueAt(i); if (isLocalDeviceAddress(info.getLogicalAddress())) { continue; } if (info.isSourceType() && !hideDevicesBehindLegacySwitch(info)) { infoList.add(info); } } return infoList; } // Check if we are hiding CEC devices connected to a legacy (non-CEC) switch. // This only applies to TV devices. // Returns true if the policy is set to true, and the device to check does not have // a parent CEC device (which should be the CEC-enabled switch) in the list. // Devices with an invalid physical address are assumed to NOT be connected to a legacy switch. private boolean hideDevicesBehindLegacySwitch(HdmiDeviceInfo info) { return isLocalDeviceAddress(Constants.ADDR_TV) && HdmiConfig.HIDE_DEVICES_BEHIND_LEGACY_SWITCH && !isConnectedToCecSwitch(info.getPhysicalAddress(), getCecSwitches()) && info.getPhysicalAddress() != HdmiDeviceInfo.PATH_INVALID; } /** * 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(HdmiCecLocalDevice localDevice, int address) { assertRunOnServiceThread(); HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address)); mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState(); localDevice.mCecMessageCache.flushMessagesFrom(address); if (info.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) { // Don't notify listeners of devices that haven't reported their physical address yet return; } invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); } public 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; } updateCecDevice(info.toBuilder().setDevicePowerStatus(newPowerStatus).build()); } /** * Whether a device of the specified physical address is connected to ARC enabled port. */ boolean isConnectedToArcPort(int physicalAddress) { int portId = physicalAddressToPortId(physicalAddress); if (portId != Constants.INVALID_PORT_ID && portId != Constants.CEC_SWITCH_HOME) { return mPortInfoMap.get(portId).isArcSupported(); } return false; } // Initialize HDMI port information. Combine the information from CEC and MHL HAL and // keep them in one place. @ServiceThreadOnly @VisibleForTesting public void initPortInfo() { assertRunOnServiceThread(); HdmiPortInfo[] cecPortInfo = null; // CEC HAL provides majority of the info while MHL does only MHL support flag for // each port. Return empty array if CEC HAL didn't provide the info. if (mHdmiCecController != null) { cecPortInfo = mHdmiCecController.getPortInfos(); } if (cecPortInfo == null) { return; } SparseArray portInfoMap = new SparseArray<>(); SparseIntArray portIdMap = new SparseIntArray(); SparseArray portDeviceMap = new SparseArray<>(); for (HdmiPortInfo info : cecPortInfo) { portIdMap.put(info.getAddress(), info.getId()); portInfoMap.put(info.getId(), info); portDeviceMap.put(info.getId(), HdmiDeviceInfo.hardwarePort(info.getAddress(), info.getId())); } mPortIdMap = new UnmodifiableSparseIntArray(portIdMap); mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap); mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap); if (mHdmiMhlController == null) { return; } HdmiPortInfo[] mhlPortInfo = mHdmiMhlController.getPortInfos(); ArraySet mhlSupportedPorts = new ArraySet(mhlPortInfo.length); for (HdmiPortInfo info : mhlPortInfo) { if (info.isMhlSupported()) { mhlSupportedPorts.add(info.getId()); } } // Build HDMI port info list with CEC port info plus MHL supported flag. We can just use // cec port info if we do not have have port that supports MHL. if (mhlSupportedPorts.isEmpty()) { setPortInfo(Collections.unmodifiableList(Arrays.asList(cecPortInfo))); return; } ArrayList result = new ArrayList<>(cecPortInfo.length); for (HdmiPortInfo info : cecPortInfo) { if (mhlSupportedPorts.contains(info.getId())) { result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(), info.isCecSupported(), true, info.isArcSupported())); } else { result.add(info); } } setPortInfo(Collections.unmodifiableList(result)); } HdmiDeviceInfo getDeviceForPortId(int portId) { return mPortDeviceMap.get(portId, HdmiDeviceInfo.INACTIVE_DEVICE); } /** * Whether a device of the specified physical address and logical address exists * in a device info list. However, both are minimal condition and it could * be different device from the original one. * * @param logicalAddress logical address of a device to be searched * @param physicalAddress physical address of a device to be searched * @return true if exist; otherwise false */ @ServiceThreadOnly boolean isInDeviceList(int logicalAddress, int physicalAddress) { assertRunOnServiceThread(); HdmiDeviceInfo device = getCecDeviceInfo(logicalAddress); if (device == null) { return false; } return device.getPhysicalAddress() == physicalAddress; } /** * Attempts to deduce the device type of a device given its logical address. * If multiple types are possible, returns {@link HdmiDeviceInfo#DEVICE_RESERVED}. */ private static int logicalAddressToDeviceType(int logicalAddress) { switch (logicalAddress) { case Constants.ADDR_TV: return HdmiDeviceInfo.DEVICE_TV; case Constants.ADDR_RECORDER_1: case Constants.ADDR_RECORDER_2: case Constants.ADDR_RECORDER_3: return HdmiDeviceInfo.DEVICE_RECORDER; case Constants.ADDR_TUNER_1: case Constants.ADDR_TUNER_2: case Constants.ADDR_TUNER_3: case Constants.ADDR_TUNER_4: return HdmiDeviceInfo.DEVICE_TUNER; case Constants.ADDR_PLAYBACK_1: case Constants.ADDR_PLAYBACK_2: case Constants.ADDR_PLAYBACK_3: return HdmiDeviceInfo.DEVICE_PLAYBACK; case Constants.ADDR_AUDIO_SYSTEM: return HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM; default: return HdmiDeviceInfo.DEVICE_RESERVED; } } /** * Passively listen to incoming CEC messages. * * This shall not result in any CEC messages being sent. */ @ServiceThreadOnly public void handleCecMessage(HdmiCecMessage message) { assertRunOnServiceThread(); // Add device by logical address if it's not already known int sourceAddress = message.getSource(); if (getCecDeviceInfo(sourceAddress) == null) { HdmiDeviceInfo newDevice = HdmiDeviceInfo.cecDeviceBuilder() .setLogicalAddress(sourceAddress) .setDisplayName(HdmiUtils.getDefaultDeviceName(sourceAddress)) .setDeviceType(logicalAddressToDeviceType(sourceAddress)) .build(); addCecDevice(newDevice); } // If a message type has its own class, all valid messages of that type // will be represented by an instance of that class. if (message instanceof ReportFeaturesMessage) { handleReportFeatures((ReportFeaturesMessage) message); } switch (message.getOpcode()) { case Constants.MESSAGE_FEATURE_ABORT: handleFeatureAbort(message); break; case Constants.MESSAGE_REPORT_PHYSICAL_ADDRESS: handleReportPhysicalAddress(message); break; case Constants.MESSAGE_REPORT_POWER_STATUS: handleReportPowerStatus(message); break; case Constants.MESSAGE_SET_OSD_NAME: handleSetOsdName(message); break; case Constants.MESSAGE_DEVICE_VENDOR_ID: handleDeviceVendorId(message); break; case Constants.MESSAGE_CEC_VERSION: handleCecVersion(message); break; } } @ServiceThreadOnly private void handleReportFeatures(ReportFeaturesMessage message) { assertRunOnServiceThread(); HdmiDeviceInfo currentDeviceInfo = getCecDeviceInfo(message.getSource()); HdmiDeviceInfo newDeviceInfo = currentDeviceInfo.toBuilder() .setCecVersion(message.getCecVersion()) .updateDeviceFeatures(message.getDeviceFeatures()) .build(); updateCecDevice(newDeviceInfo); mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState(); } @ServiceThreadOnly private void handleFeatureAbort(HdmiCecMessage message) { assertRunOnServiceThread(); if (message.getParams().length < 2) { return; } int originalOpcode = message.getParams()[0] & 0xFF; int reason = message.getParams()[1] & 0xFF; // Check if we received in response to . // This provides information on whether the source supports the message. if (originalOpcode == Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL) { @DeviceFeatures.FeatureSupportStatus int featureSupport = reason == Constants.ABORT_UNRECOGNIZED_OPCODE ? DeviceFeatures.FEATURE_NOT_SUPPORTED : DeviceFeatures.FEATURE_SUPPORT_UNKNOWN; HdmiDeviceInfo currentDeviceInfo = getCecDeviceInfo(message.getSource()); HdmiDeviceInfo newDeviceInfo = currentDeviceInfo.toBuilder() .updateDeviceFeatures( currentDeviceInfo.getDeviceFeatures().toBuilder() .setSetAudioVolumeLevelSupport(featureSupport) .build() ) .build(); updateCecDevice(newDeviceInfo); mHdmiControlService.checkAndUpdateAbsoluteVolumeControlState(); } } @ServiceThreadOnly private void handleCecVersion(HdmiCecMessage message) { assertRunOnServiceThread(); int version = Byte.toUnsignedInt(message.getParams()[0]); updateDeviceCecVersion(message.getSource(), version); } @ServiceThreadOnly private void handleReportPhysicalAddress(HdmiCecMessage message) { assertRunOnServiceThread(); int logicalAddress = message.getSource(); int physicalAddress = HdmiUtils.twoBytesToInt(message.getParams()); int type = message.getParams()[2]; if (updateCecSwitchInfo(logicalAddress, type, physicalAddress)) return; HdmiDeviceInfo deviceInfo = getCecDeviceInfo(logicalAddress); if (deviceInfo == null) { Slog.i(TAG, "Unknown source device info for " + message); } else { HdmiDeviceInfo updatedDeviceInfo = deviceInfo.toBuilder() .setPhysicalAddress(physicalAddress) .setPortId(physicalAddressToPortId(physicalAddress)) .setDeviceType(type) .build(); updateCecDevice(updatedDeviceInfo); } } @ServiceThreadOnly private void handleReportPowerStatus(HdmiCecMessage message) { assertRunOnServiceThread(); // Update power status of device int newStatus = message.getParams()[0] & 0xFF; updateDevicePowerStatus(message.getSource(), newStatus); if (message.getDestination() == Constants.ADDR_BROADCAST) { updateDeviceCecVersion(message.getSource(), HdmiControlManager.HDMI_CEC_VERSION_2_0); } } @ServiceThreadOnly private void updateDeviceCecVersion(int logicalAddress, int hdmiCecVersion) { assertRunOnServiceThread(); HdmiDeviceInfo deviceInfo = getCecDeviceInfo(logicalAddress); if (deviceInfo == null) { Slog.w(TAG, "Can not update CEC version of non-existing device:" + logicalAddress); return; } if (deviceInfo.getCecVersion() == hdmiCecVersion) { return; } HdmiDeviceInfo updatedDeviceInfo = deviceInfo.toBuilder() .setCecVersion(hdmiCecVersion) .build(); updateCecDevice(updatedDeviceInfo); } @ServiceThreadOnly private void handleSetOsdName(HdmiCecMessage message) { assertRunOnServiceThread(); int logicalAddress = message.getSource(); String osdName; HdmiDeviceInfo deviceInfo = getCecDeviceInfo(logicalAddress); // If the device is not in device list, ignore it. if (deviceInfo == null) { Slog.i(TAG, "No source device info for ." + message); return; } try { osdName = new String(message.getParams(), "US-ASCII"); } catch (UnsupportedEncodingException e) { Slog.e(TAG, "Invalid request:" + message, e); return; } if (deviceInfo.getDisplayName() != null && deviceInfo.getDisplayName().equals(osdName)) { Slog.d(TAG, "Ignore incoming having same osd name:" + message); return; } Slog.d(TAG, "Updating device OSD name from " + deviceInfo.getDisplayName() + " to " + osdName); HdmiDeviceInfo updatedDeviceInfo = deviceInfo.toBuilder() .setDisplayName(osdName) .build(); updateCecDevice(updatedDeviceInfo); } @ServiceThreadOnly private void handleDeviceVendorId(HdmiCecMessage message) { assertRunOnServiceThread(); int logicalAddress = message.getSource(); int vendorId = HdmiUtils.threeBytesToInt(message.getParams()); HdmiDeviceInfo deviceInfo = getCecDeviceInfo(logicalAddress); if (deviceInfo == null) { Slog.i(TAG, "Unknown source device info for " + message); } else { HdmiDeviceInfo updatedDeviceInfo = deviceInfo.toBuilder() .setVendorId(vendorId) .build(); updateCecDevice(updatedDeviceInfo); } } void addCecSwitch(int physicalAddress) { mCecSwitches.add(physicalAddress); } public ArraySet getCecSwitches() { return mCecSwitches; } void removeCecSwitches(int portId) { Iterator it = mCecSwitches.iterator(); while (it.hasNext()) { int path = it.next(); int devicePortId = physicalAddressToPortId(path); if (devicePortId == portId || devicePortId == Constants.INVALID_PORT_ID) { it.remove(); } } } void removeDevicesConnectedToPort(int portId) { removeCecSwitches(portId); List toRemove = new ArrayList<>(); for (int i = 0; i < mDeviceInfos.size(); i++) { int key = mDeviceInfos.keyAt(i); int physicalAddress = mDeviceInfos.get(key).getPhysicalAddress(); int devicePortId = physicalAddressToPortId(physicalAddress); if (devicePortId == portId || devicePortId == Constants.INVALID_PORT_ID) { toRemove.add(key); } } for (Integer key : toRemove) { removeDeviceInfo(key); } } boolean updateCecSwitchInfo(int address, int type, int path) { if (address == Constants.ADDR_UNREGISTERED && type == HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH) { mCecSwitches.add(path); updateSafeDeviceInfoList(); return true; // Pure switch does not need further processing. Return here. } if (type == HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM) { mCecSwitches.add(path); } return false; } @GuardedBy("mLock") List getSafeCecDevicesLocked() { ArrayList infoList = new ArrayList<>(); for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { if (isLocalDeviceAddress(info.getLogicalAddress())) { continue; } infoList.add(info); } return infoList; } /** * Thread safe version of {@link #getCecDeviceInfo(int)}. * * @param logicalAddress logical address to be retrieved * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}. * Returns null if no logical address matched */ @Nullable HdmiDeviceInfo getSafeCecDeviceInfo(int logicalAddress) { for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { if (info.isCecDevice() && info.getLogicalAddress() == logicalAddress) { return info; } } return null; } /** * Returns the {@link HdmiDeviceInfo} instance whose physical address matches * the given routing path. CEC devices use routing path for its physical address to * describe the hierarchy of the devices in the network. * * @param path routing path or physical address * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null */ @ServiceThreadOnly final HdmiDeviceInfo getDeviceInfoByPath(int path) { assertRunOnServiceThread(); for (HdmiDeviceInfo info : getDeviceInfoList(false)) { if (info.getPhysicalAddress() == path) { return info; } } return null; } /** * Returns the {@link HdmiDeviceInfo} instance whose physical address matches * the given routing path. This is the version accessible safely from threads * other than service thread. * * @param path routing path or physical address * @return {@link HdmiDeviceInfo} if the matched info is found; otherwise null */ HdmiDeviceInfo getSafeDeviceInfoByPath(int path) { for (HdmiDeviceInfo info : mSafeAllDeviceInfos) { if (info.getPhysicalAddress() == path) { return info; } } return null; } public int getPhysicalAddress() { return mHdmiCecController.getPhysicalAddress(); } @ServiceThreadOnly public void clear() { assertRunOnServiceThread(); initPortInfo(); clearDeviceList(); clearLocalDevices(); } @ServiceThreadOnly public void clearDeviceList() { assertRunOnServiceThread(); for (HdmiDeviceInfo info : HdmiUtils.sparseArrayToList(mDeviceInfos)) { if (info.getPhysicalAddress() == getPhysicalAddress() || info.getPhysicalAddress() == HdmiDeviceInfo.PATH_INVALID) { // Don't notify listeners of local devices or devices that haven't reported their // physical address yet continue; } invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE); } mDeviceInfos.clear(); updateSafeDeviceInfoList(); } /** * Returns HDMI port information for the given port id. * * @param portId HDMI port id * @return {@link HdmiPortInfo} for the given port */ HdmiPortInfo getPortInfo(int portId) { return mPortInfoMap.get(portId, null); } /** * Returns the routing path (physical address) of the HDMI port for the given * port id. */ int portIdToPath(int portId) { HdmiPortInfo portInfo = getPortInfo(portId); if (portInfo == null) { Slog.e(TAG, "Cannot find the port info: " + portId); return Constants.INVALID_PHYSICAL_ADDRESS; } return portInfo.getAddress(); } /** * Returns the id of HDMI port located at the current device that runs this method. * * For TV with physical address 0x0000, target device 0x1120, we want port physical address * 0x1000 to get the correct port id from {@link #mPortIdMap}. For device with Physical Address * 0x2000, target device 0x2420, we want port address 0x24000 to get the port id. * *

Return {@link Constants#INVALID_PORT_ID} if target device does not connect to. * * @param path the target device's physical address. * @return the id of the port that the target device eventually connects to * on the current device. */ int physicalAddressToPortId(int path) { int physicalAddress = getPhysicalAddress(); if (path == physicalAddress) { // The local device isn't connected to any port; assign portId 0 return Constants.CEC_SWITCH_HOME; } int mask = 0xF000; int finalMask = 0xF000; int maskedAddress = physicalAddress; while (maskedAddress != 0) { maskedAddress = physicalAddress & mask; finalMask |= mask; mask >>= 4; } int portAddress = path & finalMask; return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID); } List getPortInfo() { return mPortInfo; } void setPortInfo(List portInfo) { mPortInfo = portInfo; } private boolean isLocalDeviceAddress(int address) { for (int i = 0; i < mLocalDevices.size(); i++) { int key = mLocalDevices.keyAt(i); if (mLocalDevices.get(key).getDeviceInfo().getLogicalAddress() == address) { return true; } } return false; } private void assertRunOnServiceThread() { if (Looper.myLooper() != mHandler.getLooper()) { throw new IllegalStateException("Should run on service thread."); } } protected void dump(IndentingPrintWriter pw) { pw.println("HDMI CEC Network"); pw.increaseIndent(); HdmiUtils.dumpIterable(pw, "mPortInfo:", mPortInfo); for (int i = 0; i < mLocalDevices.size(); ++i) { pw.println("HdmiCecLocalDevice #" + mLocalDevices.keyAt(i) + ":"); pw.increaseIndent(); mLocalDevices.valueAt(i).dump(pw); pw.println("Active Source history:"); pw.increaseIndent(); final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); ArrayBlockingQueue activeSourceHistory = mLocalDevices.valueAt(i).getActiveSourceHistory(); for (HdmiCecController.Dumpable activeSourceEvent : activeSourceHistory) { activeSourceEvent.dump(pw, sdf); } pw.decreaseIndent(); pw.decreaseIndent(); } HdmiUtils.dumpIterable(pw, "mDeviceInfos:", mSafeAllDeviceInfos); pw.decreaseIndent(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy