From 12b5dedfe6d5b7e37e22b969d78f0f9399d7ed42 Mon Sep 17 00:00:00 2001 From: Hongguang Date: Tue, 7 Sep 2021 10:39:08 -0700 Subject: [PATCH] Detect extcon device type from cable name. Kernel modified the extcon device name to extcon[X] for sysfs instead of separate device name. Follow that kernel change, the new ExtconUEventObserver will retrieve extcon device type from /sys/class/extcon/extcon[X]/cable.[Y]/name. The target device needs to label the SoC's extcons to sysfs_extcon in the device's vendor sepolicy to allow the system_server access. Bug: 152245127 Bug: 193492798 Bug: 193114615 Test: presubmit and manual tests Change-Id: I3468acc1ef7a273da7b81a223c884079bf465a9e --- .../android/server/ExtconUEventObserver.java | 172 ++++++++++++++---- .../android/server/WiredAccessoryManager.java | 28 ++- .../server/policy/PhoneWindowManager.java | 38 ++-- 3 files changed, 180 insertions(+), 58 deletions(-) diff --git a/services/core/java/com/android/server/ExtconUEventObserver.java b/services/core/java/com/android/server/ExtconUEventObserver.java index 5bd27c4dd481e..923db4f131e5b 100644 --- a/services/core/java/com/android/server/ExtconUEventObserver.java +++ b/services/core/java/com/android/server/ExtconUEventObserver.java @@ -16,17 +16,19 @@ package com.android.server; import android.annotation.Nullable; +import android.annotation.StringDef; +import android.os.FileUtils; import android.os.UEventObserver; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.Slog; - +import com.android.internal.annotations.GuardedBy; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; -import java.util.Locale; import java.util.Map; -import java.util.regex.Pattern; /** * A specialized UEventObserver that receives UEvents from the kernel for devices in the {@code @@ -87,41 +89,149 @@ public abstract class ExtconUEventObserver extends UEventObserver { /** An External Connection to watch. */ public static final class ExtconInfo { - private static final String TAG = "ExtconInfo"; + /* Copied from drivers/extcon/extcon.c */ - /** Returns a new list of all external connections whose name matches {@code regex}. */ - public static List getExtconInfos(@Nullable String regex) { - if (!extconExists()) { - return new ArrayList<>(0); // Always return a new list. + /* USB external connector */ + public static final String EXTCON_USB = "USB"; + public static final String EXTCON_USB_HOST = "USB-HOST"; + + /* Charger external connector */ + public static final String EXTCON_TA = "TA"; + public static final String EXTCON_FAST_CHARGER = "FAST-CHARGER"; + public static final String EXTCON_SLOW_CHARGER = "SLOW-CHARGER"; + public static final String EXTCON_CHARGE_DOWNSTREAM = "CHARGE-DOWNSTREAM"; + + /* Audio/Video external connector */ + public static final String EXTCON_LINE_IN = "LINE-IN"; + public static final String EXTCON_LINE_OUT = "LINE-OUT"; + public static final String EXTCON_MICROPHONE = "MICROPHONE"; + public static final String EXTCON_HEADPHONE = "HEADPHONE"; + + public static final String EXTCON_HDMI = "HDMI"; + public static final String EXTCON_MHL = "MHL"; + public static final String EXTCON_DVI = "DVI"; + public static final String EXTCON_VGA = "VGA"; + public static final String EXTCON_SPDIF_IN = "SPDIF-IN"; + public static final String EXTCON_SPDIF_OUT = "SPDIF-OUT"; + public static final String EXTCON_VIDEO_IN = "VIDEO-IN"; + public static final String EXTCON_VIDEO_OUT = "VIDEO-OUT"; + + /* Etc external connector */ + public static final String EXTCON_DOCK = "DOCK"; + public static final String EXTCON_JIG = "JIG"; + public static final String EXTCON_MECHANICAL = "MECHANICAL"; + + @StringDef({ + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_TA, + EXTCON_FAST_CHARGER, + EXTCON_SLOW_CHARGER, + EXTCON_CHARGE_DOWNSTREAM, + EXTCON_LINE_IN, + EXTCON_LINE_OUT, + EXTCON_MICROPHONE, + EXTCON_HEADPHONE, + EXTCON_HDMI, + EXTCON_MHL, + EXTCON_DVI, + EXTCON_VGA, + EXTCON_SPDIF_IN, + EXTCON_SPDIF_OUT, + EXTCON_VIDEO_IN, + EXTCON_VIDEO_OUT, + EXTCON_DOCK, + EXTCON_JIG, + EXTCON_MECHANICAL, + }) + + public @interface ExtconDeviceType {} + + private static final Object sLock = new Object(); + private static ExtconInfo[] sExtconInfos = null; + + private final String mName; + private final @ExtconDeviceType HashSet mDeviceTypes = new HashSet<>(); + + @GuardedBy("sLock") + private static void initExtconInfos() { + if (sExtconInfos != null) { + return; } - Pattern p = regex == null ? null : Pattern.compile(regex); + File file = new File("/sys/class/extcon"); File[] files = file.listFiles(); if (files == null) { - Slog.wtf(TAG, file + " exists " + file.exists() + " isDir " + file.isDirectory() - + " but listFiles returns null. " - + SELINUX_POLICIES_NEED_TO_BE_CHANGED); - return new ArrayList<>(0); // Always return a new list. + Slog.w(TAG, + file + " exists " + file.exists() + " isDir " + file.isDirectory() + + " but listFiles returns null." + + SELINUX_POLICIES_NEED_TO_BE_CHANGED); + sExtconInfos = new ExtconInfo[0]; } else { - ArrayList list = new ArrayList(files.length); + List list = new ArrayList<>(files.length); for (File f : files) { - String name = f.getName(); - if (p == null || p.matcher(name).matches()) { - ExtconInfo uei = new ExtconInfo(name); - list.add(uei); - if (LOG) Slog.d(TAG, name + " matches " + regex); - } else { - if (LOG) Slog.d(TAG, name + " does not match " + regex); - } + list.add(new ExtconInfo(f.getName())); } - return list; + sExtconInfos = list.toArray(new ExtconInfo[0]); } } - private final String mName; + /** + * Returns a new list of all external connections for the types given. + */ + public static List getExtconInfoForTypes( + @ExtconDeviceType String[] extconTypes) { + synchronized (sLock) { + initExtconInfos(); + } - public ExtconInfo(String name) { - mName = name; + List extcons = new ArrayList(); + for (ExtconInfo extcon : sExtconInfos) { + for (String type : extconTypes) { + if (extcon.hasCableType(type)) { + extcons.add(extcon); + break; + } + } + } + + return extcons; + } + + /** True if the given type is supported */ + public boolean hasCableType(@ExtconDeviceType String type) { + return mDeviceTypes.contains(type); + } + + private ExtconInfo(String extconName) { + mName = extconName; + + // Retrieve device types from /sys/class/extcon/extcon[X]/cable.[Y]/name + File[] cableDirs = FileUtils.listFilesOrEmpty(new File("/sys/class/extcon", mName), + (dir, cable) -> cable.startsWith("cable.")); + if (cableDirs.length == 0) { + Slog.d(TAG, + "Unable to list cables in /sys/class/extcon/" + mName + ". " + + SELINUX_POLICIES_NEED_TO_BE_CHANGED); + } + + for (File cableDir : cableDirs) { + String cableCanonicalPath = null; + try { + cableCanonicalPath = cableDir.getCanonicalPath(); + String name = FileUtils.readTextFile(new File(cableDir, "name"), 0, null); + name = name.replace("\n", "").replace("\r", ""); + if (LOG) { + Slog.v(TAG, "Add extcon cable " + cableCanonicalPath); + } + mDeviceTypes.add(name); + } catch (IOException ex) { + Slog.w(TAG, + "Unable to read " + cableCanonicalPath + "/name. " + + SELINUX_POLICIES_NEED_TO_BE_CHANGED, + ex); + } + } } /** The name of the external connection */ @@ -139,7 +249,7 @@ public abstract class ExtconUEventObserver extends UEventObserver { @Nullable public String getDevicePath() { try { - String extconPath = String.format(Locale.US, "/sys/class/extcon/%s", mName); + String extconPath = TextUtils.formatSimple("/sys/class/extcon/%s", mName); File devPath = new File(extconPath); if (devPath.exists()) { String canonicalPath = devPath.getCanonicalPath(); @@ -155,16 +265,10 @@ public abstract class ExtconUEventObserver extends UEventObserver { /** The path to the state file */ public String getStatePath() { - return String.format(Locale.US, "/sys/class/extcon/%s/state", mName); + return TextUtils.formatSimple("/sys/class/extcon/%s/state", mName); } } - /** Does the {@code /sys/class/extcon/} directory exist */ - public static boolean namedExtconDirExists(String name) { - File extconDir = new File("/sys/class/extcon/" + name); - return extconDir.exists() && extconDir.isDirectory(); - } - /** Does the {@code /sys/class/extcon} directory exist */ public static boolean extconExists() { File extconDir = new File("/sys/class/extcon"); diff --git a/services/core/java/com/android/server/WiredAccessoryManager.java b/services/core/java/com/android/server/WiredAccessoryManager.java index 7fa93c045ce05..b271d7ece733f 100644 --- a/services/core/java/com/android/server/WiredAccessoryManager.java +++ b/services/core/java/com/android/server/WiredAccessoryManager.java @@ -490,8 +490,12 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { private final List mExtconInfos; WiredAccessoryExtconObserver() { - mExtconInfos = ExtconInfo.getExtconInfos(".*audio.*"); - + mExtconInfos = ExtconInfo.getExtconInfoForTypes(new String[] { + ExtconInfo.EXTCON_HEADPHONE, + ExtconInfo.EXTCON_MICROPHONE, + ExtconInfo.EXTCON_HDMI, + ExtconInfo.EXTCON_LINE_OUT, + }); } private void init() { @@ -521,15 +525,23 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { @Override public Pair parseState(ExtconInfo extconInfo, String status) { if (LOG) Slog.v(TAG, "status " + status); - int []maskAndState = {0,0}; + int[] maskAndState = {0, 0}; // extcon event state changes from kernel4.9 // new state will be like STATE=MICROPHONE=1\nHEADPHONE=0 - updateBit(maskAndState, BIT_HEADSET_NO_MIC, status, "HEADPHONE") ; - updateBit(maskAndState, BIT_HEADSET, status,"MICROPHONE") ; - updateBit(maskAndState, BIT_HDMI_AUDIO, status,"HDMI") ; - updateBit(maskAndState, BIT_LINEOUT, status,"LINE-OUT") ; + if (extconInfo.hasCableType(ExtconInfo.EXTCON_HEADPHONE)) { + updateBit(maskAndState, BIT_HEADSET_NO_MIC, status, ExtconInfo.EXTCON_HEADPHONE); + } + if (extconInfo.hasCableType(ExtconInfo.EXTCON_MICROPHONE)) { + updateBit(maskAndState, BIT_HEADSET, status, ExtconInfo.EXTCON_MICROPHONE); + } + if (extconInfo.hasCableType(ExtconInfo.EXTCON_HDMI)) { + updateBit(maskAndState, BIT_HDMI_AUDIO, status, ExtconInfo.EXTCON_HDMI); + } + if (extconInfo.hasCableType(ExtconInfo.EXTCON_LINE_OUT)) { + updateBit(maskAndState, BIT_LINEOUT, status, ExtconInfo.EXTCON_LINE_OUT); + } if (LOG) Slog.v(TAG, "mask " + maskAndState[0] + " state " + maskAndState[1]); - return Pair.create(maskAndState[0],maskAndState[1]); + return Pair.create(maskAndState[0], maskAndState[1]); } @Override diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index ccd9709f5e10a..3185a286deed7 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -230,6 +230,7 @@ import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.util.HashSet; +import java.util.List; /** * WindowManagerPolicy implementation for the Android phone UI. This @@ -3385,13 +3386,18 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } } - } else if (ExtconUEventObserver.extconExists() - && ExtconUEventObserver.namedExtconDirExists(HdmiVideoExtconUEventObserver.NAME)) { - HdmiVideoExtconUEventObserver observer = new HdmiVideoExtconUEventObserver(); - plugged = observer.init(); - mHDMIObserver = observer; - } else if (localLOGV) { - Slog.v(TAG, "Not observing HDMI plug state because HDMI was not found."); + } else { + final List extcons = + ExtconUEventObserver.ExtconInfo.getExtconInfoForTypes( + new String[] {ExtconUEventObserver.ExtconInfo.EXTCON_HDMI}); + if (!extcons.isEmpty()) { + // TODO: handle more than one HDMI + HdmiVideoExtconUEventObserver observer = new HdmiVideoExtconUEventObserver(); + plugged = observer.init(extcons.get(0)); + mHDMIObserver = observer; + } else if (localLOGV) { + Slog.v(TAG, "Not observing HDMI plug state because HDMI was not found."); + } } // This dance forces the code in setHdmiPlugged to run. @@ -5604,23 +5610,23 @@ public class PhoneWindowManager implements WindowManagerPolicy { private class HdmiVideoExtconUEventObserver extends ExtconStateObserver { private static final String HDMI_EXIST = "HDMI=1"; private static final String NAME = "hdmi"; - private final ExtconInfo mHdmi = new ExtconInfo(NAME); - private boolean init() { + private boolean init(ExtconInfo hdmi) { boolean plugged = false; try { - plugged = parseStateFromFile(mHdmi); + plugged = parseStateFromFile(hdmi); } catch (FileNotFoundException e) { - Slog.w(TAG, mHdmi.getStatePath() - + " not found while attempting to determine initial state", e); + Slog.w(TAG, + hdmi.getStatePath() + + " not found while attempting to determine initial state", + e); } catch (IOException e) { - Slog.e( - TAG, - "Error reading " + mHdmi.getStatePath() + Slog.e(TAG, + "Error reading " + hdmi.getStatePath() + " while attempting to determine initial state", e); } - startObserving(mHdmi); + startObserving(hdmi); return plugged; }