blob: 9450089d9cc9588d78fb9e47e9de05f34207474b [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.lowpan;
import android.content.pm.PackageManager;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.lowpan.ILowpanInterface;
import android.net.lowpan.ILowpanManager;
import android.net.lowpan.ILowpanManagerListener;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.os.Binder;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.util.Log;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* LowpanService handles remote LoWPAN operation requests by implementing the ILowpanManager
* interface.
*
* @hide
*/
public class LowpanServiceImpl extends ILowpanManager.Stub {
private static final String TAG = LowpanServiceImpl.class.getSimpleName();
private final Set<ILowpanManagerListener> mListenerSet = new HashSet<>();
private final Map<String, LowpanInterfaceTracker> mInterfaceMap = new HashMap<>();
private final Context mContext;
private final HandlerThread mHandlerThread = new HandlerThread("LowpanServiceThread");
private final AtomicBoolean mStarted = new AtomicBoolean(false);
private final boolean mIsAndroidThings;
public LowpanServiceImpl(Context context) {
mContext = context;
mIsAndroidThings = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED);
}
public Looper getLooper() {
Looper looper = mHandlerThread.getLooper();
if (looper == null) {
mHandlerThread.start();
looper = mHandlerThread.getLooper();
}
return looper;
}
public void createOutstandingNetworkRequest() {
final ConnectivityManager cm =
(ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm == null) {
throw new IllegalStateException("Bad luck, ConnectivityService not started.");
}
NetworkRequest request = new NetworkRequest.Builder()
.clearCapabilities()
.addTransportType(NetworkCapabilities.TRANSPORT_LOWPAN)
.build();
// Note that this method only ever gets called once,
// so we don't need to bother with worrying about unregistering.
cm.requestNetwork(request, new NetworkCallback());
}
public void checkAndStartLowpan() {
synchronized (mInterfaceMap) {
if (mStarted.compareAndSet(false, true)) {
for (Map.Entry<String, LowpanInterfaceTracker> entry : mInterfaceMap.entrySet()) {
entry.getValue().register();
}
}
}
createOutstandingNetworkRequest();
// TODO: Bring up any daemons(like wpantund)?
}
private void enforceAccessPermission() {
try {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.ACCESS_LOWPAN_STATE, "LowpanService");
} catch (SecurityException x) {
if (!mIsAndroidThings) {
throw x;
}
mContext.enforceCallingOrSelfPermission(
"com.google.android.things.permission.ACCESS_LOWPAN_STATE", "LowpanService");
}
}
private void enforceManagePermission() {
try {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.MANAGE_LOWPAN_INTERFACES, "LowpanService");
} catch (SecurityException x) {
if (!mIsAndroidThings) {
throw x;
}
mContext.enforceCallingOrSelfPermission(
"com.google.android.things.permission.MANAGE_LOWPAN_INTERFACES", "LowpanService");
}
}
public ILowpanInterface getInterface(String name) {
ILowpanInterface iface = null;
enforceAccessPermission();
synchronized (mInterfaceMap) {
LowpanInterfaceTracker tracker = mInterfaceMap.get(name);
if (tracker != null) {
iface = tracker.mILowpanInterface;
}
}
return iface;
}
public String[] getInterfaceList() {
enforceAccessPermission();
synchronized (mInterfaceMap) {
return mInterfaceMap.keySet().toArray(new String[mInterfaceMap.size()]);
}
}
private void onInterfaceRemoved(ILowpanInterface lowpanInterface, String name) {
Log.i(TAG, "Removed LoWPAN interface `" + name + "` (" + lowpanInterface.toString() + ")");
synchronized (mListenerSet) {
for (ILowpanManagerListener listener : mListenerSet) {
try {
listener.onInterfaceRemoved(lowpanInterface);
} catch (RemoteException | ServiceSpecificException x) {
// Don't let misbehavior of a listener
// crash the system service.
Log.e(TAG, "Exception caught: " + x);
// TODO: Consider removing the listener...?
}
}
}
}
private void onInterfaceAdded(ILowpanInterface lowpanInterface, String name) {
Log.i(TAG, "Added LoWPAN interface `" + name + "` (" + lowpanInterface.toString() + ")");
synchronized (mListenerSet) {
for (ILowpanManagerListener listener : mListenerSet) {
try {
listener.onInterfaceAdded(lowpanInterface);
} catch (RemoteException | ServiceSpecificException x) {
// Don't let misbehavior of a listener
// crash the system service.
Log.e(TAG, "Exception caught: " + x);
// TODO: Consider removing the listener...?
}
}
}
}
public void addInterface(ILowpanInterface lowpanInterface) {
enforceManagePermission();
final String name;
try {
// We allow blocking calls to get the name of the interface.
Binder.allowBlocking(lowpanInterface.asBinder());
name = lowpanInterface.getName();
lowpanInterface
.asBinder()
.linkToDeath(
new IBinder.DeathRecipient() {
@Override
public void binderDied() {
Log.w(
TAG,
"LoWPAN interface `"
+ name
+ "` ("
+ lowpanInterface.toString()
+ ") died.");
removeInterface(lowpanInterface);
}
},
0);
} catch (RemoteException | ServiceSpecificException x) {
// Don't let misbehavior of an interface
// crash the system service.
Log.e(TAG, "Exception caught: " + x);
return;
}
final LowpanInterfaceTracker previous;
final LowpanInterfaceTracker agent;
synchronized (mInterfaceMap) {
previous = mInterfaceMap.get(name);
agent = new LowpanInterfaceTracker(mContext, lowpanInterface, getLooper());
mInterfaceMap.put(name, agent);
}
if (previous != null) {
previous.unregister();
onInterfaceRemoved(previous.mILowpanInterface, name);
}
if (mStarted.get()) {
agent.register();
}
onInterfaceAdded(lowpanInterface, name);
}
private void removeInterfaceByName(String name) {
final ILowpanInterface lowpanInterface;
enforceManagePermission();
if (name == null) {
return;
}
final LowpanInterfaceTracker agent;
synchronized (mInterfaceMap) {
agent = mInterfaceMap.get(name);
if (agent == null) {
return;
}
lowpanInterface = agent.mILowpanInterface;
if (mStarted.get()) {
agent.unregister();
}
mInterfaceMap.remove(name);
}
onInterfaceRemoved(lowpanInterface, name);
}
public void removeInterface(ILowpanInterface lowpanInterface) {
String name = null;
try {
name = lowpanInterface.getName();
} catch (RemoteException | ServiceSpecificException x) {
// Directly fetching the name failed, so fall back to
// a reverse lookup.
synchronized (mInterfaceMap) {
for (Map.Entry<String, LowpanInterfaceTracker> entry : mInterfaceMap.entrySet()) {
if (entry.getValue().mILowpanInterface == lowpanInterface) {
name = entry.getKey();
break;
}
}
}
}
removeInterfaceByName(name);
}
public void addListener(ILowpanManagerListener listener) {
enforceAccessPermission();
synchronized (mListenerSet) {
if (!mListenerSet.contains(listener)) {
try {
listener.asBinder()
.linkToDeath(
new IBinder.DeathRecipient() {
@Override
public void binderDied() {
synchronized (mListenerSet) {
mListenerSet.remove(listener);
}
}
},
0);
mListenerSet.add(listener);
} catch (RemoteException x) {
// We only get this exception if listener has already died.
Log.e(TAG, "Exception caught: " + x);
}
}
}
}
public void removeListener(ILowpanManagerListener listener) {
enforceAccessPermission();
synchronized (mListenerSet) {
mListenerSet.remove(listener);
// TODO: Shouldn't we be unlinking from the death notification?
}
}
}