blob: 6b42b3d06d4be84e1ef870ed9b1eb100e20ac42b [file] [log] [blame]
Nick Chalko0816eb32018-11-09 11:49:26 -08001/*
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 */
16package com.android.server;
17
18import android.annotation.Nullable;
19import android.os.UEventObserver;
20import android.util.ArrayMap;
21import android.util.Slog;
22
23import java.io.File;
24import java.io.IOException;
Nick Chalko5f1402c2018-11-09 11:52:16 -080025import java.util.ArrayList;
26import java.util.List;
Nick Chalko0816eb32018-11-09 11:49:26 -080027import java.util.Locale;
28import java.util.Map;
Nick Chalko5f1402c2018-11-09 11:52:16 -080029import java.util.regex.Pattern;
Nick Chalko0816eb32018-11-09 11:49:26 -080030
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 *
Nick Chalko0816eb32018-11-09 11:49:26 -080046 * @hide
47 */
48public abstract class ExtconUEventObserver extends UEventObserver {
49 private static final String TAG = "ExtconUEventObserver";
50 private static final boolean LOG = false;
Nick Chalko5f1402c2018-11-09 11:52:16 -080051 private static final String SELINUX_POLICIES_NEED_TO_BE_CHANGED =
Nick Chalko6bb52622019-02-13 12:24:35 -080052 "This probably means the selinux policies need to be changed.";
Nick Chalko5f1402c2018-11-09 11:52:16 -080053
Nick Chalko0816eb32018-11-09 11:49:26 -080054 private final Map<String, ExtconInfo> mExtconInfos = new ArrayMap<>();
55
56 @Override
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}
Nick Chalko7b5f4362019-02-14 09:45:55 -080071 * @param event the event
Nick Chalko0816eb32018-11-09 11:49:26 -080072 */
73 protected abstract void onUEvent(ExtconInfo extconInfo, UEvent event);
74
75 /** Starts observing {@link ExtconInfo#getDevicePath()}. */
76 public void startObserving(ExtconInfo extconInfo) {
Nick Chalko5f1402c2018-11-09 11:52:16 -080077 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 }
Nick Chalko0816eb32018-11-09 11:49:26 -080086 }
87
88 /** An External Connection to watch. */
89 public static final class ExtconInfo {
90 private static final String TAG = "ExtconInfo";
91
Nick Chalko5f1402c2018-11-09 11:52:16 -080092 /** Returns a new list of all external connections whose name matches {@code regex}. */
93 public static List<ExtconInfo> getExtconInfos(@Nullable String regex) {
Nick Chalko7b5f4362019-02-14 09:45:55 -080094 if (!extconExists()) {
95 return new ArrayList<>(0); // Always return a new list.
96 }
Nick Chalko5f1402c2018-11-09 11:52:16 -080097 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
Nick Chalko0816eb32018-11-09 11:49:26 -0800121 private final String mName;
122
123 public ExtconInfo(String name) {
124 mName = name;
125 }
126
127 /** The name of the external connection */
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
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 */
157 public String getStatePath() {
158 return String.format(Locale.US, "/sys/class/extcon/%s/state", mName);
159 }
160 }
161
162 /** Does the {@link /sys/class/extcon} directory exist */
163 public static boolean extconExists() {
164 File extconDir = new File("/sys/class/extcon");
Nick Chalko2654c8e2019-02-21 09:18:49 -0800165 return extconDir.exists() && extconDir.isDirectory();
Nick Chalko0816eb32018-11-09 11:49:26 -0800166 }
167}