summaryrefslogtreecommitdiff
path: root/service
diff options
context:
space:
mode:
authorRobert Quattlebaum <rquattle@google.com>2017-03-29 15:23:26 -0700
committerRobert Quattlebaum <rquattlebaum@nestlabs.com>2017-06-23 10:56:41 +0900
commit3c0a5570fdcc6a01e1a3a90337eebf3e4e6d19b8 (patch)
tree8079edd0458195b64d25f46ad034ddefc3283be6 /service
parent7bf3ff0542b52af0693d7c093159bcd8691a10a5 (diff)
downloadlowpan-3c0a5570fdcc6a01e1a3a90337eebf3e4e6d19b8.tar.gz
Initial commit of Android LoWPAN Manager Service
Change-Id: I5fe40aa223c15110c753fe76a670e2fe8a01c5d0 Bug: b/33073713
Diffstat (limited to 'service')
-rw-r--r--service/Android.mk29
-rw-r--r--service/java/com/android/server/lowpan/LowpanInterfaceTracker.java591
-rw-r--r--service/java/com/android/server/lowpan/LowpanService.java45
-rw-r--r--service/java/com/android/server/lowpan/LowpanServiceImpl.java275
4 files changed, 940 insertions, 0 deletions
diff --git a/service/Android.mk b/service/Android.mk
new file mode 100644
index 0000000..ff96a6c
--- /dev/null
+++ b/service/Android.mk
@@ -0,0 +1,29 @@
+#
+# 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.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := lowpan-service
+LOCAL_MODULE_TAGS :=
+LOCAL_REQUIRED_MODULES := services
+LOCAL_JAVA_LIBRARIES := services
+LOCAL_SRC_FILES += java/com/android/server/lowpan/LowpanInterfaceTracker.java
+LOCAL_SRC_FILES += java/com/android/server/lowpan/LowpanService.java
+LOCAL_SRC_FILES += java/com/android/server/lowpan/LowpanServiceImpl.java
+include $(BUILD_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/service/java/com/android/server/lowpan/LowpanInterfaceTracker.java b/service/java/com/android/server/lowpan/LowpanInterfaceTracker.java
new file mode 100644
index 0000000..32af169
--- /dev/null
+++ b/service/java/com/android/server/lowpan/LowpanInterfaceTracker.java
@@ -0,0 +1,591 @@
+/*
+ * 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.annotation.NonNull;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.NetworkAgent;
+import android.net.NetworkCapabilities;
+import android.net.NetworkFactory;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
+import android.net.lowpan.ILowpanInterface;
+import android.net.lowpan.LowpanException;
+import android.net.lowpan.LowpanInterface;
+import android.net.lowpan.LowpanProperties;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+import com.android.internal.util.HexDump;
+import com.android.internal.util.Protocol;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+import com.android.server.net.NetlinkTracker;
+
+/** Tracks connectivity of a LoWPAN interface. */
+class LowpanInterfaceTracker extends StateMachine {
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Misc Constants
+
+ /** Network type string for NetworkInfo */
+ private static final String NETWORK_TYPE = "LoWPAN";
+
+ /** Tag used for logging */
+ private static final String TAG = "LowpanInterfaceTracker";
+
+ /**
+ * Maximum network score for LoWPAN networks.
+ *
+ * <p>TODO: Research if 30 is an appropriate value.
+ */
+ private static final int NETWORK_SCORE = 30;
+
+ /** Internal debugging flag. */
+ private static final boolean DBG = true;
+
+ /** Number of state machine log records. */
+ public static final short NUM_LOG_RECS_NORMAL = 100;
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Message Code Enumeration Constants
+
+ /** The base for LoWPAN message codes */
+ static final int BASE = Protocol.BASE_LOWPAN;
+
+ static final int CMD_REGISTER = BASE + 1;
+ static final int CMD_UNREGISTER = BASE + 2;
+ static final int CMD_START_NETWORK = BASE + 3;
+ static final int CMD_STOP_NETWORK = BASE + 4;
+ static final int CMD_STATE_CHANGE = BASE + 5;
+ static final int CMD_LINK_PROPERTIES_CHANGE = BASE + 6;
+ static final int CMD_UNWANTED = BASE + 7;
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Services and interfaces
+
+ ILowpanInterface mILowpanInterface;
+ private LowpanInterface mLowpanInterface;
+ private NetworkAgent mNetworkAgent;
+ private NetworkFactory mNetworkFactory;
+ private INetworkManagementService mNmService;
+ private final NetlinkTracker mNetlinkTracker;
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Instance Variables
+
+ private String mInterfaceName;
+ private String mHwAddr;
+ private Context mContext;
+ private NetworkInfo mNetworkInfo;
+ private LinkProperties mLinkProperties;
+ private final NetworkCapabilities mNetworkCapabilities = new NetworkCapabilities();
+ private String mState = "";
+
+ ////////////////////////////////////////////////////////////////////////////
+ // State machine state instances
+
+ final DefaultState mDefaultState = new DefaultState();
+ final NormalState mNormalState = new NormalState();
+ final InitState mInitState = new InitState();
+ final OfflineState mOfflineState = new OfflineState();
+ final CommissioningState mCommissioningState = new CommissioningState();
+ final AttachingState mAttachingState = new AttachingState();
+ final AttachedState mAttachedState = new AttachedState();
+ final FaultState mFaultState = new FaultState();
+ final ConnectedState mConnectedState = new ConnectedState();
+
+ ////////////////////////////////////////////////////////////////////////////
+
+ private LocalLowpanCallback mLocalLowpanCallback = new LocalLowpanCallback();
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Misc Private Classes
+
+ private class LocalLowpanCallback extends LowpanInterface.Callback {
+ @Override
+ public void onEnabledChanged(boolean value) {}
+
+ @Override
+ public void onUpChanged(boolean value) {}
+
+ @Override
+ public void onConnectedChanged(boolean value) {}
+
+ @Override
+ public void onStateChanged(@NonNull String state) {
+ LowpanInterfaceTracker.this.sendMessage(CMD_STATE_CHANGE, state);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // State Definitions
+
+ class InitState extends State {
+ @Override
+ public void enter() {}
+
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case CMD_REGISTER:
+ if (DBG) {
+ Log.i(TAG, "CMD_REGISTER");
+ }
+ transitionTo(mDefaultState);
+ break;
+
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ @Override
+ public void exit() {}
+ }
+
+ class DefaultState extends State {
+ @Override
+ public void enter() {
+ if (DBG) {
+ Log.i(TAG, "DefaultState.enter()");
+ }
+
+ mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_NONE, 0, NETWORK_TYPE, "");
+ mNetworkInfo.setIsAvailable(true);
+
+ if (mNmService == null) {
+ IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+ mNmService = INetworkManagementService.Stub.asInterface(b);
+ }
+
+ // Start tracking interface change events.
+ try {
+ mNmService.registerObserver(mNetlinkTracker);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not register InterfaceObserver " + e);
+ }
+
+ mLowpanInterface.registerCallback(mLocalLowpanCallback);
+
+ mState = "";
+
+ sendMessage(CMD_STATE_CHANGE, mLowpanInterface.getState());
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ boolean retValue = NOT_HANDLED;
+
+ switch (message.what) {
+ case CMD_UNREGISTER:
+ transitionTo(mInitState);
+ retValue = HANDLED;
+ break;
+
+ case CMD_START_NETWORK:
+ if (DBG) {
+ Log.i(TAG, "CMD_START_NETWORK");
+ }
+ break;
+
+ case CMD_STOP_NETWORK:
+ if (DBG) {
+ Log.i(TAG, "CMD_START_NETWORK");
+ }
+ break;
+
+ case CMD_STATE_CHANGE:
+ if (!mState.equals(message.obj)) {
+ if (DBG) {
+ Log.i(
+ TAG,
+ "LowpanInterface changed state from \""
+ + mState
+ + "\" to \""
+ + message.obj.toString()
+ + "\".");
+ }
+ mState = (String) message.obj;
+ switch (mState) {
+ case LowpanInterface.STATE_OFFLINE:
+ transitionTo(mOfflineState);
+ break;
+ case LowpanInterface.STATE_COMMISSIONING:
+ transitionTo(mCommissioningState);
+ break;
+ case LowpanInterface.STATE_ATTACHING:
+ transitionTo(mAttachingState);
+ break;
+ case LowpanInterface.STATE_ATTACHED:
+ transitionTo(mConnectedState);
+ break;
+ case LowpanInterface.STATE_FAULT:
+ transitionTo(mFaultState);
+ break;
+ }
+ }
+ retValue = HANDLED;
+ break;
+ }
+ return retValue;
+ }
+
+ @Override
+ public void exit() {
+
+ try {
+ mNmService.unregisterObserver(mNetlinkTracker);
+ } catch (RemoteException x) {
+ Log.e(TAG, x.toString());
+ }
+
+ mLowpanInterface.unregisterCallback(mLocalLowpanCallback);
+ }
+ }
+
+ class NormalState extends State {
+ @Override
+ public void enter() {
+ if (DBG) {
+ Log.i(TAG, "NormalState.enter()");
+ }
+
+ if (mHwAddr == null) {
+ byte[] hwAddr = null;
+ try {
+ hwAddr = mLowpanInterface.getProperty(LowpanProperties.KEY_MAC_ADDRESS);
+ } catch (LowpanException x) {
+ Log.e(TAG, x.toString());
+ }
+
+ if (hwAddr != null) {
+ mHwAddr = HexDump.toHexString(hwAddr);
+ }
+ }
+
+ try {
+ mNmService.enableIpv6(mInterfaceName);
+ } catch (RemoteException x) {
+ Log.e(
+ TAG,
+ "Failed trying to enable IPv6 on " + mInterfaceName + ": " + x.toString());
+ }
+
+ mNetworkFactory.register();
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case CMD_UNWANTED:
+ if (mNetworkAgent == message.obj) {
+ if (DBG) {
+ Log.i(TAG, "UNWANTED.");
+ }
+ // TODO: Figure out how to properly handle this.
+ try {
+ mLowpanInterface.leave();
+ } catch (LowpanException x) {
+ Log.e(TAG, x.toString());
+ }
+ shutdownNetworkAgent();
+ }
+ break;
+
+ case CMD_LINK_PROPERTIES_CHANGE:
+ mLinkProperties = (LinkProperties) message.obj;
+ if (DBG) {
+ Log.i(TAG, "Got LinkProperties: " + mLinkProperties);
+ }
+ if (mNetworkAgent != null) {
+ mNetworkAgent.sendLinkProperties(mLinkProperties);
+ }
+ break;
+ }
+
+ return NOT_HANDLED;
+ }
+
+ @Override
+ public void exit() {
+ shutdownNetworkAgent();
+ mNetworkFactory.unregister();
+ }
+ }
+
+ class OfflineState extends State {
+ @Override
+ public void enter() {
+ shutdownNetworkAgent();
+ mNetworkInfo.setIsAvailable(true);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ return NOT_HANDLED;
+ }
+
+ @Override
+ public void exit() {}
+ }
+
+ class CommissioningState extends State {
+ @Override
+ public void enter() {}
+
+ @Override
+ public boolean processMessage(Message message) {
+ return NOT_HANDLED;
+ }
+
+ @Override
+ public void exit() {}
+ }
+
+ class AttachingState extends State {
+ @Override
+ public void enter() {
+ try {
+ mNmService.enableIpv6(mInterfaceName);
+ } catch (RemoteException x) {
+ Log.e(
+ TAG,
+ "Failed trying to enable IPv6 on " + mInterfaceName + ": " + x.toString());
+ }
+
+ mNetworkInfo.setDetailedState(DetailedState.CONNECTING, null, mHwAddr);
+ mNetworkInfo.setIsAvailable(true);
+ bringUpNetworkAgent();
+ mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ return NOT_HANDLED;
+ }
+
+ @Override
+ public void exit() {}
+ }
+
+ class AttachedState extends State {
+ @Override
+ public void enter() {
+ mNetworkInfo.setIsAvailable(true);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ switch (message.what) {
+ case CMD_STATE_CHANGE:
+ if (!mState.equals(message.obj)) {
+ if (!LowpanInterface.STATE_ATTACHED.equals(message.obj)) {
+ return NOT_HANDLED;
+ }
+ }
+ break;
+
+ default:
+ return NOT_HANDLED;
+ }
+ return HANDLED;
+ }
+
+ @Override
+ public void exit() {
+ mNetworkInfo.setIsAvailable(false);
+ }
+ }
+
+ class ConnectedState extends State {
+ @Override
+ public void enter() {
+ mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, mHwAddr);
+
+ synchronized (mNetlinkTracker) {
+ mLinkProperties = mNetlinkTracker.getLinkProperties();
+ }
+
+ bringUpNetworkAgent();
+
+ mNetworkAgent.sendLinkProperties(mLinkProperties);
+ mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+ mNetworkAgent.sendNetworkScore(NETWORK_SCORE);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ return NOT_HANDLED;
+ }
+
+ @Override
+ public void exit() {
+ if (mNetworkAgent != null) {
+ mNetworkAgent.sendNetworkScore(0);
+ }
+ }
+ }
+
+ class FaultState extends State {
+ @Override
+ public void enter() {}
+
+ @Override
+ public boolean processMessage(Message message) {
+ return NOT_HANDLED;
+ }
+
+ @Override
+ public void exit() {}
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+
+ public LowpanInterfaceTracker(
+ ILowpanInterface lowpanInterface, Context context, Looper looper) {
+ super(TAG, looper);
+
+ if (DBG) {
+ Log.i(TAG, "LowpanInterfaceTracker() begin");
+ }
+
+ setDbg(DBG);
+ setLogRecSize(NUM_LOG_RECS_NORMAL);
+ setLogOnlyTransitions(false);
+
+ mILowpanInterface = lowpanInterface;
+ mLowpanInterface = LowpanInterface.from(mILowpanInterface);
+ mContext = context;
+
+ mInterfaceName = mLowpanInterface.getName();
+
+ mLinkProperties = new LinkProperties();
+ mLinkProperties.setInterfaceName(mInterfaceName);
+
+ mNmService =
+ INetworkManagementService.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
+
+ // Initialize capabilities
+ mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_LOWPAN);
+ mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+ mNetworkCapabilities.setLinkUpstreamBandwidthKbps(100);
+ mNetworkCapabilities.setLinkDownstreamBandwidthKbps(100);
+
+ // Things don't seem to work properly without this. TODO: Investigate.
+ mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+
+ // CHECKSTYLE:OFF IndentationCheck
+ addState(mInitState);
+ addState(mDefaultState);
+ addState(mFaultState, mDefaultState);
+ addState(mNormalState, mDefaultState);
+ addState(mOfflineState, mNormalState);
+ addState(mCommissioningState, mNormalState);
+ addState(mAttachingState, mNormalState);
+ addState(mAttachedState, mNormalState);
+ addState(mConnectedState, mAttachedState);
+ // CHECKSTYLE:ON IndentationCheck
+
+ setInitialState(mInitState);
+
+ mNetworkFactory =
+ new NetworkFactory(looper, context, NETWORK_TYPE, mNetworkCapabilities) {
+ @Override
+ protected void startNetwork() {
+ LowpanInterfaceTracker.this.sendMessage(CMD_START_NETWORK);
+ }
+
+ @Override
+ protected void stopNetwork() {
+ LowpanInterfaceTracker.this.sendMessage(CMD_STOP_NETWORK);
+ }
+ };
+
+ mNetlinkTracker =
+ new NetlinkTracker(
+ mInterfaceName,
+ new NetlinkTracker.Callback() {
+ @Override
+ public void update() {
+ synchronized (mNetlinkTracker) {
+ sendMessage(
+ CMD_LINK_PROPERTIES_CHANGE,
+ mNetlinkTracker.getLinkProperties());
+ }
+ }
+ });
+
+ start();
+
+ if (DBG) {
+ Log.i(TAG, "LowpanInterfaceTracker() end");
+ }
+ }
+
+ private void shutdownNetworkAgent() {
+ mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, mHwAddr);
+ mNetworkInfo.setIsAvailable(false);
+
+ if (mNetworkAgent != null) {
+ mNetworkAgent.sendNetworkScore(0);
+ mNetworkAgent.sendNetworkInfo(mNetworkInfo);
+ }
+
+ mNetworkAgent = null;
+ }
+
+ private void bringUpNetworkAgent() {
+ if (mNetworkAgent == null) {
+ mNetworkAgent =
+ new NetworkAgent(
+ mNetworkFactory.getLooper(),
+ mContext,
+ NETWORK_TYPE,
+ mNetworkInfo,
+ mNetworkCapabilities,
+ mLinkProperties,
+ NETWORK_SCORE) {
+ public void unwanted() {
+ LowpanInterfaceTracker.this.sendMessage(CMD_UNWANTED, this);
+ };
+ };
+ }
+ }
+
+ public void register() {
+ if (DBG) {
+ Log.i(TAG, "register()");
+ }
+ sendMessage(CMD_REGISTER);
+ }
+
+ public void unregister() {
+ if (DBG) {
+ Log.i(TAG, "unregister()");
+ }
+ sendMessage(CMD_UNREGISTER);
+ }
+}
diff --git a/service/java/com/android/server/lowpan/LowpanService.java b/service/java/com/android/server/lowpan/LowpanService.java
new file mode 100644
index 0000000..910dff0
--- /dev/null
+++ b/service/java/com/android/server/lowpan/LowpanService.java
@@ -0,0 +1,45 @@
+/*
+ * 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.Context;
+import android.net.lowpan.ILowpanManager;
+import android.util.Log;
+import com.android.server.SystemService;
+
+public final class LowpanService extends SystemService {
+ private static final String TAG = LowpanService.class.getSimpleName();
+ private final LowpanServiceImpl mImpl;
+
+ public LowpanService(Context context) {
+ super(context);
+ mImpl = new LowpanServiceImpl(context);
+ }
+
+ @Override
+ public void onStart() {
+ Log.i(TAG, "Registering " + ILowpanManager.LOWPAN_SERVICE_NAME);
+ publishBinderService(ILowpanManager.LOWPAN_SERVICE_NAME, mImpl);
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+ mImpl.checkAndStartLowpan();
+ }
+ }
+}
diff --git a/service/java/com/android/server/lowpan/LowpanServiceImpl.java b/service/java/com/android/server/lowpan/LowpanServiceImpl.java
new file mode 100644
index 0000000..be3eef6
--- /dev/null
+++ b/service/java/com/android/server/lowpan/LowpanServiceImpl.java
@@ -0,0 +1,275 @@
+/*
+ * 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.Context;
+import android.net.lowpan.ILowpanInterface;
+import android.net.lowpan.ILowpanManager;
+import android.net.lowpan.ILowpanManagerListener;
+import android.os.Binder;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+
+/**
+ * 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);
+
+ public LowpanServiceImpl(Context context) {
+ mContext = context;
+ }
+
+ public Looper getLooper() {
+ Looper looper = mHandlerThread.getLooper();
+ if (looper == null) {
+ mHandlerThread.start();
+ looper = mHandlerThread.getLooper();
+ }
+
+ return looper;
+ }
+
+ public void checkAndStartLowpan() {
+ synchronized (mInterfaceMap) {
+ if (mStarted.compareAndSet(false, true)) {
+ for (Map.Entry<String, LowpanInterfaceTracker> entry : mInterfaceMap.entrySet()) {
+ entry.getValue().register();
+ }
+ }
+ }
+
+ // TODO: Bring up any daemons(like wpantund)?
+ }
+
+ private void enforceAccessPermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_LOWPAN_STATE, "LowpanService");
+ }
+
+ private void enforceManagePermission() {
+ // TODO: Change to android.Manifest.permission.MANAGE_lowpanInterfaceS
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.CHANGE_LOWPAN_STATE, "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 x) {
+ // Just skip.
+ Log.e(TAG, "Exception caught: " + x);
+ }
+ }
+ }
+ }
+
+ 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 x) {
+ // Just skip.
+ Log.e(TAG, "Exception caught: " + x);
+ }
+ }
+ }
+ }
+
+ 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 x) {
+ Log.e(TAG, "Exception caught: " + x);
+ return;
+ }
+
+ final LowpanInterfaceTracker previous;
+ final LowpanInterfaceTracker agent;
+
+ synchronized (mInterfaceMap) {
+ previous = mInterfaceMap.get(name);
+
+ agent = new LowpanInterfaceTracker(lowpanInterface, mContext, 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 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) {
+ 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?
+ }
+ }
+}