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