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.os.UEventObserver; 20 import android.util.ArrayMap; 21 import android.util.Slog; 22 23 import java.io.File; 24 import java.io.IOException; 25 import java.util.ArrayList; 26 import java.util.List; 27 import java.util.Locale; 28 import java.util.Map; 29 import java.util.regex.Pattern; 30 31 /** 32 * A specialized UEventObserver that receives UEvents from the kernel for devices in the {@code 33 * /sys/class/extcon}. directory 34 * 35 * <p>Subclass ExtconUEventObserver, implementing {@link #onUEvent(ExtconInfo, UEvent)}, then call 36 * startObserving() with a ExtconInfo to observe. The UEvent thread will then call your onUEvent() 37 * method when a UEvent occurs that matches the path of your ExtconInfos. 38 * 39 * <p>Call stopObserving() to stop receiving UEvents. 40 * 41 * <p>There is only one UEvent thread per process, even if that process has multiple UEventObserver 42 * subclass instances. The UEvent thread starts when the startObserving() is called for the first 43 * time in that process. Once started the UEvent thread will not stop (although it can stop 44 * notifying UEventObserver's via stopObserving()). 45 * 46 * @hide 47 */ 48 public abstract class ExtconUEventObserver extends UEventObserver { 49 private static final String TAG = "ExtconUEventObserver"; 50 private static final boolean LOG = false; 51 private static final String SELINUX_POLICIES_NEED_TO_BE_CHANGED = 52 "This probably means the selinux policies need to be changed."; 53 54 private final Map<String, ExtconInfo> mExtconInfos = new ArrayMap<>(); 55 56 @Override onUEvent(UEvent event)57 public final void onUEvent(UEvent event) { 58 String devPath = event.get("DEVPATH"); 59 ExtconInfo info = mExtconInfos.get(devPath); 60 if (info != null) { 61 onUEvent(info, event); 62 } else { 63 Slog.w(TAG, "No match found for DEVPATH of " + event + " in " + mExtconInfos); 64 } 65 } 66 67 /** 68 * Subclasses of ExtconUEventObserver should override this method to handle UEvents. 69 * 70 * @param extconInfo that matches the {@code DEVPATH} of {@code event} 71 * @param event the event 72 */ onUEvent(ExtconInfo extconInfo, UEvent event)73 protected abstract void onUEvent(ExtconInfo extconInfo, UEvent event); 74 75 /** Starts observing {@link ExtconInfo#getDevicePath()}. */ startObserving(ExtconInfo extconInfo)76 public void startObserving(ExtconInfo extconInfo) { 77 String devicePath = extconInfo.getDevicePath(); 78 if (devicePath == null) { 79 Slog.wtf(TAG, "Unable to start observing " + extconInfo.getName() 80 + " because the device path is null. " + SELINUX_POLICIES_NEED_TO_BE_CHANGED); 81 } else { 82 mExtconInfos.put(devicePath, extconInfo); 83 if (LOG) Slog.v(TAG, "Observing " + devicePath); 84 startObserving("DEVPATH=" + devicePath); 85 } 86 } 87 88 /** An External Connection to watch. */ 89 public static final class ExtconInfo { 90 private static final String TAG = "ExtconInfo"; 91 92 /** Returns a new list of all external connections whose name matches {@code regex}. */ getExtconInfos(@ullable String regex)93 public static List<ExtconInfo> getExtconInfos(@Nullable String regex) { 94 if (!extconExists()) { 95 return new ArrayList<>(0); // Always return a new list. 96 } 97 Pattern p = regex == null ? null : Pattern.compile(regex); 98 File file = new File("/sys/class/extcon"); 99 File[] files = file.listFiles(); 100 if (files == null) { 101 Slog.wtf(TAG, file + " exists " + file.exists() + " isDir " + file.isDirectory() 102 + " but listFiles returns null. " 103 + SELINUX_POLICIES_NEED_TO_BE_CHANGED); 104 return new ArrayList<>(0); // Always return a new list. 105 } else { 106 ArrayList list = new ArrayList(files.length); 107 for (File f : files) { 108 String name = f.getName(); 109 if (p == null || p.matcher(name).matches()) { 110 ExtconInfo uei = new ExtconInfo(name); 111 list.add(uei); 112 if (LOG) Slog.d(TAG, name + " matches " + regex); 113 } else { 114 if (LOG) Slog.d(TAG, name + " does not match " + regex); 115 } 116 } 117 return list; 118 } 119 } 120 121 private final String mName; 122 ExtconInfo(String name)123 public ExtconInfo(String name) { 124 mName = name; 125 } 126 127 /** The name of the external connection */ getName()128 public String getName() { 129 return mName; 130 } 131 132 /** 133 * The path to the device for this external connection. 134 * 135 * <p><b>NOTE</b> getting this path involves resolving a symlink. 136 * 137 * @return the device path, or null if it not found. 138 */ 139 @Nullable getDevicePath()140 public String getDevicePath() { 141 try { 142 String extconPath = String.format(Locale.US, "/sys/class/extcon/%s", mName); 143 File devPath = new File(extconPath); 144 if (devPath.exists()) { 145 String canonicalPath = devPath.getCanonicalPath(); 146 int start = canonicalPath.indexOf("/devices"); 147 return canonicalPath.substring(start); 148 } 149 return null; 150 } catch (IOException e) { 151 Slog.e(TAG, "Could not get the extcon device path for " + mName, e); 152 return null; 153 } 154 } 155 156 /** The path to the state file */ getStatePath()157 public String getStatePath() { 158 return String.format(Locale.US, "/sys/class/extcon/%s/state", mName); 159 } 160 } 161 162 /** Does the {@code /sys/class/extcon/<name>} directory exist */ namedExtconDirExists(String name)163 public static boolean namedExtconDirExists(String name) { 164 File extconDir = new File("/sys/class/extcon/" + name); 165 return extconDir.exists() && extconDir.isDirectory(); 166 } 167 168 /** Does the {@code /sys/class/extcon} directory exist */ extconExists()169 public static boolean extconExists() { 170 File extconDir = new File("/sys/class/extcon"); 171 return extconDir.exists() && extconDir.isDirectory(); 172 } 173 } 174