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 com.android.server.lowpan;
18 
19 import android.content.pm.PackageManager;
20 import android.content.Context;
21 import android.net.ConnectivityManager;
22 import android.net.ConnectivityManager.NetworkCallback;
23 import android.net.lowpan.ILowpanInterface;
24 import android.net.lowpan.ILowpanManager;
25 import android.net.lowpan.ILowpanManagerListener;
26 import android.net.NetworkCapabilities;
27 import android.net.NetworkRequest;
28 import android.os.Binder;
29 import android.os.HandlerThread;
30 import android.os.IBinder;
31 import android.os.Looper;
32 import android.os.RemoteException;
33 import android.os.ServiceSpecificException;
34 import android.util.Log;
35 import java.util.concurrent.atomic.AtomicBoolean;
36 import java.util.HashMap;
37 import java.util.HashSet;
38 import java.util.Map;
39 import java.util.Set;
40 
41 /**
42  * LowpanService handles remote LoWPAN operation requests by implementing the ILowpanManager
43  * interface.
44  *
45  * @hide
46  */
47 public class LowpanServiceImpl extends ILowpanManager.Stub {
48     private static final String TAG = LowpanServiceImpl.class.getSimpleName();
49     private final Set<ILowpanManagerListener> mListenerSet = new HashSet<>();
50     private final Map<String, LowpanInterfaceTracker> mInterfaceMap = new HashMap<>();
51     private final Context mContext;
52     private final HandlerThread mHandlerThread = new HandlerThread("LowpanServiceThread");
53     private final AtomicBoolean mStarted = new AtomicBoolean(false);
54     private final boolean mIsAndroidThings;
55 
LowpanServiceImpl(Context context)56     public LowpanServiceImpl(Context context) {
57         mContext = context;
58         mIsAndroidThings = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED);
59     }
60 
getLooper()61     public Looper getLooper() {
62         Looper looper = mHandlerThread.getLooper();
63         if (looper == null) {
64             mHandlerThread.start();
65             looper = mHandlerThread.getLooper();
66         }
67 
68         return looper;
69     }
70 
createOutstandingNetworkRequest()71     public void createOutstandingNetworkRequest() {
72         final ConnectivityManager cm =
73                 (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
74 
75         if (cm == null) {
76             throw new IllegalStateException("Bad luck, ConnectivityService not started.");
77         }
78 
79         NetworkRequest request = new NetworkRequest.Builder()
80                 .clearCapabilities()
81                 .addTransportType(NetworkCapabilities.TRANSPORT_LOWPAN)
82                 .build();
83 
84         // Note that this method only ever gets called once,
85         // so we don't need to bother with worrying about unregistering.
86 
87         cm.requestNetwork(request, new NetworkCallback());
88     }
89 
checkAndStartLowpan()90     public void checkAndStartLowpan() {
91         synchronized (mInterfaceMap) {
92             if (mStarted.compareAndSet(false, true)) {
93                 for (Map.Entry<String, LowpanInterfaceTracker> entry : mInterfaceMap.entrySet()) {
94                     entry.getValue().register();
95                 }
96             }
97         }
98 
99         createOutstandingNetworkRequest();
100 
101         // TODO: Bring up any daemons(like wpantund)?
102     }
103 
enforceAccessPermission()104     private void enforceAccessPermission() {
105         try {
106             mContext.enforceCallingOrSelfPermission(
107                     android.Manifest.permission.ACCESS_LOWPAN_STATE, "LowpanService");
108         } catch (SecurityException x) {
109             if (!mIsAndroidThings) {
110                 throw x;
111             }
112             mContext.enforceCallingOrSelfPermission(
113                     "com.google.android.things.permission.ACCESS_LOWPAN_STATE", "LowpanService");
114         }
115     }
116 
enforceManagePermission()117     private void enforceManagePermission() {
118         try {
119             mContext.enforceCallingOrSelfPermission(
120                     android.Manifest.permission.MANAGE_LOWPAN_INTERFACES, "LowpanService");
121         } catch (SecurityException x) {
122             if (!mIsAndroidThings) {
123                 throw x;
124             }
125             mContext.enforceCallingOrSelfPermission(
126                     "com.google.android.things.permission.MANAGE_LOWPAN_INTERFACES", "LowpanService");
127         }
128     }
129 
getInterface(String name)130     public ILowpanInterface getInterface(String name) {
131         ILowpanInterface iface = null;
132 
133         enforceAccessPermission();
134 
135         synchronized (mInterfaceMap) {
136             LowpanInterfaceTracker tracker = mInterfaceMap.get(name);
137             if (tracker != null) {
138                 iface = tracker.mILowpanInterface;
139             }
140         }
141 
142         return iface;
143     }
144 
getInterfaceList()145     public String[] getInterfaceList() {
146         enforceAccessPermission();
147         synchronized (mInterfaceMap) {
148             return mInterfaceMap.keySet().toArray(new String[mInterfaceMap.size()]);
149         }
150     }
151 
onInterfaceRemoved(ILowpanInterface lowpanInterface, String name)152     private void onInterfaceRemoved(ILowpanInterface lowpanInterface, String name) {
153         Log.i(TAG, "Removed LoWPAN interface `" + name + "` (" + lowpanInterface.toString() + ")");
154         synchronized (mListenerSet) {
155             for (ILowpanManagerListener listener : mListenerSet) {
156                 try {
157                     listener.onInterfaceRemoved(lowpanInterface);
158                 } catch (RemoteException | ServiceSpecificException x) {
159                     // Don't let misbehavior of a listener
160                     // crash the system service.
161                     Log.e(TAG, "Exception caught: " + x);
162 
163                     // TODO: Consider removing the listener...?
164                 }
165             }
166         }
167     }
168 
onInterfaceAdded(ILowpanInterface lowpanInterface, String name)169     private void onInterfaceAdded(ILowpanInterface lowpanInterface, String name) {
170         Log.i(TAG, "Added LoWPAN interface `" + name + "` (" + lowpanInterface.toString() + ")");
171         synchronized (mListenerSet) {
172             for (ILowpanManagerListener listener : mListenerSet) {
173                 try {
174                     listener.onInterfaceAdded(lowpanInterface);
175                 } catch (RemoteException | ServiceSpecificException x) {
176                     // Don't let misbehavior of a listener
177                     // crash the system service.
178                     Log.e(TAG, "Exception caught: " + x);
179 
180                     // TODO: Consider removing the listener...?
181                 }
182             }
183         }
184     }
185 
addInterface(ILowpanInterface lowpanInterface)186     public void addInterface(ILowpanInterface lowpanInterface) {
187         enforceManagePermission();
188 
189         final String name;
190 
191         try {
192             // We allow blocking calls to get the name of the interface.
193             Binder.allowBlocking(lowpanInterface.asBinder());
194 
195             name = lowpanInterface.getName();
196             lowpanInterface
197                     .asBinder()
198                     .linkToDeath(
199                             new IBinder.DeathRecipient() {
200                                 @Override
201                                 public void binderDied() {
202                                     Log.w(
203                                             TAG,
204                                             "LoWPAN interface `"
205                                                     + name
206                                                     + "` ("
207                                                     + lowpanInterface.toString()
208                                                     + ") died.");
209                                     removeInterface(lowpanInterface);
210                                 }
211                             },
212                             0);
213 
214         } catch (RemoteException | ServiceSpecificException x) {
215             // Don't let misbehavior of an interface
216             // crash the system service.
217             Log.e(TAG, "Exception caught: " + x);
218             return;
219         }
220 
221         final LowpanInterfaceTracker previous;
222         final LowpanInterfaceTracker agent;
223 
224         synchronized (mInterfaceMap) {
225             previous = mInterfaceMap.get(name);
226 
227             agent = new LowpanInterfaceTracker(mContext, lowpanInterface, getLooper());
228 
229             mInterfaceMap.put(name, agent);
230         }
231 
232         if (previous != null) {
233             previous.unregister();
234             onInterfaceRemoved(previous.mILowpanInterface, name);
235         }
236 
237         if (mStarted.get()) {
238             agent.register();
239         }
240 
241         onInterfaceAdded(lowpanInterface, name);
242     }
243 
removeInterfaceByName(String name)244     private void removeInterfaceByName(String name) {
245         final ILowpanInterface lowpanInterface;
246 
247         enforceManagePermission();
248 
249         if (name == null) {
250             return;
251         }
252 
253         final LowpanInterfaceTracker agent;
254 
255         synchronized (mInterfaceMap) {
256             agent = mInterfaceMap.get(name);
257 
258             if (agent == null) {
259                 return;
260             }
261 
262             lowpanInterface = agent.mILowpanInterface;
263 
264             if (mStarted.get()) {
265                 agent.unregister();
266             }
267 
268             mInterfaceMap.remove(name);
269         }
270 
271         onInterfaceRemoved(lowpanInterface, name);
272     }
273 
removeInterface(ILowpanInterface lowpanInterface)274     public void removeInterface(ILowpanInterface lowpanInterface) {
275         String name = null;
276 
277         try {
278             name = lowpanInterface.getName();
279         } catch (RemoteException | ServiceSpecificException x) {
280             // Directly fetching the name failed, so fall back to
281             // a reverse lookup.
282             synchronized (mInterfaceMap) {
283                 for (Map.Entry<String, LowpanInterfaceTracker> entry : mInterfaceMap.entrySet()) {
284                     if (entry.getValue().mILowpanInterface == lowpanInterface) {
285                         name = entry.getKey();
286                         break;
287                     }
288                 }
289             }
290         }
291 
292         removeInterfaceByName(name);
293     }
294 
addListener(ILowpanManagerListener listener)295     public void addListener(ILowpanManagerListener listener) {
296         enforceAccessPermission();
297         synchronized (mListenerSet) {
298             if (!mListenerSet.contains(listener)) {
299                 try {
300                     listener.asBinder()
301                             .linkToDeath(
302                                     new IBinder.DeathRecipient() {
303                                         @Override
304                                         public void binderDied() {
305                                             synchronized (mListenerSet) {
306                                                 mListenerSet.remove(listener);
307                                             }
308                                         }
309                                     },
310                                     0);
311                     mListenerSet.add(listener);
312                 } catch (RemoteException x) {
313                     // We only get this exception if listener has already died.
314                     Log.e(TAG, "Exception caught: " + x);
315                 }
316             }
317         }
318     }
319 
removeListener(ILowpanManagerListener listener)320     public void removeListener(ILowpanManagerListener listener) {
321         enforceAccessPermission();
322         synchronized (mListenerSet) {
323             mListenerSet.remove(listener);
324             // TODO: Shouldn't we be unlinking from the death notification?
325         }
326     }
327 }
328