From 4fdd0f47bc10bdbc429c6316c2cc98a747570b12 Mon Sep 17 00:00:00 2001 From: Jinsuk Kim Date: Wed, 6 Aug 2014 07:23:32 +0900 Subject: [PATCH] CEC: Hide CEC devices behind non-CEC switch CEC devices connected to a non-CEC switch are not under full control of TV. Will be filtered out from the list of devices provided to TIF. Bug: 16797838 Change-Id: I8f8ff3a33ddeeb0a2877ca0e596a24096e648879 --- .../android/server/hdmi/HdmiCecLocalDeviceTv.java | 85 ++++++++++++++++++++-- .../java/com/android/server/hdmi/HdmiConfig.java | 6 ++ 2 files changed, 83 insertions(+), 8 deletions(-) diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java index 6bafbd360ec4..9b668e2fa00f 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java @@ -41,6 +41,7 @@ import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserHandle; import android.provider.Settings.Global; +import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; @@ -52,7 +53,9 @@ import com.android.server.hdmi.HdmiControlService.SendMessageCallback; import java.io.UnsupportedEncodingException; 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.Locale; @@ -109,6 +112,10 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { private final HdmiCecStandbyModeHandler mStandbyHandler; + // 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(); + HdmiCecLocalDeviceTv(HdmiControlService service) { super(service, HdmiCecDeviceInfo.DEVICE_TV); mPrevPortId = Constants.INVALID_PORT_ID; @@ -126,6 +133,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { mAddress, mService.getPhysicalAddress(), mDeviceType)); mService.sendCecCommand(HdmiCecMessageBuilder.buildDeviceVendorIdCommand( mAddress, mService.getVendorId())); + mCecSwitches.add(mService.getPhysicalAddress()); // TV is a CEC switch too. launchRoutingControl(reason != HdmiControlService.INITIATED_BY_ENABLE_CEC && reason != HdmiControlService.INITIATED_BY_BOOT_UP); launchDeviceDiscovery(); @@ -426,15 +434,27 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { @ServiceThreadOnly protected boolean handleReportPhysicalAddress(HdmiCecMessage message) { assertRunOnServiceThread(); + int path = HdmiUtils.twoBytesToInt(message.getParams()); + int address = message.getSource(); + + // Build cec switch list with pure CEC switch, AVR. + if (address == Constants.ADDR_UNREGISTERED) { + int type = message.getParams()[2]; + if (type == HdmiCecDeviceInfo.DEVICE_PURE_CEC_SWITCH) { + mCecSwitches.add(path); + updateSafeDeviceInfoList(); + return true; // Pure switch does not need further processing. Return here. + } else if (type == HdmiCecDeviceInfo.DEVICE_AUDIO_SYSTEM) { + mCecSwitches.add(path); + } + } + // Ignore if [Device Discovery Action] is going on. if (hasAction(DeviceDiscoveryAction.class)) { - Slog.i(TAG, "Ignore unrecognizable " - + "because Device Discovery Action is on-going:" + message); + Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message); return true; } - int path = HdmiUtils.twoBytesToInt(message.getParams()); - int address = message.getSource(); if (!isInDeviceList(path, address)) { handleNewDeviceAtTheTailOfActivePath(path); } @@ -619,7 +639,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { private void clearDeviceInfoList() { assertRunOnServiceThread(); for (HdmiCecDeviceInfo info : mSafeExternalInputs) { - mService.invokeDeviceEventListeners(info, false); + invokeDeviceEventListener(info, false); } mDeviceInfos.clear(); updateSafeDeviceInfoList(); @@ -1012,13 +1032,49 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { if (isLocalDeviceAddress(i)) { continue; } - if (info.isSourceType()) { + if (info.isSourceType() && !hideDevicesBehindLegacySwitch(info)) { infoList.add(info); } } return infoList; } + // Check if we are hiding CEC devices connected to a legacy (non-CEC) switch. + // 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. + private boolean hideDevicesBehindLegacySwitch(HdmiCecDeviceInfo info) { + return HdmiConfig.HIDE_DEVICES_BEHIND_LEGACY_SWITCH + && !isConnectedToCecSwitch(info.getPhysicalAddress(), mCecSwitches); + } + + 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; + } + + private void invokeDeviceEventListener(HdmiCecDeviceInfo info, boolean activated) { + if (!hideDevicesBehindLegacySwitch(info)) { + mService.invokeDeviceEventListeners(info, activated); + } + } + @ServiceThreadOnly private boolean isLocalDeviceAddress(int address) { assertRunOnServiceThread(); @@ -1087,7 +1143,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { // The addition of TV device itself should not be notified. return; } - mService.invokeDeviceEventListeners(info, true); + invokeDeviceEventListener(info, true); } /** @@ -1101,7 +1157,7 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { HdmiCecDeviceInfo info = removeDeviceInfo(address); mCecMessageCache.flushMessagesFrom(address); - mService.invokeDeviceEventListeners(info, false); + invokeDeviceEventListener(info, false); } @ServiceThreadOnly @@ -1190,6 +1246,9 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { void onHotplug(int portId, boolean connected) { assertRunOnServiceThread(); + if (!connected) { + removeCecSwitches(portId); + } // Tv device will have permanent HotplugDetectionAction. List hotplugActions = getActions(HotplugDetectionAction.class); if (!hotplugActions.isEmpty()) { @@ -1200,6 +1259,16 @@ final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice { } } + private void removeCecSwitches(int portId) { + Iterator it = mCecSwitches.iterator(); + while (!it.hasNext()) { + int path = it.next(); + if (pathToPortId(path) == portId) { + it.remove(); + } + } + } + @ServiceThreadOnly void setAutoDeviceOff(boolean enabled) { assertRunOnServiceThread(); diff --git a/services/core/java/com/android/server/hdmi/HdmiConfig.java b/services/core/java/com/android/server/hdmi/HdmiConfig.java index c95c96dc1b8e..046f393a0756 100644 --- a/services/core/java/com/android/server/hdmi/HdmiConfig.java +++ b/services/core/java/com/android/server/hdmi/HdmiConfig.java @@ -46,5 +46,11 @@ final class HdmiConfig { // action. They will have their own retransmission count. static final int RETRANSMISSION_COUNT = 1; + // Do not export the CEC devices connected to a legacy HDMI switch. The usage of legacy + // (non-CEC) switches is deprecated. They stop the correct operation of many mandatory + // CEC features. If set to true, do not pass the list of CEC devices behind the legacy + // switch since they won't be under control from TV. + static final boolean HIDE_DEVICES_BEHIND_LEGACY_SWITCH = true; + private HdmiConfig() { /* cannot be instantiated */ } } -- 2.11.0