• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.server;
17 
18 import android.annotation.Nullable;
19 import android.annotation.StringDef;
20 import android.os.FileUtils;
21 import android.os.UEventObserver;
22 import android.text.TextUtils;
23 import android.util.ArrayMap;
24 import android.util.Slog;
25 import com.android.internal.annotations.GuardedBy;
26 import java.io.File;
27 import java.io.IOException;
28 import java.util.ArrayList;
29 import java.util.HashSet;
30 import java.util.List;
31 import java.util.Map;
32 
33 /**
34  * A specialized UEventObserver that receives UEvents from the kernel for devices in the {@code
35  * /sys/class/extcon}. directory
36  *
37  * <p>Subclass ExtconUEventObserver, implementing {@link #onUEvent(ExtconInfo, UEvent)}, then call
38  * startObserving() with a ExtconInfo to observe. The UEvent thread will then call your onUEvent()
39  * method when a UEvent occurs that matches the path of your ExtconInfos.
40  *
41  * <p>Call stopObserving() to stop receiving UEvents.
42  *
43  * <p>There is only one UEvent thread per process, even if that process has multiple UEventObserver
44  * subclass instances. The UEvent thread starts when the startObserving() is called for the first
45  * time in that process. Once started the UEvent thread will not stop (although it can stop
46  * notifying UEventObserver's via stopObserving()).
47  *
48  * @hide
49  */
50 public abstract class ExtconUEventObserver extends UEventObserver {
51     private static final String TAG = "ExtconUEventObserver";
52     private static final boolean LOG = false;
53     private static final String SELINUX_POLICIES_NEED_TO_BE_CHANGED =
54             "This probably means the selinux policies need to be changed.";
55 
56     private final Map<String, ExtconInfo> mExtconInfos = new ArrayMap<>();
57 
58     @Override
onUEvent(UEvent event)59     public final void onUEvent(UEvent event) {
60         String devPath = event.get("DEVPATH");
61         ExtconInfo info = mExtconInfos.get(devPath);
62         if (info != null) {
63             onUEvent(info, event);
64         } else {
65             Slog.w(TAG, "No match found for DEVPATH of " + event + " in " + mExtconInfos);
66         }
67     }
68 
69     /**
70      * Subclasses of ExtconUEventObserver should override this method to handle UEvents.
71      *
72      * @param extconInfo that matches the {@code DEVPATH} of {@code event}
73      * @param event      the event
74      */
onUEvent(ExtconInfo extconInfo, UEvent event)75     protected abstract void onUEvent(ExtconInfo extconInfo, UEvent event);
76 
77     /** Starts observing {@link ExtconInfo#getDevicePath()}. */
startObserving(ExtconInfo extconInfo)78     public void startObserving(ExtconInfo extconInfo) {
79         String devicePath = extconInfo.getDevicePath();
80         if (devicePath == null) {
81             Slog.wtf(TAG, "Unable to start observing  " + extconInfo.getName()
82                     + " because the device path is null. " + SELINUX_POLICIES_NEED_TO_BE_CHANGED);
83         } else {
84             mExtconInfos.put(devicePath, extconInfo);
85             if (LOG) Slog.v(TAG, "Observing  " + devicePath);
86             startObserving("DEVPATH=" + devicePath);
87         }
88     }
89 
90     /** An External Connection to watch. */
91     public static final class ExtconInfo {
92         /* Copied from drivers/extcon/extcon.c */
93 
94         /* USB external connector */
95         public static final String EXTCON_USB = "USB";
96         public static final String EXTCON_USB_HOST = "USB-HOST";
97 
98         /* Charger external connector */
99         public static final String EXTCON_TA = "TA";
100         public static final String EXTCON_FAST_CHARGER = "FAST-CHARGER";
101         public static final String EXTCON_SLOW_CHARGER = "SLOW-CHARGER";
102         public static final String EXTCON_CHARGE_DOWNSTREAM = "CHARGE-DOWNSTREAM";
103 
104         /* Audio/Video external connector */
105         public static final String EXTCON_LINE_IN = "LINE-IN";
106         public static final String EXTCON_LINE_OUT = "LINE-OUT";
107         public static final String EXTCON_MICROPHONE = "MICROPHONE";
108         public static final String EXTCON_HEADPHONE = "HEADPHONE";
109 
110         public static final String EXTCON_HDMI = "HDMI";
111         public static final String EXTCON_MHL = "MHL";
112         public static final String EXTCON_DVI = "DVI";
113         public static final String EXTCON_VGA = "VGA";
114         public static final String EXTCON_SPDIF_IN = "SPDIF-IN";
115         public static final String EXTCON_SPDIF_OUT = "SPDIF-OUT";
116         public static final String EXTCON_VIDEO_IN = "VIDEO-IN";
117         public static final String EXTCON_VIDEO_OUT = "VIDEO-OUT";
118 
119         /* Etc external connector */
120         public static final String EXTCON_DOCK = "DOCK";
121         public static final String EXTCON_JIG = "JIG";
122         public static final String EXTCON_MECHANICAL = "MECHANICAL";
123 
124         @StringDef({
125                 EXTCON_USB,
126                 EXTCON_USB_HOST,
127                 EXTCON_TA,
128                 EXTCON_FAST_CHARGER,
129                 EXTCON_SLOW_CHARGER,
130                 EXTCON_CHARGE_DOWNSTREAM,
131                 EXTCON_LINE_IN,
132                 EXTCON_LINE_OUT,
133                 EXTCON_MICROPHONE,
134                 EXTCON_HEADPHONE,
135                 EXTCON_HDMI,
136                 EXTCON_MHL,
137                 EXTCON_DVI,
138                 EXTCON_VGA,
139                 EXTCON_SPDIF_IN,
140                 EXTCON_SPDIF_OUT,
141                 EXTCON_VIDEO_IN,
142                 EXTCON_VIDEO_OUT,
143                 EXTCON_DOCK,
144                 EXTCON_JIG,
145                 EXTCON_MECHANICAL,
146         })
147 
148         public @interface ExtconDeviceType {}
149 
150         private static final Object sLock = new Object();
151         private static ExtconInfo[] sExtconInfos = null;
152 
153         private final String mName;
154         private final @ExtconDeviceType HashSet<String> mDeviceTypes = new HashSet<>();
155 
156         @GuardedBy("sLock")
initExtconInfos()157         private static void initExtconInfos() {
158             if (sExtconInfos != null) {
159                 return;
160             }
161 
162             File file = new File("/sys/class/extcon");
163             File[] files = file.listFiles();
164             if (files == null) {
165                 Slog.w(TAG,
166                         file + " exists " + file.exists() + " isDir " + file.isDirectory()
167                                 + " but listFiles returns null."
168                                 + SELINUX_POLICIES_NEED_TO_BE_CHANGED);
169                 sExtconInfos = new ExtconInfo[0];
170             } else {
171                 List<ExtconInfo> list = new ArrayList<>(files.length);
172                 for (File f : files) {
173                     list.add(new ExtconInfo(f.getName()));
174                 }
175                 sExtconInfos = list.toArray(new ExtconInfo[0]);
176             }
177         }
178 
179         /**
180          * Returns a new list of all external connections for the types given.
181          */
getExtconInfoForTypes( @xtconDeviceType String[] extconTypes)182         public static List<ExtconInfo> getExtconInfoForTypes(
183                 @ExtconDeviceType String[] extconTypes) {
184             synchronized (sLock) {
185                 initExtconInfos();
186             }
187 
188             List<ExtconInfo> extcons = new ArrayList<ExtconInfo>();
189             for (ExtconInfo extcon : sExtconInfos) {
190                 for (String type : extconTypes) {
191                     if (extcon.hasCableType(type)) {
192                         extcons.add(extcon);
193                         break;
194                     }
195                 }
196             }
197 
198             return extcons;
199         }
200 
201         /** True if the given type is supported */
hasCableType(@xtconDeviceType String type)202         public boolean hasCableType(@ExtconDeviceType String type) {
203             return mDeviceTypes.contains(type);
204         }
205 
ExtconInfo(String extconName)206         private ExtconInfo(String extconName) {
207             mName = extconName;
208 
209             // Retrieve device types from /sys/class/extcon/extcon[X]/cable.[Y]/name
210             File[] cableDirs = FileUtils.listFilesOrEmpty(new File("/sys/class/extcon", mName),
211                     (dir, cable) -> cable.startsWith("cable."));
212             if (cableDirs.length == 0) {
213                 Slog.d(TAG,
214                         "Unable to list cables in /sys/class/extcon/" + mName + ". "
215                                 + SELINUX_POLICIES_NEED_TO_BE_CHANGED);
216             }
217 
218             for (File cableDir : cableDirs) {
219                 String cableCanonicalPath = null;
220                 try {
221                     cableCanonicalPath = cableDir.getCanonicalPath();
222                     String name = FileUtils.readTextFile(new File(cableDir, "name"), 0, null);
223                     name = name.replace("\n", "").replace("\r", "");
224                     if (LOG) {
225                         Slog.v(TAG, "Add extcon cable " + cableCanonicalPath);
226                     }
227                     mDeviceTypes.add(name);
228                 } catch (IOException ex) {
229                     Slog.w(TAG,
230                             "Unable to read " + cableCanonicalPath + "/name. "
231                                     + SELINUX_POLICIES_NEED_TO_BE_CHANGED,
232                             ex);
233                 }
234             }
235         }
236 
237         /** The name of the external connection */
getName()238         public String getName() {
239             return mName;
240         }
241 
242         /**
243          * The path to the device for this external connection.
244          *
245          * <p><b>NOTE</b> getting this path involves resolving a symlink.
246          *
247          * @return the device path, or null if it not found.
248          */
249         @Nullable
getDevicePath()250         public String getDevicePath() {
251             try {
252                 String extconPath = TextUtils.formatSimple("/sys/class/extcon/%s", mName);
253                 File devPath = new File(extconPath);
254                 if (devPath.exists()) {
255                     String canonicalPath = devPath.getCanonicalPath();
256                     int start = canonicalPath.indexOf("/devices");
257                     return canonicalPath.substring(start);
258                 }
259                 return null;
260             } catch (IOException e) {
261                 Slog.e(TAG, "Could not get the extcon device path for " + mName, e);
262                 return null;
263             }
264         }
265 
266         /** The path to the state file */
getStatePath()267         public String getStatePath() {
268             return TextUtils.formatSimple("/sys/class/extcon/%s/state", mName);
269         }
270     }
271 
272     /** Does the {@code /sys/class/extcon} directory exist */
extconExists()273     public static boolean extconExists() {
274         File extconDir = new File("/sys/class/extcon");
275         return extconDir.exists() && extconDir.isDirectory();
276     }
277 }
278