1 /*
2  * Copyright (C) 2017 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.net.lowpan;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.Context;
22 import android.os.Handler;
23 import android.os.IBinder;
24 import android.os.Looper;
25 import android.os.RemoteException;
26 import android.os.ServiceManager;
27 import java.lang.ref.WeakReference;
28 import java.util.HashMap;
29 import java.util.Map;
30 import java.util.WeakHashMap;
31 
32 /**
33  * Manager object for looking up LoWPAN interfaces.
34  *
35  * @hide
36  */
37 // @SystemApi
38 public class LowpanManager {
39     private static final String TAG = LowpanManager.class.getSimpleName();
40 
41     /** @hide */
42     // @SystemApi
43     public abstract static class Callback {
onInterfaceAdded(LowpanInterface lowpanInterface)44         public void onInterfaceAdded(LowpanInterface lowpanInterface) {}
45 
onInterfaceRemoved(LowpanInterface lowpanInterface)46         public void onInterfaceRemoved(LowpanInterface lowpanInterface) {}
47     }
48 
49     private final Map<Integer, ILowpanManagerListener> mListenerMap = new HashMap<>();
50     private final Map<String, LowpanInterface> mInterfaceCache = new HashMap<>();
51 
52     /* This is a WeakHashMap because we don't want to hold onto
53      * a strong reference to ILowpanInterface, so that it can be
54      * garbage collected if it isn't being used anymore. Since
55      * the value class holds onto this specific ILowpanInterface,
56      * we also need to have a weak reference to the value.
57      * This design pattern allows us to skip removal of items
58      * from this Map without leaking memory.
59      */
60     private final Map<IBinder, WeakReference<LowpanInterface>> mBinderCache =
61             new WeakHashMap<>();
62 
63     private final ILowpanManager mService;
64     private final Context mContext;
65     private final Looper mLooper;
66 
67     // Static Methods
68 
from(Context context)69     public static LowpanManager from(Context context) {
70         return (LowpanManager) context.getSystemService(Context.LOWPAN_SERVICE);
71     }
72 
73     /** @hide */
getManager()74     public static LowpanManager getManager() {
75         IBinder binder = ServiceManager.getService(Context.LOWPAN_SERVICE);
76 
77         if (binder != null) {
78             ILowpanManager service = ILowpanManager.Stub.asInterface(binder);
79             return new LowpanManager(service);
80         }
81 
82         return null;
83     }
84 
85     // Constructors
86 
LowpanManager(ILowpanManager service)87     LowpanManager(ILowpanManager service) {
88         mService = service;
89         mContext = null;
90         mLooper = null;
91     }
92 
93     /**
94      * Create a new LowpanManager instance. Applications will almost always want to use {@link
95      * android.content.Context#getSystemService Context.getSystemService()} to retrieve the standard
96      * {@link android.content.Context#LOWPAN_SERVICE Context.LOWPAN_SERVICE}.
97      *
98      * @param context the application context
99      * @param service the Binder interface
100      * @param looper the default Looper to run callbacks on
101      * @hide - hide this because it takes in a parameter of type ILowpanManager, which is a system
102      *     private class.
103      */
LowpanManager(Context context, ILowpanManager service, Looper looper)104     public LowpanManager(Context context, ILowpanManager service, Looper looper) {
105         mContext = context;
106         mService = service;
107         mLooper = looper;
108     }
109 
110     /** @hide */
111     @Nullable
getInterfaceNoCreate(@onNull ILowpanInterface ifaceService)112     public LowpanInterface getInterfaceNoCreate(@NonNull ILowpanInterface ifaceService) {
113         LowpanInterface iface = null;
114 
115         synchronized (mBinderCache) {
116             if (mBinderCache.containsKey(ifaceService.asBinder())) {
117                 iface = mBinderCache.get(ifaceService.asBinder()).get();
118             }
119         }
120 
121         return iface;
122     }
123 
124     /** @hide */
125     @Nullable
getInterface(@onNull ILowpanInterface ifaceService)126     public LowpanInterface getInterface(@NonNull ILowpanInterface ifaceService) {
127         LowpanInterface iface = null;
128 
129         try {
130             synchronized (mBinderCache) {
131                 if (mBinderCache.containsKey(ifaceService.asBinder())) {
132                     iface = mBinderCache.get(ifaceService.asBinder()).get();
133                 }
134 
135                 if (iface == null) {
136                     String ifaceName = ifaceService.getName();
137 
138                     iface = new LowpanInterface(mContext, ifaceService, mLooper);
139 
140                     synchronized (mInterfaceCache) {
141                         mInterfaceCache.put(iface.getName(), iface);
142                     }
143 
144                     mBinderCache.put(ifaceService.asBinder(), new WeakReference(iface));
145 
146                     /* Make sure we remove the object from the
147                      * interface cache if the associated service
148                      * dies.
149                      */
150                     ifaceService
151                             .asBinder()
152                             .linkToDeath(
153                                     new IBinder.DeathRecipient() {
154                                         @Override
155                                         public void binderDied() {
156                                             synchronized (mInterfaceCache) {
157                                                 LowpanInterface iface =
158                                                         mInterfaceCache.get(ifaceName);
159 
160                                                 if ((iface != null)
161                                                         && (iface.getService() == ifaceService)) {
162                                                     mInterfaceCache.remove(ifaceName);
163                                                 }
164                                             }
165                                         }
166                                     },
167                                     0);
168                 }
169             }
170         } catch (RemoteException x) {
171             throw x.rethrowAsRuntimeException();
172         }
173 
174         return iface;
175     }
176 
177     /**
178      * Returns a reference to the requested LowpanInterface object. If the given interface doesn't
179      * exist, or it is not a LoWPAN interface, returns null.
180      */
181     @Nullable
getInterface(@onNull String name)182     public LowpanInterface getInterface(@NonNull String name) {
183         LowpanInterface iface = null;
184 
185         try {
186             /* This synchronized block covers both branches of the enclosed
187              * if() statement in order to avoid a race condition. Two threads
188              * calling getInterface() with the same name would race to create
189              * the associated LowpanInterface object, creating two of them.
190              * Having the whole block be synchronized avoids that race.
191              */
192             synchronized (mInterfaceCache) {
193                 if (mInterfaceCache.containsKey(name)) {
194                     iface = mInterfaceCache.get(name);
195 
196                 } else {
197                     ILowpanInterface ifaceService = mService.getInterface(name);
198 
199                     if (ifaceService != null) {
200                         iface = getInterface(ifaceService);
201                     }
202                 }
203             }
204         } catch (RemoteException x) {
205             throw x.rethrowFromSystemServer();
206         }
207 
208         return iface;
209     }
210 
211     /**
212      * Returns a reference to the first registered LowpanInterface object. If there are no LoWPAN
213      * interfaces registered, returns null.
214      */
215     @Nullable
getInterface()216     public LowpanInterface getInterface() {
217         String[] ifaceList = getInterfaceList();
218         if (ifaceList.length > 0) {
219             return getInterface(ifaceList[0]);
220         }
221         return null;
222     }
223 
224     /**
225      * Returns a string array containing the names of LoWPAN interfaces. This list may contain fewer
226      * interfaces if the calling process does not have permissions to see individual interfaces.
227      */
228     @NonNull
getInterfaceList()229     public String[] getInterfaceList() {
230         try {
231             return mService.getInterfaceList();
232         } catch (RemoteException x) {
233             throw x.rethrowFromSystemServer();
234         }
235     }
236 
237     /**
238      * Registers a callback object to receive notifications when LoWPAN interfaces are added or
239      * removed.
240      *
241      * @hide
242      */
registerCallback(@onNull Callback cb, @Nullable Handler handler)243     public void registerCallback(@NonNull Callback cb, @Nullable Handler handler)
244             throws LowpanException {
245         ILowpanManagerListener.Stub listenerBinder =
246                 new ILowpanManagerListener.Stub() {
247                     private Handler mHandler;
248 
249                     {
250                         if (handler != null) {
251                             mHandler = handler;
252                         } else if (mLooper != null) {
253                             mHandler = new Handler(mLooper);
254                         } else {
255                             mHandler = new Handler();
256                         }
257                     }
258 
259                     @Override
260                     public void onInterfaceAdded(ILowpanInterface ifaceService) {
261                         Runnable runnable =
262                                 () -> {
263                                     LowpanInterface iface = getInterface(ifaceService);
264 
265                                     if (iface != null) {
266                                         cb.onInterfaceAdded(iface);
267                                     }
268                                 };
269 
270                         mHandler.post(runnable);
271                     }
272 
273                     @Override
274                     public void onInterfaceRemoved(ILowpanInterface ifaceService) {
275                         Runnable runnable =
276                                 () -> {
277                                     LowpanInterface iface = getInterfaceNoCreate(ifaceService);
278 
279                                     if (iface != null) {
280                                         cb.onInterfaceRemoved(iface);
281                                     }
282                                 };
283 
284                         mHandler.post(runnable);
285                     }
286                 };
287         try {
288             mService.addListener(listenerBinder);
289         } catch (RemoteException x) {
290             throw x.rethrowFromSystemServer();
291         }
292 
293         synchronized (mListenerMap) {
294             mListenerMap.put(Integer.valueOf(System.identityHashCode(cb)), listenerBinder);
295         }
296     }
297 
298     /** @hide */
registerCallback(@onNull Callback cb)299     public void registerCallback(@NonNull Callback cb) throws LowpanException {
300         registerCallback(cb, null);
301     }
302 
303     /**
304      * Unregisters a previously registered {@link LowpanManager.Callback} object.
305      *
306      * @hide
307      */
unregisterCallback(@onNull Callback cb)308     public void unregisterCallback(@NonNull Callback cb) {
309         Integer hashCode = Integer.valueOf(System.identityHashCode(cb));
310         ILowpanManagerListener listenerBinder = null;
311 
312         synchronized (mListenerMap) {
313             listenerBinder = mListenerMap.get(hashCode);
314             mListenerMap.remove(hashCode);
315         }
316 
317         if (listenerBinder != null) {
318             try {
319                 mService.removeListener(listenerBinder);
320             } catch (RemoteException x) {
321                 throw x.rethrowFromSystemServer();
322             }
323         } else {
324             throw new RuntimeException("Attempt to unregister an unknown callback");
325         }
326     }
327 }
328