1 /*
2  * Copyright (C) 2008 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 
17 package android.os;
18 
19 import android.compat.annotation.UnsupportedAppUsage;
20 import android.util.Log;
21 
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 
25 /**
26  * UEventObserver is an abstract class that receives UEvents from the kernel.<p>
27  *
28  * Subclass UEventObserver, implementing onUEvent(UEvent event), then call
29  * startObserving() with a match string. The UEvent thread will then call your
30  * onUEvent() method when a UEvent occurs that contains your match string.<p>
31  *
32  * Call stopObserving() to stop receiving UEvents.<p>
33  *
34  * There is only one UEvent thread per process, even if that process has
35  * multiple UEventObserver subclass instances. The UEvent thread starts when
36  * the startObserving() is called for the first time in that process. Once
37  * started the UEvent thread will not stop (although it can stop notifying
38  * UEventObserver's via stopObserving()).<p>
39  *
40  * @hide
41 */
42 public abstract class UEventObserver {
43     private static final String TAG = "UEventObserver";
44     private static final boolean DEBUG = false;
45 
46     private static UEventThread sThread;
47 
nativeSetup()48     private static native void nativeSetup();
nativeWaitForNextEvent()49     private static native String nativeWaitForNextEvent();
nativeAddMatch(String match)50     private static native void nativeAddMatch(String match);
nativeRemoveMatch(String match)51     private static native void nativeRemoveMatch(String match);
52 
53     @UnsupportedAppUsage
UEventObserver()54     public UEventObserver() {
55     }
56 
57     @Override
finalize()58     protected void finalize() throws Throwable {
59         try {
60             stopObserving();
61         } finally {
62             super.finalize();
63         }
64     }
65 
getThread()66     private static UEventThread getThread() {
67         synchronized (UEventObserver.class) {
68             if (sThread == null) {
69                 sThread = new UEventThread();
70                 sThread.start();
71             }
72             return sThread;
73         }
74     }
75 
peekThread()76     private static UEventThread peekThread() {
77         synchronized (UEventObserver.class) {
78             return sThread;
79         }
80     }
81 
82     /**
83      * Begin observation of UEvents.<p>
84      * This method will cause the UEvent thread to start if this is the first
85      * invocation of startObserving in this process.<p>
86      * Once called, the UEvent thread will call onUEvent() when an incoming
87      * UEvent matches the specified string.<p>
88      * This method can be called multiple times to register multiple matches.
89      * Only one call to stopObserving is required even with multiple registered
90      * matches.
91      *
92      * @param match A substring of the UEvent to match.  Try to be as specific
93      * as possible to avoid incurring unintended additional cost from processing
94      * irrelevant messages.  Netlink messages can be moderately high bandwidth and
95      * are expensive to parse.  For example, some devices may send one netlink message
96      * for each vsync period.
97      */
98     @UnsupportedAppUsage
startObserving(String match)99     public final void startObserving(String match) {
100         if (match == null || match.isEmpty()) {
101             throw new IllegalArgumentException("match substring must be non-empty");
102         }
103 
104         final UEventThread t = getThread();
105         t.addObserver(match, this);
106     }
107 
108     /**
109      * End observation of UEvents.<p>
110      * This process's UEvent thread will never call onUEvent() on this
111      * UEventObserver after this call. Repeated calls have no effect.
112      */
113     @UnsupportedAppUsage
stopObserving()114     public final void stopObserving() {
115         final UEventThread t = peekThread();
116         if (t != null) {
117             t.removeObserver(this);
118         }
119     }
120 
121     /**
122      * Subclasses of UEventObserver should override this method to handle
123      * UEvents.
124      */
125     @UnsupportedAppUsage
onUEvent(UEvent event)126     public abstract void onUEvent(UEvent event);
127 
128     /**
129      * Representation of a UEvent.
130      */
131     public static final class UEvent {
132         // collection of key=value pairs parsed from the uevent message
133         private final HashMap<String,String> mMap = new HashMap<String,String>();
134 
UEvent(String message)135         public UEvent(String message) {
136             int offset = 0;
137             int length = message.length();
138 
139             while (offset < length) {
140                 int equals = message.indexOf('=', offset);
141                 int at = message.indexOf('\0', offset);
142                 if (at < 0) break;
143 
144                 if (equals > offset && equals < at) {
145                     // key is before the equals sign, and value is after
146                     mMap.put(message.substring(offset, equals),
147                             message.substring(equals + 1, at));
148                 }
149 
150                 offset = at + 1;
151             }
152         }
153 
154         @UnsupportedAppUsage
get(String key)155         public String get(String key) {
156             return mMap.get(key);
157         }
158 
159         @UnsupportedAppUsage
get(String key, String defaultValue)160         public String get(String key, String defaultValue) {
161             String result = mMap.get(key);
162             return (result == null ? defaultValue : result);
163         }
164 
toString()165         public String toString() {
166             return mMap.toString();
167         }
168     }
169 
170     private static final class UEventThread extends Thread {
171         /** Many to many mapping of string match to observer.
172          *  Multimap would be better, but not available in android, so use
173          *  an ArrayList where even elements are the String match and odd
174          *  elements the corresponding UEventObserver observer */
175         private final ArrayList<Object> mKeysAndObservers = new ArrayList<Object>();
176 
177         private final ArrayList<UEventObserver> mTempObserversToSignal =
178                 new ArrayList<UEventObserver>();
179 
UEventThread()180         public UEventThread() {
181             super("UEventObserver");
182         }
183 
184         @Override
run()185         public void run() {
186             nativeSetup();
187 
188             while (true) {
189                 String message = nativeWaitForNextEvent();
190                 if (message != null) {
191                     if (DEBUG) {
192                         Log.d(TAG, message);
193                     }
194                     sendEvent(message);
195                 }
196             }
197         }
198 
sendEvent(String message)199         private void sendEvent(String message) {
200             synchronized (mKeysAndObservers) {
201                 final int N = mKeysAndObservers.size();
202                 for (int i = 0; i < N; i += 2) {
203                     final String key = (String)mKeysAndObservers.get(i);
204                     if (message.contains(key)) {
205                         final UEventObserver observer =
206                                 (UEventObserver)mKeysAndObservers.get(i + 1);
207                         mTempObserversToSignal.add(observer);
208                     }
209                 }
210             }
211 
212             if (!mTempObserversToSignal.isEmpty()) {
213                 final UEvent event = new UEvent(message);
214                 final int N = mTempObserversToSignal.size();
215                 for (int i = 0; i < N; i++) {
216                     final UEventObserver observer = mTempObserversToSignal.get(i);
217                     observer.onUEvent(event);
218                 }
219                 mTempObserversToSignal.clear();
220             }
221         }
222 
addObserver(String match, UEventObserver observer)223         public void addObserver(String match, UEventObserver observer) {
224             synchronized (mKeysAndObservers) {
225                 mKeysAndObservers.add(match);
226                 mKeysAndObservers.add(observer);
227                 nativeAddMatch(match);
228             }
229         }
230 
231         /** Removes every key/value pair where value=observer from mObservers */
removeObserver(UEventObserver observer)232         public void removeObserver(UEventObserver observer) {
233             synchronized (mKeysAndObservers) {
234                 for (int i = 0; i < mKeysAndObservers.size(); ) {
235                     if (mKeysAndObservers.get(i + 1) == observer) {
236                         mKeysAndObservers.remove(i + 1);
237                         final String match = (String)mKeysAndObservers.remove(i);
238                         nativeRemoveMatch(match);
239                     } else {
240                         i += 2;
241                     }
242                 }
243             }
244         }
245     }
246 }
247