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
This commit is contained in:
Hongguang
2021-09-07 10:39:08 -07:00
parent 82bf215341
commit 12b5dedfe6
3 changed files with 180 additions and 58 deletions

View File

@@ -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<ExtconInfo> 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<String> 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<ExtconInfo> 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<ExtconInfo> getExtconInfoForTypes(
@ExtconDeviceType String[] extconTypes) {
synchronized (sLock) {
initExtconInfos();
}
public ExtconInfo(String name) {
mName = name;
List<ExtconInfo> extcons = new ArrayList<ExtconInfo>();
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/<name>} 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");

View File

@@ -490,8 +490,12 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks {
private final List<ExtconInfo> 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<Integer, Integer> 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

View File

@@ -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<ExtconUEventObserver.ExtconInfo> 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<Boolean> {
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;
}