Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 12b5dedf authored by Hongguang's avatar Hongguang
Browse files

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
parent 82bf2153
Loading
Loading
Loading
Loading
+137 −33
Original line number Diff line number Diff line
@@ -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 */

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

        /** 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.
        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()
                Slog.w(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.
                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()));
                }
                sExtconInfos = list.toArray(new ExtconInfo[0]);
            }
                return list;
        }

        /**
         * Returns a new list of all external connections for the types given.
         */
        public static List<ExtconInfo> getExtconInfoForTypes(
                @ExtconDeviceType String[] extconTypes) {
            synchronized (sLock) {
                initExtconInfos();
            }

        private final String mName;
            List<ExtconInfo> extcons = new ArrayList<ExtconInfo>();
            for (ExtconInfo extcon : sExtconInfos) {
                for (String type : extconTypes) {
                    if (extcon.hasCableType(type)) {
                        extcons.add(extcon);
                        break;
                    }
                }
            }

        public ExtconInfo(String name) {
            mName = name;
            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,14 +265,8 @@ 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 */
+20 −8
Original line number Diff line number Diff line
@@ -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() {
@@ -524,10 +528,18 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks {
            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]);
        }
+22 −16
Original line number Diff line number Diff line
@@ -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,14 +3386,19 @@ public class PhoneWindowManager implements WindowManagerPolicy {
                    }
                }
            }
        } else if (ExtconUEventObserver.extconExists()
                && ExtconUEventObserver.namedExtconDirExists(HdmiVideoExtconUEventObserver.NAME)) {
        } 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();
                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.
        // Always do this so the sticky intent is stuck (to false) if there is no hdmi.
@@ -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;
        }