Initial support for proposed android.net.lowpan

Change-Id: I0bf8edb5199d50d2a549a353b2785aef8134ff92
diff --git a/Android.mk b/Android.mk
index 28abb89..91b835d 100644
--- a/Android.mk
+++ b/Android.mk
@@ -558,6 +558,15 @@
 
 LOCAL_AIDL_INCLUDES += system/update_engine/binder_bindings
 
+LOCAL_AIDL_INCLUDES += frameworks/base/lowpan/java
+LOCAL_SRC_FILES += \
+	lowpan/java/android/net/lowpan/ILowpanEnergyScanCallback.aidl \
+	lowpan/java/android/net/lowpan/ILowpanNetScanCallback.aidl \
+	lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl \
+	lowpan/java/android/net/lowpan/ILowpanInterface.aidl \
+	lowpan/java/android/net/lowpan/ILowpanManagerListener.aidl \
+	lowpan/java/android/net/lowpan/ILowpanManager.aidl
+
 # FRAMEWORKS_BASE_JAVA_SRC_DIRS comes from build/core/pathmap.mk
 LOCAL_AIDL_INCLUDES += \
       $(FRAMEWORKS_BASE_JAVA_SRC_DIRS) \
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e39cb70..ed344b5 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1322,6 +1322,27 @@
     <permission android:name="android.permission.NETWORK_SETTINGS"
         android:protectionLevel="signature" />
 
+    <!-- #SystemApi @hide Allows applications to access information about LoWPAN interfaces.
+         <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.ACCESS_LOWPAN_STATE"
+        android:protectionLevel="signature|privileged" />
+
+    <!-- #SystemApi @hide Allows applications to change LoWPAN connectivity state.
+         <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.CHANGE_LOWPAN_STATE"
+        android:protectionLevel="signature|privileged" />
+
+    <!-- #SystemApi @hide Allows applications to read LoWPAN credential.
+         <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.READ_LOWPAN_CREDENTIAL"
+        android:protectionLevel="signature|privileged" />
+
+    <!-- #SystemApi @hide Allows a service to register or unregister
+         new LoWPAN interfaces.
+         <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.MANAGE_LOWPAN_INTERFACES"
+        android:protectionLevel="signature|privileged" />
+
     <!-- ======================================= -->
     <!-- Permissions for short range, peripheral networks -->
     <!-- ======================================= -->
diff --git a/lowpan/Android.mk b/lowpan/Android.mk
new file mode 100644
index 0000000..9e9164f
--- /dev/null
+++ b/lowpan/Android.mk
@@ -0,0 +1,31 @@
+#
+# 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)
+
+ifneq (,$(findstring lowpan/java,$(FRAMEWORKS_BASE_SUBDIRS)))
+include $(CLEAR_VARS)
+LOCAL_MODULE := libandroid_net_lowpan
+LOCAL_MODULE_TAGS := optional
+LOCAL_SHARED_LIBRARIES += libbase
+LOCAL_SHARED_LIBRARIES += libbinder
+LOCAL_SHARED_LIBRARIES += libutils
+LOCAL_AIDL_INCLUDES += frameworks/native/aidl/binder
+LOCAL_AIDL_INCLUDES += frameworks/base/lowpan/java
+LOCAL_AIDL_INCLUDES += frameworks/base/core/java
+LOCAL_SRC_FILES += $(call all-Iaidl-files-under, java/android/net/lowpan)
+include $(BUILD_SHARED_LIBRARY)
+endif
diff --git a/lowpan/java/android/net/lowpan/ILowpanEnergyScanCallback.aidl b/lowpan/java/android/net/lowpan/ILowpanEnergyScanCallback.aidl
new file mode 100644
index 0000000..f09dbe3
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/ILowpanEnergyScanCallback.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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 android.net.lowpan;
+
+/** {@hide} */
+interface ILowpanEnergyScanCallback {
+    oneway void onEnergyScanResult(int channel, int rssi);
+    oneway void onEnergyScanFinished();
+}
diff --git a/lowpan/java/android/net/lowpan/ILowpanInterface.aidl b/lowpan/java/android/net/lowpan/ILowpanInterface.aidl
new file mode 100644
index 0000000..647fcc1
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/ILowpanInterface.aidl
@@ -0,0 +1,154 @@
+/*
+ * 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 android.net.lowpan;
+
+import android.net.lowpan.ILowpanInterfaceListener;
+import android.net.lowpan.ILowpanNetScanCallback;
+import android.net.lowpan.ILowpanEnergyScanCallback;
+import android.os.PersistableBundle;
+import android.net.IpPrefix;
+
+/** {@hide} */
+interface ILowpanInterface {
+
+    //////////////////////////////////////////////////////////////////////////
+    // Permission String Constants
+
+    /* These are here for the sake of C++ interface implementations. */
+
+    const String PERM_ACCESS_LOWPAN_STATE    = "android.permission.ACCESS_LOWPAN_STATE";
+    const String PERM_CHANGE_LOWPAN_STATE    = "android.permission.CHANGE_LOWPAN_STATE";
+    const String PERM_READ_LOWPAN_CREDENTIAL = "android.permission.READ_LOWPAN_CREDENTIAL";
+
+    //////////////////////////////////////////////////////////////////////////
+    // Property Key Constants
+
+    const String KEY_INTERFACE_ENABLED      = "android.net.lowpan.property.INTERFACE_ENABLED";
+    const String KEY_INTERFACE_UP           = "android.net.lowpan.property.INTERFACE_UP";
+    const String KEY_INTERFACE_COMMISSIONED = "android.net.lowpan.property.INTERFACE_COMMISSIONED";
+    const String KEY_INTERFACE_CONNECTED    = "android.net.lowpan.property.INTERFACE_CONNECTED";
+    const String KEY_INTERFACE_STATE        = "android.net.lowpan.property.INTERFACE_STATE";
+
+    const String KEY_NETWORK_NAME             = "android.net.lowpan.property.NETWORK_NAME";
+    const String KEY_NETWORK_TYPE             = "android.net.lowpan.property.NETWORK_TYPE";
+    const String KEY_NETWORK_PANID            = "android.net.lowpan.property.NETWORK_PANID";
+    const String KEY_NETWORK_XPANID           = "android.net.lowpan.property.NETWORK_XPANID";
+    const String KEY_NETWORK_ROLE             = "android.net.lowpan.property.NETWORK_ROLE";
+    const String KEY_NETWORK_MASTER_KEY       = "android.net.lowpan.property.NETWORK_MASTER_KEY";
+    const String KEY_NETWORK_MASTER_KEY_INDEX
+        = "android.net.lowpan.property.NETWORK_MASTER_KEY_INDEX";
+
+    const String KEY_SUPPORTED_CHANNELS = "android.net.lowpan.property.SUPPORTED_CHANNELS";
+    const String KEY_CHANNEL            = "android.net.lowpan.property.CHANNEL";
+    const String KEY_CHANNEL_MASK       = "android.net.lowpan.property.CHANNEL_MASK";
+    const String KEY_MAX_TX_POWER       = "android.net.lowpan.property.MAX_TX_POWER";
+    const String KEY_RSSI               = "android.net.lowpan.property.RSSI";
+    const String KEY_LQI                = "android.net.lowpan.property.LQI";
+
+    const String KEY_LINK_ADDRESS_ARRAY = "android.net.lowpan.property.LINK_ADDRESS_ARRAY";
+    const String KEY_ROUTE_INFO_ARRAY   = "android.net.lowpan.property.ROUTE_INFO_ARRAY";
+
+    const String KEY_BEACON_ADDRESS     = "android.net.lowpan.property.BEACON_ORIGIN_ADDRESS";
+    const String KEY_BEACON_CAN_ASSIST  = "android.net.lowpan.property.BEACON_CAN_ASSIST";
+
+    const String DRIVER_VERSION         = "android.net.lowpan.property.DRIVER_VERSION";
+    const String NCP_VERSION            = "android.net.lowpan.property.NCP_VERSION";
+
+    /** @hide */
+    const String KEY_EXTENDED_ADDRESS = "android.net.lowpan.property.EXTENDED_ADDRESS";
+
+    /** @hide */
+    const String KEY_MAC_ADDRESS      = "android.net.lowpan.property.MAC_ADDRESS";
+
+    //////////////////////////////////////////////////////////////////////////
+    // Interface States
+
+    const String STATE_OFFLINE = "offline";
+    const String STATE_COMMISSIONING = "commissioning";
+    const String STATE_ATTACHING = "attaching";
+    const String STATE_ATTACHED = "attached";
+    const String STATE_FAULT = "fault";
+
+    //////////////////////////////////////////////////////////////////////////
+    // Device Roles
+
+    const String ROLE_END_DEVICE = "end-device";
+    const String ROLE_ROUTER = "router";
+    const String ROLE_SLEEPY_END_DEVICE = "sleepy-end-device";
+    const String ROLE_SLEEPY_ROUTER = "sleepy-router";
+    const String ROLE_UNKNOWN = "unknown";
+
+    //////////////////////////////////////////////////////////////////////////
+    // Service-Specific Error Code Constants
+
+    const int ERROR_UNSPECIFIED = 1;
+    const int ERROR_INVALID_ARGUMENT = 2;
+    const int ERROR_DISABLED = 3;
+    const int ERROR_WRONG_STATE = 4;
+    const int ERROR_INVALID_TYPE = 5;
+    const int ERROR_INVALID_VALUE = 6;
+    const int ERROR_TIMEOUT = 7;
+    const int ERROR_IO_FAILURE = 8;
+    const int ERROR_BUSY = 9;
+    const int ERROR_ALREADY = 10;
+    const int ERROR_CANCELED = 11;
+    const int ERROR_CREDENTIAL_NEEDED = 12;
+    const int ERROR_FEATURE_NOT_SUPPORTED = 14;
+    const int ERROR_PROPERTY_NOT_FOUND = 16;
+    const int ERROR_JOIN_FAILED_UNKNOWN = 18;
+    const int ERROR_JOIN_FAILED_AT_SCAN = 19;
+    const int ERROR_JOIN_FAILED_AT_AUTH = 20;
+    const int ERROR_FORM_FAILED_AT_SCAN = 21;
+    const int ERROR_NCP_PROBLEM = 27;
+    const int ERROR_PERMISSION_DENIED = 28;
+
+    //////////////////////////////////////////////////////////////////////////
+    // Methods
+
+    @utf8InCpp String getName();
+
+    void join(in Map parameters);
+    void form(in Map parameters);
+    void leave();
+    void reset();
+
+    void beginLowPower();
+    void pollForData();
+
+    oneway void onHostWake();
+
+    @utf8InCpp String[] getPropertyKeys();
+    Map getProperties(in @utf8InCpp String[] keys);
+    void setProperties(in Map properties);
+
+    void addListener(ILowpanInterfaceListener listener);
+    oneway void removeListener(ILowpanInterfaceListener listener);
+
+    void startNetScan(in Map properties, ILowpanNetScanCallback listener);
+    oneway void stopNetScan();
+
+    void startEnergyScan(in Map properties, ILowpanEnergyScanCallback listener);
+    oneway void stopEnergyScan();
+
+    void addOnMeshPrefix(in IpPrefix prefix, int flags);
+    oneway void removeOnMeshPrefix(in IpPrefix prefix);
+
+    void addExternalRoute(in IpPrefix prefix, int flags);
+    oneway void removeExternalRoute(in IpPrefix prefix);
+
+    @utf8InCpp String getPropertyAsString(@utf8InCpp String key);
+}
diff --git a/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl b/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl
new file mode 100644
index 0000000..c99d732
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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 android.net.lowpan;
+
+/** {@hide} */
+interface ILowpanInterfaceListener {
+    oneway void onPropertiesChanged(in Map properties);
+}
diff --git a/lowpan/java/android/net/lowpan/ILowpanManager.aidl b/lowpan/java/android/net/lowpan/ILowpanManager.aidl
new file mode 100644
index 0000000..5a8d7dc
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/ILowpanManager.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 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 android.net.lowpan;
+import android.net.lowpan.ILowpanInterface;
+import android.net.lowpan.ILowpanManagerListener;
+
+/** {@hide} */
+interface ILowpanManager {
+
+    const String LOWPAN_SERVICE_NAME = "lowpan";
+
+    ILowpanInterface getInterface(@utf8InCpp String name);
+
+    @utf8InCpp String[] getInterfaceList();
+
+    void addListener(ILowpanManagerListener listener);
+    void removeListener(ILowpanManagerListener listener);
+
+    void addInterface(ILowpanInterface lowpan_interface);
+    void removeInterface(ILowpanInterface lowpan_interface);
+}
diff --git a/lowpan/java/android/net/lowpan/ILowpanManagerListener.aidl b/lowpan/java/android/net/lowpan/ILowpanManagerListener.aidl
new file mode 100644
index 0000000..d4846f6
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/ILowpanManagerListener.aidl
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 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 android.net.lowpan;
+
+import android.net.lowpan.ILowpanInterface;
+
+/** {@hide} */
+interface ILowpanManagerListener {
+    oneway void onInterfaceAdded(ILowpanInterface lowpanInterface);
+    oneway void onInterfaceRemoved(ILowpanInterface lowpanInterface);
+}
diff --git a/lowpan/java/android/net/lowpan/ILowpanNetScanCallback.aidl b/lowpan/java/android/net/lowpan/ILowpanNetScanCallback.aidl
new file mode 100644
index 0000000..c20a6f8
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/ILowpanNetScanCallback.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 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 android.net.lowpan;
+
+/** {@hide} */
+interface ILowpanNetScanCallback {
+    oneway void onNetScanBeacon(in Map parameters);
+    oneway void onNetScanFinished();
+}
diff --git a/lowpan/java/android/net/lowpan/LowpanBeaconInfo.java b/lowpan/java/android/net/lowpan/LowpanBeaconInfo.java
new file mode 100644
index 0000000..b344527
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanBeaconInfo.java
@@ -0,0 +1,150 @@
+/*
+ * 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 android.net.lowpan;
+
+import com.android.internal.util.HexDump;
+import java.util.Collection;
+import java.util.Map;
+import java.util.TreeSet;
+
+/**
+ * Describes a LoWPAN Beacon
+ *
+ * @hide
+ */
+//@SystemApi
+public class LowpanBeaconInfo extends LowpanIdentity {
+
+    private int mRssi = UNKNOWN;
+    private int mLqi = UNKNOWN;
+    private byte[] mBeaconAddress = null;
+    private final TreeSet<Integer> mFlags = new TreeSet<>();
+
+    public static final int FLAG_CAN_ASSIST = 1;
+
+    static class Builder extends LowpanIdentity.Builder {
+        private final LowpanBeaconInfo identity = new LowpanBeaconInfo();
+
+        public Builder setRssi(int x) {
+            identity.mRssi = x;
+            return this;
+        }
+
+        public Builder setLqi(int x) {
+            identity.mLqi = x;
+            return this;
+        }
+
+        public Builder setBeaconAddress(byte x[]) {
+            identity.mBeaconAddress = x.clone();
+            return this;
+        }
+
+        public Builder setFlag(int x) {
+            identity.mFlags.add(x);
+            return this;
+        }
+
+        public Builder setFlags(Collection<Integer> x) {
+            identity.mFlags.addAll(x);
+            return this;
+        }
+
+        /** @hide */
+        Builder updateFromMap(Map map) {
+            if (map.containsKey(LowpanProperties.KEY_RSSI.getName())) {
+                setRssi(LowpanProperties.KEY_RSSI.getFromMap(map));
+            }
+            if (map.containsKey(LowpanProperties.KEY_LQI.getName())) {
+                setLqi(LowpanProperties.KEY_LQI.getFromMap(map));
+            }
+            if (map.containsKey(LowpanProperties.KEY_BEACON_ADDRESS.getName())) {
+                setBeaconAddress(LowpanProperties.KEY_BEACON_ADDRESS.getFromMap(map));
+            }
+            identity.mFlags.clear();
+            if (map.containsKey(LowpanProperties.KEY_BEACON_CAN_ASSIST.getName())
+                    && LowpanProperties.KEY_BEACON_CAN_ASSIST.getFromMap(map).booleanValue()) {
+                setFlag(FLAG_CAN_ASSIST);
+            }
+            super.updateFromMap(map);
+            return this;
+        }
+
+        public LowpanBeaconInfo build() {
+            return identity;
+        }
+    }
+
+    private LowpanBeaconInfo() {}
+
+    public int getRssi() {
+        return mRssi;
+    }
+
+    public int getLqi() {
+        return mLqi;
+    }
+
+    public byte[] getBeaconAddress() {
+        return mBeaconAddress.clone();
+    }
+
+    public Collection<Integer> getFlags() {
+        return mFlags.clone();
+    }
+
+    public boolean isFlagSet(int flag) {
+        return mFlags.contains(flag);
+    }
+
+    @Override
+    void addToMap(Map<String, Object> parameters) {
+        super.addToMap(parameters);
+    }
+
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+
+        sb.append(super.toString());
+
+        if (mRssi != UNKNOWN) {
+            sb.append(", RSSI: ").append(mRssi);
+        }
+
+        if (mLqi != UNKNOWN) {
+            sb.append(", LQI: ").append(mLqi);
+        }
+
+        if (mBeaconAddress != null) {
+            sb.append(", BeaconAddress: ").append(HexDump.toHexString(mBeaconAddress));
+        }
+
+        for (Integer flag : mFlags) {
+            switch (flag.intValue()) {
+                case FLAG_CAN_ASSIST:
+                    sb.append(", CAN_ASSIST");
+                    break;
+                default:
+                    sb.append(", FLAG_").append(Integer.toHexString(flag));
+                    break;
+            }
+        }
+
+        return sb.toString();
+    }
+}
diff --git a/lowpan/java/android/net/lowpan/LowpanChannelInfo.java b/lowpan/java/android/net/lowpan/LowpanChannelInfo.java
new file mode 100644
index 0000000..50afe6d
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanChannelInfo.java
@@ -0,0 +1,87 @@
+/*
+ * 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 android.net.lowpan;
+
+
+/** Provides detailed information about a given channel. */
+//@SystemApi
+public class LowpanChannelInfo {
+
+    public static final int UNKNOWN_POWER = Integer.MAX_VALUE;
+
+    //////////////////////////////////////////////////////////////////////////
+    // Instance Variables
+
+    private String mName = null;
+    private int mIndex = 0;
+    private boolean mIsMaskedByRegulatoryDomain = false;
+    private float mSpectrumCenterFrequency = 0.0f;
+    private float mSpectrumBandwidth = 0.0f;
+    private int mMaxTransmitPower = UNKNOWN_POWER;
+
+    //////////////////////////////////////////////////////////////////////////
+    // Public Getters and Setters
+
+    public String getName() {
+        return mName;
+    }
+
+    public int getIndex() {
+        return mIndex;
+    }
+
+    public int getMaxTransmitPower() {
+        return mMaxTransmitPower;
+    }
+
+    public boolean isMaskedByRegulatoryDomain() {
+        return mIsMaskedByRegulatoryDomain;
+    }
+
+    public float getSpectrumCenterFrequency() {
+        return mSpectrumCenterFrequency;
+    }
+
+    public float getSpectrumBandwidth() {
+        return mSpectrumBandwidth;
+    }
+
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+
+        sb.append("Channel ").append(mIndex);
+
+        if (mName != null) {
+            sb.append(" (").append(mName).append(")");
+        }
+
+        if (mSpectrumCenterFrequency > 0.0f) {
+            sb.append(", SpectrumCenterFrequency: ").append(mSpectrumCenterFrequency).append("Hz");
+        }
+
+        if (mSpectrumBandwidth > 0.0f) {
+            sb.append(", SpectrumBandwidth: ").append(mSpectrumBandwidth).append("Hz");
+        }
+
+        if (mMaxTransmitPower != UNKNOWN_POWER) {
+            sb.append(", MaxTransmitPower: ").append(mMaxTransmitPower);
+        }
+
+        return sb.toString();
+    }
+}
diff --git a/lowpan/java/android/net/lowpan/LowpanCommissioningSession.java b/lowpan/java/android/net/lowpan/LowpanCommissioningSession.java
new file mode 100644
index 0000000..9cad00c
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanCommissioningSession.java
@@ -0,0 +1,73 @@
+/*
+ * 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 android.net.lowpan;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Network;
+import android.os.Handler;
+import java.net.InetSocketAddress;
+
+/**
+ * Commissioning Session.
+ *
+ * <p>This class enables a device to learn the credential needed to join a network using a technique
+ * called "in-band commissioning".
+ *
+ * @hide
+ */
+//@SystemApi
+public abstract class LowpanCommissioningSession {
+    public LowpanCommissioningSession() {}
+
+    /**
+     * Callback base class for {@link LowpanCommissioningSession}
+     *
+     * @hide
+     */
+    //@SystemApi
+    public class Callback {
+        public void onReceiveFromCommissioner(@NonNull byte[] packet) {};
+
+        public void onClosed() {};
+    }
+
+    /** TODO: doc */
+    @NonNull
+    public abstract LowpanBeaconInfo getBeaconInfo();
+
+    /** TODO: doc */
+    public abstract void sendToCommissioner(@NonNull byte[] packet);
+
+    /** TODO: doc */
+    public abstract void setCallback(@Nullable Callback cb, @Nullable Handler handler);
+
+    /** TODO: doc */
+    public abstract void close();
+
+    /**
+     * This method is largely for Nest Weave, as an alternative to {@link #sendToCommissioner()}
+     * and @{link Callback#onReceiveFromCommissioner()}.
+     *
+     * <p>When used with the Network instance obtained from getNetwork(), the caller can use the
+     * given InetSocketAddress to communicate with the commissioner using a UDP (or, under certain
+     * circumstances, TCP) socket.
+     */
+    public abstract @Nullable InetSocketAddress getInetSocketAddress();
+
+    public abstract @Nullable Network getNetwork();
+}
diff --git a/lowpan/java/android/net/lowpan/LowpanCredential.java b/lowpan/java/android/net/lowpan/LowpanCredential.java
new file mode 100644
index 0000000..dea4d78
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanCredential.java
@@ -0,0 +1,93 @@
+/*
+ * 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 android.net.lowpan;
+
+
+import java.util.Map;
+
+/**
+ * Describes a credential for a LoWPAN network.
+ *
+ * @hide
+ */
+//@SystemApi
+public class LowpanCredential {
+
+    public static final int UNSPECIFIED_KEY_INDEX = 0;
+
+    private byte[] mMasterKey = null;
+    private int mMasterKeyIndex = UNSPECIFIED_KEY_INDEX;
+
+    LowpanCredential() {}
+
+    private LowpanCredential(byte[] masterKey, int keyIndex) {
+        setMasterKey(masterKey, keyIndex);
+    }
+
+    private LowpanCredential(byte[] masterKey) {
+        setMasterKey(masterKey);
+    }
+
+    public static LowpanCredential createMasterKey(byte[] masterKey) {
+        return new LowpanCredential(masterKey);
+    }
+
+    public static LowpanCredential createMasterKey(byte[] masterKey, int keyIndex) {
+        return new LowpanCredential(masterKey, keyIndex);
+    }
+
+    public void setMasterKey(byte[] masterKey) {
+        if (masterKey != null) {
+            masterKey = masterKey.clone();
+        }
+        mMasterKey = masterKey;
+    }
+
+    public void setMasterKeyIndex(int keyIndex) {
+        mMasterKeyIndex = keyIndex;
+    }
+
+    public void setMasterKey(byte[] masterKey, int keyIndex) {
+        setMasterKey(masterKey);
+        setMasterKeyIndex(keyIndex);
+    }
+
+    public byte[] getMasterKey() {
+        if (mMasterKey != null) {
+            return mMasterKey.clone();
+        }
+        return null;
+    }
+
+    public int getMasterKeyIndex() {
+        return mMasterKeyIndex;
+    }
+
+    public boolean isMasterKey() {
+        return mMasterKey != null;
+    }
+
+    void addToMap(Map<String, Object> parameters) throws LowpanException {
+        if (isMasterKey()) {
+            LowpanProperties.KEY_NETWORK_MASTER_KEY.putInMap(parameters, getMasterKey());
+            LowpanProperties.KEY_NETWORK_MASTER_KEY_INDEX.putInMap(
+                    parameters, getMasterKeyIndex());
+        } else {
+            throw new LowpanException("Unsupported Network Credential");
+        }
+    }
+}
diff --git a/lowpan/java/android/net/lowpan/LowpanEnergyScanResult.java b/lowpan/java/android/net/lowpan/LowpanEnergyScanResult.java
new file mode 100644
index 0000000..c680687
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanEnergyScanResult.java
@@ -0,0 +1,54 @@
+/*
+ * 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 android.net.lowpan;
+
+
+/**
+ * Describes the result from one channel of an energy scan.
+ *
+ * @hide
+ */
+//@SystemApi
+public class LowpanEnergyScanResult {
+    public static final int UNKNOWN = Integer.MAX_VALUE;
+
+    private int mChannel = UNKNOWN;
+    private int mMaxRssi = UNKNOWN;
+
+    public LowpanEnergyScanResult() {}
+
+    public int getChannel() {
+        return mChannel;
+    }
+
+    public int getMaxRssi() {
+        return mMaxRssi;
+    }
+
+    public void setChannel(int x) {
+        mChannel = x;
+    }
+
+    public void setMaxRssi(int x) {
+        mMaxRssi = x;
+    }
+
+    @Override
+    public String toString() {
+        return "LowpanEnergyScanResult(channel: " + mChannel + ", maxRssi:" + mMaxRssi + ")";
+    }
+}
diff --git a/lowpan/java/android/net/lowpan/LowpanException.java b/lowpan/java/android/net/lowpan/LowpanException.java
new file mode 100644
index 0000000..8ff37f9
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanException.java
@@ -0,0 +1,290 @@
+/*
+ * 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 android.net.lowpan;
+
+import android.os.DeadObjectException;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.AndroidException;
+
+/**
+ * <code>LowpanException</code> is thrown if an action to a LoWPAN interface could not be performed
+ * or a LoWPAN interface property could not be fetched or changed.
+ *
+ * @see LowpanInterface
+ * @hide
+ */
+//@SystemApi
+public class LowpanException extends AndroidException {
+    // Make the eclipse warning about serializable exceptions go away
+    private static final long serialVersionUID = 0x31863cbe562b0e11l; // randomly generated
+
+    public static final int LOWPAN_ERROR = 1;
+    public static final int LOWPAN_CREDENTIAL_NEEDED = 2;
+    public static final int LOWPAN_DEAD = 3;
+    public static final int LOWPAN_DISABLED = 4;
+    public static final int LOWPAN_WRONG_STATE = 5;
+    public static final int LOWPAN_BUSY = 7;
+    public static final int LOWPAN_NCP_PROBLEM = 8;
+    public static final int LOWPAN_ALREADY = 9;
+    public static final int LOWPAN_CANCELED = 10;
+    public static final int LOWPAN_FEATURE_NOT_SUPPORTED = 12;
+    public static final int LOWPAN_PROPERTY_NOT_FOUND = 13;
+    public static final int LOWPAN_JOIN_FAILED_UNKNOWN = 14;
+    public static final int LOWPAN_JOIN_FAILED_AT_SCAN = 15;
+    public static final int LOWPAN_JOIN_FAILED_AT_AUTH = 16;
+    public static final int LOWPAN_FORM_FAILED_AT_SCAN = 17;
+
+    /**
+     * Convert ServiceSpecificExceptions and Binder RemoteExceptions from LoWPAN binder interfaces
+     * into the correct public exceptions.
+     *
+     * @hide
+     */
+    public static void throwAsPublicException(Throwable t) throws LowpanException {
+        if (t instanceof ServiceSpecificException) {
+            ServiceSpecificException e = (ServiceSpecificException) t;
+            int reason;
+            switch (e.errorCode) {
+                case ILowpanInterface.ERROR_INVALID_ARGUMENT:
+                case ILowpanInterface.ERROR_INVALID_TYPE:
+                case ILowpanInterface.ERROR_INVALID_VALUE:
+                    throw new IllegalArgumentException(e.getMessage(), e);
+
+                case ILowpanInterface.ERROR_PERMISSION_DENIED:
+                    throw new SecurityException(e.getMessage(), e);
+
+                case ILowpanInterface.ERROR_DISABLED:
+                    reason = LowpanException.LOWPAN_DISABLED;
+                    break;
+
+                case ILowpanInterface.ERROR_WRONG_STATE:
+                    reason = LowpanException.LOWPAN_WRONG_STATE;
+                    break;
+
+                case ILowpanInterface.ERROR_BUSY:
+                    reason = LowpanException.LOWPAN_BUSY;
+                    break;
+
+                case ILowpanInterface.ERROR_ALREADY:
+                    reason = LowpanException.LOWPAN_ALREADY;
+                    break;
+
+                case ILowpanInterface.ERROR_CANCELED:
+                    reason = LowpanException.LOWPAN_CANCELED;
+                    break;
+
+                case ILowpanInterface.ERROR_CREDENTIAL_NEEDED:
+                    reason = LowpanException.LOWPAN_CREDENTIAL_NEEDED;
+                    break;
+
+                case ILowpanInterface.ERROR_FEATURE_NOT_SUPPORTED:
+                    reason = LowpanException.LOWPAN_FEATURE_NOT_SUPPORTED;
+                    break;
+
+                case ILowpanInterface.ERROR_PROPERTY_NOT_FOUND:
+                    reason = LowpanException.LOWPAN_PROPERTY_NOT_FOUND;
+                    break;
+
+                case ILowpanInterface.ERROR_JOIN_FAILED_UNKNOWN:
+                    reason = LowpanException.LOWPAN_JOIN_FAILED_UNKNOWN;
+                    break;
+
+                case ILowpanInterface.ERROR_JOIN_FAILED_AT_SCAN:
+                    reason = LowpanException.LOWPAN_JOIN_FAILED_AT_SCAN;
+                    break;
+
+                case ILowpanInterface.ERROR_JOIN_FAILED_AT_AUTH:
+                    reason = LowpanException.LOWPAN_JOIN_FAILED_AT_AUTH;
+                    break;
+
+                case ILowpanInterface.ERROR_FORM_FAILED_AT_SCAN:
+                    reason = LowpanException.LOWPAN_FORM_FAILED_AT_SCAN;
+                    break;
+
+                case ILowpanInterface.ERROR_TIMEOUT:
+                case ILowpanInterface.ERROR_NCP_PROBLEM:
+                    reason = LowpanException.LOWPAN_NCP_PROBLEM;
+                    break;
+                case ILowpanInterface.ERROR_UNSPECIFIED:
+                default:
+                    reason = LOWPAN_ERROR;
+                    break;
+            }
+            throw new LowpanException(reason, e.getMessage(), e);
+        } else if (t instanceof DeadObjectException) {
+            throw new LowpanException(LOWPAN_DEAD, t);
+        } else if (t instanceof RemoteException) {
+            throw new UnsupportedOperationException(
+                    "An unknown RemoteException was thrown" + " which should never happen.", t);
+        } else if (t instanceof RuntimeException) {
+            RuntimeException e = (RuntimeException) t;
+            throw e;
+        }
+    }
+
+    private final int mReason;
+
+    public final int getReason() {
+        return mReason;
+    }
+
+    public LowpanException(int problem) {
+        super(getDefaultMessage(problem));
+        mReason = problem;
+    }
+
+    public LowpanException(String message) {
+        super(getCombinedMessage(LOWPAN_ERROR, message));
+        mReason = LOWPAN_ERROR;
+    }
+
+    public LowpanException(int problem, String message, Throwable cause) {
+        super(getCombinedMessage(problem, message), cause);
+        mReason = problem;
+    }
+
+    public LowpanException(int problem, Throwable cause) {
+        super(getDefaultMessage(problem), cause);
+        mReason = problem;
+    }
+
+    /** @hide */
+    public static String getDefaultMessage(int problem) {
+        String problemString;
+
+        // TODO: Does this need localization?
+
+        switch (problem) {
+            case LOWPAN_DEAD:
+                problemString = "LoWPAN interface is no longer alive";
+                break;
+            case LOWPAN_DISABLED:
+                problemString = "LoWPAN interface is disabled";
+                break;
+            case LOWPAN_WRONG_STATE:
+                problemString = "LoWPAN interface in wrong state to perfom requested action";
+                break;
+            case LOWPAN_BUSY:
+                problemString =
+                        "LoWPAN interface was unable to perform the requestion action because it was busy";
+                break;
+            case LOWPAN_NCP_PROBLEM:
+                problemString =
+                        "The Network Co-Processor associated with this interface has experienced a problem";
+                break;
+            case LOWPAN_ALREADY:
+                problemString = "The LoWPAN interface is already in the given state";
+                break;
+            case LOWPAN_CANCELED:
+                problemString = "This operation was canceled";
+                break;
+            case LOWPAN_CREDENTIAL_NEEDED:
+                problemString = "Additional credentials are required to complete this operation";
+                break;
+            case LOWPAN_FEATURE_NOT_SUPPORTED:
+                problemString =
+                        "A dependent feature required to perform the given action is not currently supported";
+                break;
+            case LOWPAN_PROPERTY_NOT_FOUND:
+                problemString = "The given property was not found";
+                break;
+            case LOWPAN_JOIN_FAILED_UNKNOWN:
+                problemString = "The join operation failed for an unspecified reason";
+                break;
+            case LOWPAN_JOIN_FAILED_AT_SCAN:
+                problemString =
+                        "The join operation failed because it could not communicate with any peers";
+                break;
+            case LOWPAN_JOIN_FAILED_AT_AUTH:
+                problemString =
+                        "The join operation failed because the credentials were not accepted by any peers";
+                break;
+            case LOWPAN_FORM_FAILED_AT_SCAN:
+                problemString = "Network form failed";
+                break;
+            case LOWPAN_ERROR:
+            default:
+                problemString = "The requested LoWPAN operation failed";
+                break;
+        }
+
+        return problemString;
+    }
+
+    private static String getCombinedMessage(int problem, String message) {
+        String problemString = getProblemString(problem);
+        return String.format("%s (%d): %s", problemString, problem, message);
+    }
+
+    private static String getProblemString(int problem) {
+        String problemString;
+
+        switch (problem) {
+            case LOWPAN_ERROR:
+                problemString = "LOWPAN_ERROR";
+                break;
+            case LOWPAN_DEAD:
+                problemString = "LOWPAN_DEAD";
+                break;
+            case LOWPAN_DISABLED:
+                problemString = "LOWPAN_DISABLED";
+                break;
+            case LOWPAN_WRONG_STATE:
+                problemString = "LOWPAN_WRONG_STATE";
+                break;
+            case LOWPAN_BUSY:
+                problemString = "LOWPAN_BUSY";
+                break;
+            case LOWPAN_NCP_PROBLEM:
+                problemString = "LOWPAN_NCP_PROBLEM";
+                break;
+            case LOWPAN_ALREADY:
+                problemString = "LOWPAN_ALREADY";
+                break;
+            case LOWPAN_CANCELED:
+                problemString = "LOWPAN_CANCELED";
+                break;
+            case LOWPAN_CREDENTIAL_NEEDED:
+                problemString = "LOWPAN_CREDENTIAL_NEEDED";
+                break;
+            case LOWPAN_FEATURE_NOT_SUPPORTED:
+                problemString = "LOWPAN_FEATURE_NOT_SUPPORTED";
+                break;
+            case LOWPAN_PROPERTY_NOT_FOUND:
+                problemString = "LOWPAN_PROPERTY_NOT_FOUND";
+                break;
+            case LOWPAN_JOIN_FAILED_UNKNOWN:
+                problemString = "LOWPAN_JOIN_FAILED_UNKNOWN";
+                break;
+            case LOWPAN_JOIN_FAILED_AT_SCAN:
+                problemString = "LOWPAN_JOIN_FAILED_AT_SCAN";
+                break;
+            case LOWPAN_JOIN_FAILED_AT_AUTH:
+                problemString = "LOWPAN_JOIN_FAILED_AT_AUTH";
+                break;
+            case LOWPAN_FORM_FAILED_AT_SCAN:
+                problemString = "LOWPAN_FORM_FAILED_AT_SCAN";
+                break;
+            default:
+                problemString = "LOWPAN_ERROR_CODE_" + problem;
+                break;
+        }
+
+        return problemString;
+    }
+}
diff --git a/lowpan/java/android/net/lowpan/LowpanIdentity.java b/lowpan/java/android/net/lowpan/LowpanIdentity.java
new file mode 100644
index 0000000..2e7b560
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanIdentity.java
@@ -0,0 +1,179 @@
+/*
+ * 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 android.net.lowpan;
+
+import com.android.internal.util.HexDump;
+import java.util.Map;
+
+/**
+ * Describes an instance of a LoWPAN network.
+ *
+ * @hide
+ */
+//@SystemApi
+public class LowpanIdentity {
+
+    //////////////////////////////////////////////////////////////////////////
+    // Constants
+
+    /** @hide */
+    public static final int TYPE_ZIGBEE = 1;
+
+    /** @hide */
+    public static final int TYPE_ZIGBEE_IP = 2;
+
+    /** @hide */
+    public static final int TYPE_THREAD = 3;
+
+    public static final int UNKNOWN = Integer.MAX_VALUE;
+
+    //////////////////////////////////////////////////////////////////////////
+    // Builder
+
+    /** @hide */
+    //@SystemApi
+    public static class Builder {
+        private final LowpanIdentity identity = new LowpanIdentity();
+
+        public Builder setName(String x) {
+            identity.mName = x;
+            return this;
+        }
+
+        public Builder setXpanid(byte x[]) {
+            identity.mXpanid = x.clone();
+            return this;
+        }
+
+        public Builder setPanid(int x) {
+            identity.mPanid = x;
+            return this;
+        }
+
+        /** @hide */
+        public Builder setType(int x) {
+            identity.mType = x;
+            return this;
+        }
+
+        public Builder setChannel(int x) {
+            identity.mChannel = x;
+            return this;
+        }
+
+        /** @hide */
+        Builder updateFromMap(Map map) {
+            if (map.containsKey(ILowpanInterface.KEY_NETWORK_NAME)) {
+                setName(LowpanProperties.KEY_NETWORK_NAME.getFromMap(map));
+            }
+            if (map.containsKey(ILowpanInterface.KEY_NETWORK_PANID)) {
+                setPanid(LowpanProperties.KEY_NETWORK_PANID.getFromMap(map));
+            }
+            if (map.containsKey(ILowpanInterface.KEY_NETWORK_XPANID)) {
+                setXpanid(LowpanProperties.KEY_NETWORK_XPANID.getFromMap(map));
+            }
+            if (map.containsKey(ILowpanInterface.KEY_CHANNEL)) {
+                setChannel(LowpanProperties.KEY_CHANNEL.getFromMap(map));
+            }
+            if (map.containsKey(ILowpanInterface.KEY_NETWORK_TYPE)) {
+                setType(LowpanProperties.KEY_NETWORK_TYPE.getFromMap(map));
+            }
+            return this;
+        }
+
+        public LowpanIdentity build() {
+            return identity;
+        }
+    }
+
+    LowpanIdentity() {}
+
+    //////////////////////////////////////////////////////////////////////////
+    // Instance Variables
+
+    private String mName = null;
+    private byte[] mXpanid = null;
+    private int mType = UNKNOWN;
+    private int mPanid = UNKNOWN;
+    private int mChannel = UNKNOWN;
+
+    //////////////////////////////////////////////////////////////////////////
+    // Public Getters and Setters
+
+    public String getName() {
+        return mName;
+    }
+
+    public byte[] getXpanid() {
+        return mXpanid.clone();
+    }
+
+    public int getPanid() {
+        return mPanid;
+    }
+
+    /** @hide */
+    public int getType() {
+        return mType;
+    }
+
+    public int getChannel() {
+        return mChannel;
+    }
+
+    static void addToMap(Map<String, Object> parameters, LowpanIdentity networkInfo) {
+        if (networkInfo.getName() != null) {
+            LowpanProperties.KEY_NETWORK_NAME.putInMap(parameters, networkInfo.getName());
+        }
+        if (networkInfo.getPanid() != LowpanIdentity.UNKNOWN) {
+            LowpanProperties.KEY_NETWORK_PANID.putInMap(
+                    parameters, networkInfo.getPanid());
+        }
+        if (networkInfo.getChannel() != LowpanIdentity.UNKNOWN) {
+            LowpanProperties.KEY_CHANNEL.putInMap(
+                    parameters, networkInfo.getChannel());
+        }
+        if (networkInfo.getXpanid() != null) {
+            LowpanProperties.KEY_NETWORK_XPANID.putInMap(parameters, networkInfo.getXpanid());
+        }
+    }
+
+    void addToMap(Map<String, Object> parameters) {
+        addToMap(parameters, this);
+    }
+
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+
+        sb.append("Name: ").append(mName == null ? "<none>" : mName);
+
+        if (mXpanid != null) {
+            sb.append(", XPANID: ").append(HexDump.toHexString(mXpanid));
+        }
+
+        if (mPanid != UNKNOWN) {
+            sb.append(", PANID: ").append(String.format("0x%04X", mPanid));
+        }
+
+        if (mChannel != UNKNOWN) {
+            sb.append(", Channel: ").append(mChannel);
+        }
+
+        return sb.toString();
+    }
+}
diff --git a/lowpan/java/android/net/lowpan/LowpanInterface.java b/lowpan/java/android/net/lowpan/LowpanInterface.java
new file mode 100644
index 0000000..cd54819
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanInterface.java
@@ -0,0 +1,824 @@
+/*
+ * 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 android.net.lowpan;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.IpPrefix;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Class for managing a specific Low-power Wireless Personal Area Network (LoWPAN) interface.
+ *
+ * @hide
+ */
+//@SystemApi
+public class LowpanInterface {
+    private static final String TAG = LowpanInterface.class.getSimpleName();
+
+    /** Detached role. The interface is not currently attached to a network. */
+    public static final String ROLE_DETACHED = "detached";
+
+    /** End-device role. End devices do not route traffic for other nodes. */
+    public static final String ROLE_END_DEVICE = "end-device";
+
+    /** Router role. Routers help route traffic around the mesh network. */
+    public static final String ROLE_ROUTER = "router";
+
+    /**
+     * Sleepy End-Device role.
+     *
+     * <p>End devices with this role are nominally asleep, waking up periodically to check in with
+     * their parent to see if there are packets destined for them. Such devices are capable of
+     * extraordinarilly low power consumption, but packet latency can be on the order of dozens of
+     * seconds(depending on how the node is configured).
+     */
+    public static final String ROLE_SLEEPY_END_DEVICE = "sleepy-end-device";
+
+    /**
+     * Sleepy-router role.
+     *
+     * <p>Routers with this role are nominally asleep, waking up periodically to check in with other
+     * routers and their children.
+     */
+    public static final String ROLE_SLEEPY_ROUTER = "sleepy-router";
+
+    /** TODO: doc */
+    public static final String ROLE_LEADER = "leader";
+
+    /** TODO: doc */
+    public static final String ROLE_COORDINATOR = "coordinator";
+
+    /**
+     * Offline state.
+     *
+     * <p>This is the initial state of the LoWPAN interface when the underlying driver starts. In
+     * this state the NCP is idle and not connected to any network.
+     *
+     * <p>This state can be explicitly entered by calling {@link #reset()}, {@link #leave()}, or
+     * <code>setUp(false)</code>, with the later two only working if we were not previously in the
+     * {@link #STATE_FAULT} state.
+     *
+     * @see #getState()
+     * @see #STATE_FAULT
+     */
+    public static final String STATE_OFFLINE = "offline";
+
+    /**
+     * Commissioning state.
+     *
+     * <p>The interface enters this state after a call to {@link #startCommissioningSession()}. This
+     * state may only be entered directly from the {@link #STATE_OFFLINE} state.
+     *
+     * @see #startCommissioningSession()
+     * @see #getState()
+     * @hide
+     */
+    public static final String STATE_COMMISSIONING = "commissioning";
+
+    /**
+     * Attaching state.
+     *
+     * <p>The interface enters this state when it starts the process of trying to find other nodes
+     * so that it can attach to any pre-existing network fragment, or when it is in the process of
+     * calculating the optimal values for unspecified parameters when forming a new network.
+     *
+     * <p>The interface may stay in this state for a prolonged period of time (or may spontaneously
+     * enter this state from {@link #STATE_ATTACHED}) if the underlying network technology is
+     * heirarchical (like ZigBeeIP) or if the device role is that of an "end-device" ({@link
+     * #ROLE_END_DEVICE} or {@link #ROLE_SLEEPY_END_DEVICE}). This is because such roles cannot
+     * create their own network fragments.
+     *
+     * @see #STATE_ATTACHED
+     * @see #getState()
+     */
+    public static final String STATE_ATTACHING = "attaching";
+
+    /**
+     * Attached state.
+     *
+     * <p>The interface enters this state from {@link #STATE_ATTACHING} once it is actively
+     * participating on a network fragment.
+     *
+     * @see #STATE_ATTACHING
+     * @see #getState()
+     */
+    public static final String STATE_ATTACHED = "attached";
+
+    /**
+     * Fault state.
+     *
+     * <p>The interface will enter this state when the driver has detected some sort of problem from
+     * which it was not immediately able to recover.
+     *
+     * <p>This state can be entered spontaneously from any other state. Calling {@link #reset} will
+     * cause the device to return to the {@link #STATE_OFFLINE} state.
+     *
+     * @see #getState
+     * @see #STATE_OFFLINE
+     */
+    public static final String STATE_FAULT = "fault";
+
+    /**
+     * Network type for Thread 1.x networks.
+     *
+     * @see android.net.lowpan.LowpanIdentity#getType
+     * @see #getLowpanIdentity
+     * @hide
+     */
+    public static final String NETWORK_TYPE_THREAD = "org.threadgroup.thread.v1";
+
+    /**
+     * Network type for ZigBeeIP 1.x networks.
+     *
+     * @see android.net.lowpan.LowpanIdentity#getType
+     * @see #getLowpanIdentity
+     * @hide
+     */
+    public static final String NETWORK_TYPE_ZIGBEE_IP = "org.zigbee.zigbeeip.v1";
+
+    private static final String NETWORK_PROPERTY_KEYS[] = {
+        LowpanProperties.KEY_NETWORK_NAME.getName(),
+        LowpanProperties.KEY_NETWORK_PANID.getName(),
+        LowpanProperties.KEY_NETWORK_XPANID.getName(),
+        LowpanProperties.KEY_CHANNEL.getName()
+    };
+
+    /**
+     * Callback base class for LowpanInterface
+     *
+     * @hide
+     */
+    //@SystemApi
+    public abstract static class Callback {
+        public void onConnectedChanged(boolean value) {}
+
+        public void onEnabledChanged(boolean value) {}
+
+        public void onUpChanged(boolean value) {}
+
+        public void onRoleChanged(@NonNull String value) {}
+
+        public void onStateChanged(@NonNull String state) {}
+
+        public void onLowpanIdentityChanged(@NonNull LowpanIdentity value) {}
+
+        /** @hide */
+        public void onPropertiesChanged(@NonNull Map properties) {}
+    }
+
+    private ILowpanInterface mBinder;
+    private final HashMap<Integer, ILowpanInterfaceListener> mListenerMap = new HashMap<>();
+
+    /** Map between IBinder identity hashes and LowpanInstance objects. */
+    private static final HashMap<Integer, LowpanInterface> sInstanceMap = new HashMap<>();
+
+    private LowpanInterface(IBinder binder) {
+        mBinder = ILowpanInterface.Stub.asInterface(binder);
+    }
+
+    /**
+     * Get the LowpanInterface object associated with this IBinder. Returns null if this IBinder
+     * does not implement the appropriate interface.
+     *
+     * @hide
+     */
+    @NonNull
+    public static final LowpanInterface from(IBinder binder) {
+        Integer hashCode = Integer.valueOf(System.identityHashCode(binder));
+        LowpanInterface instance;
+
+        synchronized (sInstanceMap) {
+            instance = sInstanceMap.get(hashCode);
+
+            if (instance == null) {
+                instance = new LowpanInterface(binder);
+                sInstanceMap.put(hashCode, instance);
+            }
+        }
+
+        return instance;
+    }
+
+    /** {@hide} */
+    public static final LowpanInterface from(ILowpanInterface iface) {
+        return from(iface.asBinder());
+    }
+
+    /** {@hide} */
+    public static final LowpanInterface getInterfaceFromBinder(IBinder binder) {
+        return from(binder);
+    }
+
+    /**
+     * Returns the IBinder object associated with this interface.
+     *
+     * @hide
+     */
+    public IBinder getBinder() {
+        return mBinder.asBinder();
+    }
+
+    private static void throwAsPublicException(Throwable t) throws LowpanException {
+        LowpanException.throwAsPublicException(t);
+    }
+
+    //////////////////////////////////////////////////////////////////////////
+    // Private Property Helpers
+
+    void setProperties(Map properties) throws LowpanException {
+        try {
+            mBinder.setProperties(properties);
+
+        } catch (RemoteException x) {
+            // Catch and ignore all binder exceptions
+            Log.e(TAG, x.toString());
+
+        } catch (ServiceSpecificException x) {
+            throwAsPublicException(x);
+        }
+    }
+
+    @NonNull
+    Map<String, Object> getProperties(String keys[]) throws LowpanException {
+        try {
+            return mBinder.getProperties(keys);
+        } catch (RemoteException x) {
+            // Catch and ignore all binder exceptions
+            Log.e(TAG, x.toString());
+        } catch (ServiceSpecificException x) {
+            throwAsPublicException(x);
+        }
+        return new HashMap();
+    }
+
+    /** @hide */
+    public <T> void setProperty(LowpanProperty<T> key, T value) throws LowpanException {
+        HashMap<String, T> prop = new HashMap<>();
+        prop.put(key.getName(), value);
+        setProperties(prop);
+    }
+
+    /** @hide */
+    @Nullable
+    public <T> T getProperty(LowpanProperty<T> key) throws LowpanException {
+        Map<String, Object> map = getProperties(new String[] {key.getName()});
+        if (map != null && !map.isEmpty()) {
+            // We know there is only one value.
+            return (T) map.values().iterator().next();
+        }
+        return null;
+    }
+
+    @Nullable
+    <T> String getPropertyAsString(LowpanProperty<T> key) throws LowpanException {
+        try {
+            return mBinder.getPropertyAsString(key.getName());
+        } catch (RemoteException x) {
+            // Catch and ignore all binder exceptions
+            Log.e(TAG, x.toString());
+        } catch (ServiceSpecificException x) {
+            throwAsPublicException(x);
+        }
+        return null;
+    }
+
+    int getPropertyAsInt(LowpanProperty<Integer> key) throws LowpanException {
+        Integer value = getProperty(key);
+        return (value != null) ? value : 0;
+    }
+
+    boolean getPropertyAsBoolean(LowpanProperty<Boolean> key) throws LowpanException {
+        Boolean value = getProperty(key);
+        return (value != null) ? value : 0;
+    }
+
+    //////////////////////////////////////////////////////////////////////////
+    // Public Actions
+
+    /**
+     * Form a new network with the given network information optional credential. Unspecified fields
+     * in the network information will be filled in with reasonable values. If the network
+     * credential is unspecified, one will be generated automatically.
+     *
+     * <p>This method will block until either the network was successfully formed or an error
+     * prevents the network form being formed.
+     *
+     * <p>Upon success, the interface will be up and attached to the newly formed network.
+     *
+     * @see #join(LowpanProvision)
+     */
+    public void form(@NonNull LowpanProvision provision) throws LowpanException {
+        try {
+            Map<String, Object> parameters = new HashMap();
+            provision.addToMap(parameters);
+            mBinder.form(parameters);
+        } catch (RemoteException x) {
+            throwAsPublicException(x);
+        } catch (ServiceSpecificException x) {
+            throwAsPublicException(x);
+        }
+    }
+
+    /**
+     * Attempts to join a new network with the given network information. This method will block
+     * until either the network was successfully joined or an error prevented the network from being
+     * formed. Upon success, the interface will be up and attached to the newly joined network.
+     *
+     * <p>Note that “joining” is distinct from “attaching”: Joining requires at least one other peer
+     * device to be present in order for the operation to complete successfully.
+     */
+    public void join(@NonNull LowpanProvision provision) throws LowpanException {
+        try {
+            Map<String, Object> parameters = new HashMap();
+            provision.addToMap(parameters);
+            mBinder.join(parameters);
+        } catch (RemoteException x) {
+            throwAsPublicException(x);
+        } catch (ServiceSpecificException x) {
+            throwAsPublicException(x);
+        }
+    }
+
+    /**
+     * Attaches to the network described by identity and credential. This is similar to {@link
+     * #join}, except that (assuming the identity and credential are valid) it will always succeed
+     * and provision the interface, even if there are no peers nearby.
+     *
+     * <p>This method will block execution until the operation has completed.
+     */
+    public void attach(@NonNull LowpanProvision provision) throws LowpanException {
+        if (ROLE_DETACHED.equals(getRole())) {
+            Map<String, Object> parameters = new HashMap();
+            provision.addToMap(parameters);
+            setProperties(parameters);
+            setUp(true);
+        } else {
+            throw new LowpanException(LowpanException.LOWPAN_ALREADY);
+        }
+    }
+
+    /**
+     * Bring down the network interface and forget all non-volatile details about the current
+     * network.
+     *
+     * <p>This method will block execution until the operation has completed.
+     */
+    public void leave() throws LowpanException {
+        try {
+            mBinder.leave();
+        } catch (RemoteException x) {
+            throwAsPublicException(x);
+        } catch (ServiceSpecificException x) {
+            throwAsPublicException(x);
+        }
+    }
+
+    /**
+     * Start a new commissioning session. Will fail if the interface is attached to a network or if
+     * the interface is disabled.
+     */
+    public @NonNull LowpanCommissioningSession startCommissioningSession(
+            @NonNull LowpanBeaconInfo beaconInfo) throws LowpanException {
+
+        /* TODO: Implement startCommissioningSession */
+        throw new LowpanException(LowpanException.LOWPAN_FEATURE_NOT_SUPPORTED);
+    }
+
+    /**
+     * Reset this network interface as if it has been power cycled. Will bring the network interface
+     * down if it was previously up. Will not erase any non-volatile settings.
+     *
+     * <p>This method will block execution until the operation has completed.
+     *
+     * @hide
+     */
+    public void reset() throws LowpanException {
+        try {
+            mBinder.reset();
+        } catch (RemoteException x) {
+            throwAsPublicException(x);
+        } catch (ServiceSpecificException x) {
+            throwAsPublicException(x);
+        }
+    }
+
+    //////////////////////////////////////////////////////////////////////////
+    // Public Getters and Setters
+
+    /**
+     * Returns the name of this network interface.
+     *
+     * <p>Will return empty string if this interface is no longer viable.
+     */
+    @NonNull
+    public String getName() {
+        try {
+            return mBinder.getName();
+        } catch (RemoteException x) {
+            // Catch and ignore all binder exceptions
+            // when fetching the name.
+            Log.e(TAG, x.toString());
+        } catch (ServiceSpecificException x) {
+            // Catch and ignore all service-specific exceptions
+            // when fetching the name.
+            Log.e(TAG, x.toString());
+        }
+        return "";
+    }
+
+  /**
+   * Indicates if the interface is enabled or disabled.
+   *
+   * @see #setEnabled
+   * @see android.net.lowpan.LowpanException#LOWPAN_DISABLED
+   */
+  public boolean isEnabled() {
+        try {
+            return getPropertyAsBoolean(LowpanProperties.KEY_INTERFACE_ENABLED);
+        } catch (LowpanException x) {
+            return false;
+        }
+    }
+
+  /**
+   * Enables or disables the LoWPAN interface. When disabled, the interface is put into a low-power
+   * state and all commands that require the NCP to be queried will fail with {@link
+   * android.net.lowpan.LowpanException#LOWPAN_DISABLED}.
+   *
+   * @see #isEnabled
+   * @see android.net.lowpan.LowpanException#LOWPAN_DISABLED
+   * @hide
+   */
+  public void setEnabled(boolean enabled) throws LowpanException {
+        setProperty(LowpanProperties.KEY_INTERFACE_ENABLED, enabled);
+    }
+
+    /**
+     * Indicates if the network interface is up or down.
+     *
+     * @hide
+     */
+    public boolean isUp() {
+        try {
+            return getPropertyAsBoolean(LowpanProperties.KEY_INTERFACE_UP);
+        } catch (LowpanException x) {
+            return false;
+        }
+    }
+
+    /**
+     * Bring up or shut down the network interface.
+     *
+     * <p>This method brings up or shuts down the network interface, attaching or (gracefully)
+     * detaching from the currently configured LoWPAN network as appropriate.
+     *
+     * @hide
+     */
+    public void setUp(boolean interfaceUp) throws LowpanException {
+        setProperty(LowpanProperties.KEY_INTERFACE_UP, interfaceUp);
+    }
+
+    /**
+     * Indicates if there is at least one peer in range.
+     *
+     * @return <code>true</code> if we have at least one other peer in range, <code>false</code>
+     *     otherwise.
+     */
+    public boolean isConnected() {
+        try {
+            return getPropertyAsBoolean(LowpanProperties.KEY_INTERFACE_CONNECTED);
+        } catch (LowpanException x) {
+            return false;
+        }
+    }
+
+    /**
+     * Indicates if this interface is currently commissioned onto an existing network. If the
+     * interface is commissioned, the interface may be brought up using setUp().
+     */
+    public boolean isCommissioned() {
+        try {
+            return getPropertyAsBoolean(LowpanProperties.KEY_INTERFACE_COMMISSIONED);
+        } catch (LowpanException x) {
+            return false;
+        }
+    }
+
+    /**
+     * Get interface state
+     *
+     * <h3>State Diagram</h3>
+     *
+     * <img src="LowpanInterface-1.png" />
+     *
+     * @return The current state of the interface.
+     * @see #STATE_OFFLINE
+     * @see #STATE_COMMISSIONING
+     * @see #STATE_ATTACHING
+     * @see #STATE_ATTACHED
+     * @see #STATE_FAULT
+     */
+    public String getState() {
+        try {
+            return getProperty(LowpanProperties.KEY_INTERFACE_STATE);
+        } catch (LowpanException x) {
+            Log.e(TAG, x.toString());
+            return STATE_FAULT;
+        }
+    }
+
+    /** TODO: doc */
+    public LowpanIdentity getLowpanIdentity() {
+        LowpanIdentity.Builder builder = new LowpanIdentity.Builder();
+        try {
+            builder.updateFromMap(getProperties(NETWORK_PROPERTY_KEYS));
+        } catch (LowpanException x) {
+            // We ignore all LoWPAN-specitic exceptions here.
+        }
+
+        return builder.build();
+    }
+
+    /**
+     * TODO: doc
+     *
+     * @hide
+     */
+    public void setLowpanIdentity(LowpanIdentity network) throws LowpanException {
+        Map<String, Object> map = new HashMap();
+        LowpanIdentity.addToMap(map, network);
+        setProperties(map);
+    }
+
+    /** TODO: doc */
+    @NonNull
+    public String getRole() {
+        String role = null;
+
+        try {
+            role = getProperty(LowpanProperties.KEY_NETWORK_ROLE);
+        } catch (LowpanException x) {
+            // We ignore all LoWPAN-specitic exceptions here.
+            Log.e(TAG, x.toString());
+        }
+
+        if (role == null) {
+            role = ROLE_DETACHED;
+        }
+
+        return role;
+    }
+
+    /** TODO: doc */
+    @Nullable
+    public LowpanCredential getLowpanCredential() {
+        LowpanCredential credential = null;
+
+        try {
+            Integer keyIndex = getProperty(LowpanProperties.KEY_NETWORK_MASTER_KEY_INDEX);
+
+            if (keyIndex == null) {
+                credential =
+                        LowpanCredential.createMasterKey(
+                                getProperty(LowpanProperties.KEY_NETWORK_MASTER_KEY));
+            } else {
+                credential =
+                        LowpanCredential.createMasterKey(
+                                getProperty(LowpanProperties.KEY_NETWORK_MASTER_KEY),
+                                keyIndex.intValue());
+            }
+        } catch (LowpanException x) {
+            // We ignore all LoWPAN-specitic exceptions here.
+            Log.e(TAG, x.toString());
+        }
+
+        return credential;
+    }
+
+    /**
+     * TODO: doc
+     *
+     * @hide
+     */
+    public void setLowpanCredential(LowpanCredential networkCredential) throws LowpanException {
+        Map<String, Object> map = new HashMap();
+        networkCredential.addToMap(map);
+        setProperties(map);
+    }
+
+    //////////////////////////////////////////////////////////////////////////
+    // Listener Support
+
+    /**
+     * Registers a subclass of {@link LowpanInterface.Callback} to receive events.
+     *
+     * @param cb Subclass of {@link LowpanInterface.Callback} which will receive events.
+     * @param handler If not <code>null</code>, events will be dispatched via the given handler
+     *     object. If <code>null</code>, the thread upon which events will be dispatched is
+     *     unspecified.
+     * @see #registerCallback(Callback)
+     * @see #unregisterCallback(Callback)
+     */
+    public void registerCallback(@NonNull Callback cb, @Nullable Handler handler) {
+        ILowpanInterfaceListener.Stub listenerBinder =
+                new ILowpanInterfaceListener.Stub() {
+                    public void onPropertiesChanged(Map<String, Object> properties) {
+                        Runnable runnable =
+                                new Runnable() {
+                                    @Override
+                                    public void run() {
+                                        for (String key : (Set<String>) properties.keySet()) {
+                                            Object value = properties.get(key);
+                                            switch (key) {
+                                                case ILowpanInterface.KEY_INTERFACE_ENABLED:
+                                                    cb.onEnabledChanged(
+                                                            ((Boolean) value).booleanValue());
+                                                    break;
+                                                case ILowpanInterface.KEY_INTERFACE_UP:
+                                                    cb.onUpChanged(
+                                                            ((Boolean) value).booleanValue());
+                                                    break;
+                                                case ILowpanInterface.KEY_INTERFACE_CONNECTED:
+                                                    cb.onConnectedChanged(
+                                                            ((Boolean) value).booleanValue());
+                                                    break;
+                                                case ILowpanInterface.KEY_INTERFACE_STATE:
+                                                    cb.onStateChanged((String) value);
+                                                    break;
+                                                case ILowpanInterface.KEY_NETWORK_NAME:
+                                                case ILowpanInterface.KEY_NETWORK_PANID:
+                                                case ILowpanInterface.KEY_NETWORK_XPANID:
+                                                case ILowpanInterface.KEY_CHANNEL:
+                                                    cb.onLowpanIdentityChanged(getLowpanIdentity());
+                                                    break;
+                                                case ILowpanInterface.KEY_NETWORK_ROLE:
+                                                    cb.onRoleChanged(value.toString());
+                                                    break;
+                                            }
+                                        }
+                                        cb.onPropertiesChanged(properties);
+                                    }
+                                };
+
+                        if (handler != null) {
+                            handler.post(runnable);
+                        } else {
+                            runnable.run();
+                        }
+                    }
+                };
+        try {
+            mBinder.addListener(listenerBinder);
+        } catch (RemoteException x) {
+            // Log and ignore. If this happens, this interface
+            // is likely dead anyway.
+            Log.e(TAG, x.toString());
+        }
+        synchronized (mListenerMap) {
+            mListenerMap.put(System.identityHashCode(cb), listenerBinder);
+        }
+    }
+
+    /**
+     * Registers a subclass of {@link LowpanInterface.Callback} to receive events.
+     *
+     * <p>The thread upon which events will be dispatched is unspecified.
+     *
+     * @param cb Subclass of {@link LowpanInterface.Callback} which will receive events.
+     * @see #registerCallback(Callback, Handler)
+     * @see #unregisterCallback(Callback)
+     */
+    public void registerCallback(Callback cb) {
+        registerCallback(cb, null);
+    }
+
+    /**
+     * Unregisters a previously registered callback class.
+     *
+     * @param cb Subclass of {@link LowpanInterface.Callback} which was previously registered to
+     *     receive events.
+     * @see #registerCallback(Callback, Handler)
+     * @see #registerCallback(Callback)
+     */
+    public void unregisterCallback(Callback cb) {
+        int hashCode = System.identityHashCode(cb);
+        ILowpanInterfaceListener listenerBinder = mListenerMap.get(hashCode);
+
+        if (listenerBinder != null) {
+            synchronized (mListenerMap) {
+                mListenerMap.remove(hashCode);
+            }
+            try {
+                mBinder.removeListener(listenerBinder);
+            } catch (RemoteException x) {
+                // Catch and ignore all binder exceptions
+                Log.e(TAG, x.toString());
+            }
+        }
+    }
+
+  //////////////////////////////////////////////////////////////////////////
+  // Active and Passive Scanning
+
+  /**
+   * Creates a new {@link android.net.lowpan.LowpanScanner} object for this interface.
+   *
+   * <p>This method allocates a new unique object for each call.
+   *
+   * @see android.net.lowpan.LowpanScanner
+   */
+  public @NonNull LowpanScanner createScanner() {
+        return new LowpanScanner(mBinder);
+    }
+
+    //////////////////////////////////////////////////////////////////////////
+    // Route Management
+
+    /**
+     * Advertise the given IP prefix as an on-mesh prefix.
+     *
+     * @hide
+     */
+    public void addOnMeshPrefix(IpPrefix prefix, int flags) throws LowpanException {
+        try {
+            mBinder.addOnMeshPrefix(prefix, flags);
+        } catch (RemoteException x) {
+            throwAsPublicException(x);
+        } catch (ServiceSpecificException x) {
+            throwAsPublicException(x);
+        }
+    }
+
+    /**
+     * Remove an IP prefix previously advertised by this device from the list of advertised on-mesh
+     * prefixes.
+     *
+     * @hide
+     */
+    public void removeOnMeshPrefix(IpPrefix prefix) {
+        try {
+            mBinder.removeOnMeshPrefix(prefix);
+        } catch (RemoteException x) {
+            // Catch and ignore all binder exceptions
+            Log.e(TAG, x.toString());
+        } catch (ServiceSpecificException x) {
+            // Catch and ignore all service exceptions
+            Log.e(TAG, x.toString());
+        }
+    }
+
+    /**
+     * Advertise this device to other devices on the mesh network as having a specific route to the
+     * given network. This device will then receive forwarded traffic for that network.
+     *
+     * @hide
+     */
+    public void addExternalRoute(IpPrefix prefix, int flags) throws LowpanException {
+        try {
+            mBinder.addExternalRoute(prefix, flags);
+        } catch (RemoteException x) {
+            throwAsPublicException(x);
+        } catch (ServiceSpecificException x) {
+            throwAsPublicException(x);
+        }
+    }
+
+    /**
+     * Revoke a previously advertised specific route to the given network.
+     *
+     * @hide
+     */
+    public void removeExternalRoute(IpPrefix prefix) {
+        try {
+            mBinder.removeExternalRoute(prefix);
+        } catch (RemoteException x) {
+            // Catch and ignore all binder exceptions
+            Log.e(TAG, x.toString());
+        } catch (ServiceSpecificException x) {
+            // Catch and ignore all service exceptions
+            Log.e(TAG, x.toString());
+        }
+    }
+}
diff --git a/lowpan/java/android/net/lowpan/LowpanManager.java b/lowpan/java/android/net/lowpan/LowpanManager.java
new file mode 100644
index 0000000..b58608d
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanManager.java
@@ -0,0 +1,283 @@
+/*
+ * 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 android.net.lowpan;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.DeadObjectException;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.AndroidException;
+import android.util.Log;
+import java.util.HashMap;
+
+/**
+ * Manager object for looking up LoWPAN interfaces.
+ *
+ * @hide
+ */
+//@SystemApi
+public class LowpanManager {
+    private static final String TAG = LowpanManager.class.getSimpleName();
+
+    //////////////////////////////////////////////////////////////////////////
+    // Public Classes
+
+    /** @hide */
+    //@SystemApi
+    public abstract static class Callback {
+        public void onInterfaceAdded(LowpanInterface lowpan_interface) {}
+
+        public void onInterfaceRemoved(LowpanInterface lowpan_interface) {}
+    }
+
+    //////////////////////////////////////////////////////////////////////////
+    // Instance Variables
+
+    private ILowpanManager mManager;
+    private HashMap<Integer, ILowpanManagerListener> mListenerMap = new HashMap<>();
+
+    //////////////////////////////////////////////////////////////////////////
+
+    private static LowpanManager sSingletonInstance;
+
+    //////////////////////////////////////////////////////////////////////////
+    // Static Methods
+
+    /** Returns a reference to the LowpanManager object, allocating it if necessary. */
+    public static LowpanManager getManager() {
+        return from(null);
+    }
+
+    public static LowpanManager from(Context context) {
+        // TODO: Actually get this from the context!
+
+        if (sSingletonInstance == null) {
+            sSingletonInstance = new LowpanManager();
+        }
+        return sSingletonInstance;
+    }
+
+    //////////////////////////////////////////////////////////////////////////
+    // Constructors
+
+    /**
+     * Private LowpanManager constructor. Since we are a singleton, we do not allow external
+     * construction.
+     */
+    private LowpanManager() {}
+
+    //////////////////////////////////////////////////////////////////////////
+    // Private Methods
+
+    /**
+     * Returns a reference to the ILowpanManager interface, provided by the LoWPAN Manager Service.
+     */
+    @Nullable
+    private ILowpanManager getILowpanManager() {
+        ILowpanManager manager = mManager;
+        if (manager == null) {
+            IBinder serviceBinder =
+                    new ServiceManager().getService(ILowpanManager.LOWPAN_SERVICE_NAME);
+            mManager = manager = ILowpanManager.Stub.asInterface(serviceBinder);
+
+            // Add any listeners
+            synchronized (mListenerMap) {
+                for (Integer hashObj : mListenerMap.keySet()) {
+                    try {
+                        manager.addListener(mListenerMap.get(hashObj));
+                    } catch (RemoteException x) {
+                        // Consider any failure here as implying the manager is defunct
+                        mManager = manager = null;
+                    }
+                }
+            }
+        }
+        return manager;
+    }
+
+    //////////////////////////////////////////////////////////////////////////
+    // Public Methods
+
+    /**
+     * Returns a reference to the requested LowpanInterface object. If the given interface doesn't
+     * exist, or it is not a LoWPAN interface, returns null.
+     */
+    @Nullable
+    public LowpanInterface getInterface(@NonNull String name) {
+        LowpanInterface ret = null;
+        ILowpanManager manager = getILowpanManager();
+
+        // Maximum number of tries is two. We should only try
+        // more than once if our manager has died or there
+        // was some sort of AIDL buffer full event.
+        for (int i = 0; i < 2 && manager != null; i++) {
+            try {
+                ILowpanInterface iface = manager.getInterface(name);
+                if (iface != null) {
+                    ret = LowpanInterface.getInterfaceFromBinder(iface.asBinder());
+                }
+                break;
+            } catch (RemoteException x) {
+                // In all of the cases when we get this exception, we reconnect and try again
+                mManager = null;
+                manager = getILowpanManager();
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Returns a reference to the first registered LowpanInterface object. If there are no LoWPAN
+     * interfaces registered, returns null.
+     */
+    @Nullable
+    public LowpanInterface getInterface() {
+        String[] ifaceList = getInterfaceList();
+        if (ifaceList != null && ifaceList.length > 0) {
+            return getInterface(ifaceList[0]);
+        }
+        return null;
+    }
+
+    /**
+     * Returns a string array containing the names of LoWPAN interfaces. This list may contain fewer
+     * interfaces if the calling process does not have permissions to see individual interfaces.
+     */
+    @NonNull
+    public String[] getInterfaceList() {
+        ILowpanManager manager = getILowpanManager();
+
+        if (manager != null) {
+            try {
+                return manager.getInterfaceList();
+
+            } catch (RemoteException x) {
+                // In all of the cases when we get this exception, we reconnect and try again
+                mManager = null;
+                try {
+                    manager = getILowpanManager();
+                    if (manager != null) {
+                        return manager.getInterfaceList();
+                    }
+                } catch (RemoteException ex) {
+                    // Something weird is going on, so we log it
+                    // and fall back thru to returning an empty array.
+                    Log.e(TAG, ex.toString());
+                    mManager = null;
+                }
+            }
+        }
+
+        // Return empty list if we have no service.
+        return new String[0];
+    }
+
+    /**
+     * Registers a callback object to receive notifications when LoWPAN interfaces are added or
+     * removed.
+     *
+     * @hide
+     */
+    public void registerCallback(@NonNull Callback cb, @Nullable Handler handler)
+            throws LowpanException {
+        ILowpanManagerListener.Stub listenerBinder =
+                new ILowpanManagerListener.Stub() {
+                    public void onInterfaceAdded(ILowpanInterface lowpan_interface) {
+                        Runnable runnable =
+                                new Runnable() {
+                                    @Override
+                                    public void run() {
+                                        cb.onInterfaceAdded(
+                                                LowpanInterface.getInterfaceFromBinder(
+                                                        lowpan_interface.asBinder()));
+                                    }
+                                };
+
+                        if (handler != null) {
+                            handler.post(runnable);
+                        } else {
+                            runnable.run();
+                        }
+                    }
+
+                    public void onInterfaceRemoved(ILowpanInterface lowpan_interface) {
+                        Runnable runnable =
+                                new Runnable() {
+                                    @Override
+                                    public void run() {
+                                        cb.onInterfaceRemoved(
+                                                LowpanInterface.getInterfaceFromBinder(
+                                                        lowpan_interface.asBinder()));
+                                    }
+                                };
+
+                        if (handler != null) {
+                            handler.post(runnable);
+                        } else {
+                            runnable.run();
+                        }
+                    }
+                };
+        ILowpanManager manager = getILowpanManager();
+        if (manager != null) {
+            try {
+                manager.addListener(listenerBinder);
+            } catch (DeadObjectException x) {
+                mManager = null;
+                // Tickle the ILowpanManager instance, which might
+                // get us added back.
+                getILowpanManager();
+            } catch (Throwable x) {
+                LowpanException.throwAsPublicException(x);
+            }
+        }
+        synchronized (mListenerMap) {
+            mListenerMap.put(Integer.valueOf(System.identityHashCode(cb)), listenerBinder);
+        }
+    }
+
+    /** @hide */
+    public void registerCallback(@NonNull Callback cb) throws LowpanException {
+        registerCallback(cb, null);
+    }
+
+    /**
+     * Unregisters a previously registered {@link LowpanManager.Callback} object.
+     *
+     * @hide
+     */
+    public void unregisterCallback(@NonNull Callback cb) throws AndroidException {
+        Integer hashCode = Integer.valueOf(System.identityHashCode(cb));
+        ILowpanManagerListener listenerBinder = mListenerMap.get(hashCode);
+        if (listenerBinder != null) {
+            synchronized (mListenerMap) {
+                mListenerMap.remove(hashCode);
+            }
+            if (getILowpanManager() != null) {
+                try {
+                    mManager.removeListener(listenerBinder);
+                } catch (DeadObjectException x) {
+                    mManager = null;
+                }
+            }
+        }
+    }
+}
diff --git a/lowpan/java/android/net/lowpan/LowpanProperties.java b/lowpan/java/android/net/lowpan/LowpanProperties.java
new file mode 100644
index 0000000..0d5acc2
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanProperties.java
@@ -0,0 +1,125 @@
+/*
+ * 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 android.net.lowpan;
+
+import android.net.LinkAddress;
+import android.net.RouteInfo;
+import java.util.List;
+
+/** {@hide} */
+public final class LowpanProperties {
+
+    public static final LowpanProperty<Boolean> KEY_INTERFACE_ENABLED =
+            new LowpanStandardProperty(
+                    "android.net.lowpan.property.INTERFACE_ENABLED", Boolean.class);
+    public static final LowpanProperty<Boolean> KEY_INTERFACE_COMMISSIONED =
+            new LowpanStandardProperty(
+                    "android.net.lowpan.property.INTERFACE_COMMISSIONED", Boolean.class);
+    public static final LowpanProperty<Boolean> KEY_INTERFACE_CONNECTED =
+            new LowpanStandardProperty(
+                    "android.net.lowpan.property.INTERFACE_CONNECTED", Boolean.class);
+    public static final LowpanProperty<Boolean> KEY_INTERFACE_UP =
+            new LowpanStandardProperty("android.net.lowpan.property.INTERFACE_UP", Boolean.class);
+    public static final LowpanProperty<String> KEY_INTERFACE_STATE =
+            new LowpanStandardProperty("android.net.lowpan.property.INTERFACE_STATE", String.class);
+
+    public static final LowpanProperty<String> KEY_NETWORK_NAME =
+            new LowpanStandardProperty("android.net.lowpan.property.NETWORK_NAME", Boolean.class);
+    public static final LowpanProperty<Integer> KEY_NETWORK_PANID =
+            new LowpanStandardProperty("android.net.lowpan.property.NETWORK_PANID", Integer.class);
+    public static final LowpanProperty<byte[]> KEY_NETWORK_XPANID =
+            new LowpanStandardProperty("android.net.lowpan.property.NETWORK_XPANID", byte[].class);
+    public static final LowpanProperty<byte[]> KEY_NETWORK_MASTER_KEY =
+            new LowpanStandardProperty(
+                    "android.net.lowpan.property.NETWORK_MASTER_KEY", byte[].class);
+    public static final LowpanProperty<Integer> KEY_NETWORK_MASTER_KEY_INDEX =
+            new LowpanStandardProperty(
+                    "android.net.lowpan.property.NETWORK_MASTER_KEY_INDEX", Integer.class);
+    public static final LowpanProperty<Integer> KEY_NETWORK_TYPE =
+            new LowpanStandardProperty("android.net.lowpan.property.NETWORK_TYPE", Integer.class);
+    public static final LowpanProperty<String> KEY_NETWORK_ROLE =
+            new LowpanStandardProperty("android.net.lowpan.property.NETWORK_ROLE", String.class);
+
+    public static final LowpanProperty<Integer> KEY_CHANNEL =
+            new LowpanStandardProperty("android.net.lowpan.property.CHANNEL", Integer.class);
+    public static final LowpanProperty<int[]> KEY_CHANNEL_MASK =
+            new LowpanStandardProperty("android.net.lowpan.property.CHANNEL_MASK", int[].class);
+    public static final LowpanProperty<Integer> KEY_MAX_TX_POWER =
+            new LowpanStandardProperty("android.net.lowpan.property.MAX_TX_POWER", Integer.class);
+    public static final LowpanProperty<Integer> KEY_RSSI =
+            new LowpanStandardProperty("android.net.lowpan.property.RSSI", Integer.class);
+
+    public static final LowpanProperty<Integer> KEY_LQI =
+            new LowpanStandardProperty("android.net.lowpan.property.LQI", Integer.class);
+    public static final LowpanProperty<byte[]> KEY_BEACON_ADDRESS =
+            new LowpanStandardProperty("android.net.lowpan.property.BEACON_ADDRESS", byte[].class);
+    public static final LowpanProperty<Boolean> KEY_BEACON_CAN_ASSIST =
+            new LowpanStandardProperty(
+                    "android.net.lowpan.property.BEACON_CAN_ASSIST", Boolean.class);
+
+    public static final LowpanProperty<String> KEY_DRIVER_VERSION =
+            new LowpanStandardProperty("android.net.lowpan.property.DRIVER_VERSION", String.class);
+
+    public static final LowpanProperty<String> KEY_NCP_VERSION =
+            new LowpanStandardProperty("android.net.lowpan.property.NCP_VERSION", String.class);
+
+    public static final LowpanProperty<List<LinkAddress>> KEY_LINK_ADDRESS_ARRAY =
+            new LowpanStandardProperty(
+                    "android.net.lowpan.property.LINK_ADDRESS_ARRAY", LinkAddress[].class);
+
+    public static final LowpanProperty<List<RouteInfo>> KEY_ROUTE_INFO_ARRAY =
+            new LowpanStandardProperty(
+                    "android.net.lowpan.property.ROUTE_INFO_ARRAY", RouteInfo[].class);
+
+    /** @hide */
+    public static final LowpanProperty<byte[]> KEY_EXTENDED_ADDRESS =
+            new LowpanStandardProperty(
+                    "android.net.lowpan.property.EXTENDED_ADDRESS", byte[].class);
+
+    /** @hide */
+    public static final LowpanProperty<byte[]> KEY_MAC_ADDRESS =
+            new LowpanStandardProperty("android.net.lowpan.property.MAC_ADDRESS", byte[].class);
+
+    /** @hide */
+    private LowpanProperties() {}
+
+    /** @hide */
+    static final class LowpanStandardProperty<T> extends LowpanProperty<T> {
+        private final String mName;
+        private final Class<T> mType;
+
+        LowpanStandardProperty(String name, Class<T> type) {
+            mName = name;
+            mType = type;
+        }
+
+        @Override
+        public String getName() {
+            return mName;
+        }
+
+        @Override
+        public Class<T> getType() {
+            return mType;
+        }
+
+        @Override
+        public String toString() {
+            return getName();
+        }
+    }
+}
diff --git a/lowpan/java/android/net/lowpan/LowpanProperty.java b/lowpan/java/android/net/lowpan/LowpanProperty.java
new file mode 100644
index 0000000..7f26986
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanProperty.java
@@ -0,0 +1,34 @@
+/*
+ * 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 android.net.lowpan;
+
+import java.util.Map;
+
+/** {@hide} */
+public abstract class LowpanProperty<T> {
+    public abstract String getName();
+
+    public abstract Class<T> getType();
+
+    public void putInMap(Map map, T value) {
+        map.put(getName(), value);
+    }
+
+    public T getFromMap(Map map) {
+        return (T) map.get(getName());
+    }
+}
diff --git a/lowpan/java/android/net/lowpan/LowpanProvision.java b/lowpan/java/android/net/lowpan/LowpanProvision.java
new file mode 100644
index 0000000..ace1f9c
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanProvision.java
@@ -0,0 +1,104 @@
+/*
+ * 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 android.net.lowpan;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import java.util.Map;
+
+/**
+ * Describes the information needed to describe a network
+ *
+ * @hide
+ */
+//@SystemApi
+public class LowpanProvision {
+
+    //////////////////////////////////////////////////////////////////////////
+    // Builder
+
+    /** @hide */
+    //@SystemApi
+    public static class Builder {
+        private final LowpanProvision provision = new LowpanProvision();
+
+        public Builder setLowpanIdentity(@NonNull LowpanIdentity identity) {
+            provision.mIdentity = identity;
+            return this;
+        }
+
+        public Builder setLowpanCredential(@NonNull LowpanCredential credential) {
+            provision.mCredential = credential;
+            return this;
+        }
+
+        public LowpanProvision build() {
+            return provision;
+        }
+    }
+
+    private LowpanProvision() {}
+
+    //////////////////////////////////////////////////////////////////////////
+    // Instance Variables
+
+    private LowpanIdentity mIdentity = new LowpanIdentity();
+    private LowpanCredential mCredential = null;
+
+    //////////////////////////////////////////////////////////////////////////
+    // Public Getters and Setters
+
+    @NonNull
+    public LowpanIdentity getLowpanIdentity() {
+        return mIdentity;
+    }
+
+    @Nullable
+    public LowpanCredential getLowpanCredential() {
+        return mCredential;
+    }
+
+    //////////////////////////////////////////////////////////////////////////
+    // LoWPAN-Internal Methods
+
+    static void addToMap(Map<String, Object> parameters, LowpanProvision provision)
+            throws LowpanException {
+        provision.mIdentity.addToMap(parameters);
+        if (provision.mCredential != null) {
+            provision.mCredential.addToMap(parameters);
+        }
+    }
+
+    void addToMap(Map<String, Object> parameters) throws LowpanException {
+        addToMap(parameters, this);
+    }
+
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+
+        sb.append("LowpanProvision { identity => ").append(mIdentity.toString());
+
+        if (mCredential != null) {
+            sb.append(", credential: ").append(mCredential.toString());
+        }
+
+        sb.append("}");
+
+        return sb.toString();
+    }
+};
diff --git a/lowpan/java/android/net/lowpan/LowpanScanner.java b/lowpan/java/android/net/lowpan/LowpanScanner.java
new file mode 100644
index 0000000..754f72e3
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanScanner.java
@@ -0,0 +1,317 @@
+/*
+ * 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 android.net.lowpan;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Log;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * LoWPAN Scanner
+ *
+ * <p>This class allows performing network (active) scans and energy (passive) scans.
+ *
+ * @see LowpanInterface
+ * @hide
+ */
+//@SystemApi
+public class LowpanScanner {
+    private static final String TAG = LowpanInterface.class.getSimpleName();
+
+    //////////////////////////////////////////////////////////////////////////
+    // Public Classes
+
+    /**
+     * Callback base class for LowpanScanner
+     *
+     * @hide
+     */
+    //@SystemApi
+    public abstract static class Callback {
+        public void onNetScanBeacon(LowpanBeaconInfo beacon) {}
+
+        public void onEnergyScanResult(LowpanEnergyScanResult result) {}
+
+        public void onScanFinished() {}
+    }
+
+    //////////////////////////////////////////////////////////////////////////
+    // Instance Variables
+
+    private ILowpanInterface mBinder;
+    private Callback mCallback = null;
+    private Handler mHandler = null;
+    private List<Integer> mChannelMask = null;
+    private int mTxPower = Integer.MAX_VALUE;
+
+    //////////////////////////////////////////////////////////////////////////
+    // Constructors/Accessors and Exception Glue
+
+    LowpanScanner(@NonNull ILowpanInterface binder) {
+        mBinder = binder;
+    }
+
+    /** Sets an instance of {@link LowpanScanner.Callback} to receive events. */
+    public void setCallback(@Nullable Callback cb, @Nullable Handler handler) {
+        mCallback = cb;
+        mHandler = handler;
+    }
+
+    /** Sets an instance of {@link LowpanScanner.Callback} to receive events. */
+    public void setCallback(@Nullable Callback cb) {
+        setCallback(cb, null);
+    }
+
+    /**
+     * Sets the channel mask to use when scanning.
+     *
+     * @param mask The channel mask to use when scanning. If <code>null</code>, any previously set
+     *     channel mask will be cleared and all channels not masked by the current regulatory zone
+     *     will be scanned.
+     */
+    public void setChannelMask(@Nullable Collection<Integer> mask) {
+        if (mask == null) {
+            mChannelMask = null;
+        } else {
+            if (mChannelMask == null) {
+                mChannelMask = new ArrayList<>();
+            } else {
+                mChannelMask.clear();
+            }
+            mChannelMask.addAll(mask);
+        }
+    }
+
+    /**
+     * Gets the current channel mask.
+     *
+     * @return the current channel mask, or <code>null</code> if no channel mask is currently set.
+     */
+    public @Nullable Collection<Integer> getChannelMask() {
+        return mChannelMask.clone();
+    }
+
+    /**
+     * Adds a channel to the channel mask used for scanning.
+     *
+     * <p>If a channel mask was previously <code>null</code>, a new one is created containing only
+     * this channel. May be called multiple times to add additional channels ot the channel mask.
+     *
+     * @see #setChannelMask
+     * @see #getChannelMask
+     * @see #getTxPower
+     */
+    public void addChannel(int channel) {
+        if (mChannelMask == null) {
+            mChannelMask = new ArrayList<>();
+        }
+        mChannelMask.add(Integer.valueOf(channel));
+    }
+
+    /**
+     * Sets the maximum transmit power to be used for active scanning.
+     *
+     * <p>The actual transmit power used is the lesser of this value and the currently configured
+     * maximum transmit power for the interface.
+     *
+     * @see #getTxPower
+     */
+    public void setTxPower(int txPower) {
+        mTxPower = txPower;
+    }
+
+    /**
+     * Gets the maximum transmit power used for active scanning.
+     *
+     * @see #setTxPower
+     */
+    public int getTxPower() {
+        return mTxPower;
+    }
+
+    private Map<String, Object> createScanOptionMap() {
+        Map<String, Object> map = new HashMap();
+
+        if (mChannelMask != null) {
+            LowpanProperties.KEY_CHANNEL_MASK.putInMap(
+                    map, mChannelMask.stream().mapToInt(i -> i).toArray());
+        }
+
+        if (mTxPower != Integer.MAX_VALUE) {
+            LowpanProperties.KEY_MAX_TX_POWER.putInMap(map, Integer.valueOf(mTxPower));
+        }
+
+        return map;
+    }
+
+    /**
+     * Start a network scan.
+     *
+     * <p>This method will return once the scan has started.
+     *
+     * @see #stopNetScan
+     */
+    public void startNetScan() throws LowpanException {
+        Map<String, Object> map = createScanOptionMap();
+
+        ILowpanNetScanCallback binderListener =
+                new ILowpanNetScanCallback.Stub() {
+                    public void onNetScanBeacon(Map parameters) {
+                        Callback callback = mCallback;
+                        Handler handler = mHandler;
+
+                        if (callback == null) {
+                            return;
+                        }
+
+                        Runnable runnable = () -> callback.onNetScanBeacon(
+                                new LowpanBeaconInfo.Builder()
+                                        .updateFromMap(parameters)
+                                        .build());
+
+                        if (handler != null) {
+                            handler.post(runnable);
+                        } else {
+                            runnable.run();
+                        }
+                    }
+
+                    public void onNetScanFinished() {
+                        Callback callback = mCallback;
+                        Handler handler = mHandler;
+
+                        if (callback == null) {
+                            return;
+                        }
+
+                        Runnable runnable = () -> callback.onScanFinished();
+
+                        if (handler != null) {
+                            handler.post(runnable);
+                        } else {
+                            runnable.run();
+                        }
+                    }
+                };
+
+        try {
+            mBinder.startNetScan(map, binderListener);
+        } catch (ServiceSpecificException|RemoteException x) {
+            LowpanException.throwAsPublicException(x);
+        }
+    }
+
+    /**
+     * Stop a network scan currently in progress.
+     *
+     * @see #startNetScan
+     */
+    public void stopNetScan() {
+        try {
+            mBinder.stopNetScan();
+        } catch (RemoteException x) {
+            // Catch and ignore all binder exceptions
+            Log.e(TAG, x.toString());
+        }
+    }
+
+    /**
+     * Start an energy scan.
+     *
+     * <p>This method will return once the scan has started.
+     *
+     * @see #stopEnergyScan
+     */
+    public void startEnergyScan() throws LowpanException {
+        Map<String, Object> map = createScanOptionMap();
+
+        ILowpanEnergyScanCallback binderListener =
+                new ILowpanEnergyScanCallback.Stub() {
+                    public void onEnergyScanResult(int channel, int rssi) {
+                        Callback callback = mCallback;
+                        Handler handler = mHandler;
+
+                        if (callback == null) {
+                            return;
+                        }
+
+                        Runnable runnable = () -> {
+                                    if (callback != null) {
+                                        LowpanEnergyScanResult result =
+                                                new LowpanEnergyScanResult();
+                                        result.setChannel(channel);
+                                        result.setMaxRssi(rssi);
+                                        callback.onEnergyScanResult(result);
+                                    }
+                                };
+
+                        if (handler != null) {
+                            handler.post(runnable);
+                        } else {
+                            runnable.run();
+                        }
+                    }
+
+                    public void onEnergyScanFinished() {
+                        Callback callback = mCallback;
+                        Handler handler = mHandler;
+
+                        if (callback == null) {
+                            return;
+                        }
+
+                        Runnable runnable = () -> callback.onScanFinished();
+
+                        if (handler != null) {
+                            handler.post(runnable);
+                        } else {
+                            runnable.run();
+                        }
+                    }
+                };
+
+        try {
+            mBinder.startEnergyScan(map, binderListener);
+        } catch (RemoteException x) {
+            LowpanException.throwAsPublicException(x);
+        } catch (ServiceSpecificException x) {
+            LowpanException.throwAsPublicException(x);
+        }
+    }
+
+    /**
+     * Stop an energy scan currently in progress.
+     *
+     * @see #startEnergyScan
+     */
+    public void stopEnergyScan() {
+        try {
+            mBinder.stopEnergyScan();
+        } catch (RemoteException x) {
+            // Catch and ignore all binder exceptions
+            Log.e(TAG, x.toString());
+        }
+    }
+}
diff --git a/lowpan/java/android/net/lowpan/package.html b/lowpan/java/android/net/lowpan/package.html
new file mode 100644
index 0000000..342e32ee
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/package.html
@@ -0,0 +1,29 @@
+<HTML>
+<BODY>
+<p>@SystemApi</p>
+<!-- @hide -->
+<p>Provides classes to manage Low-power Wireless Personal Area Network (LoWPAN) functionality on the device.
+Examples of such network technologies include <a href="http://threadgroup.org/">Thread</a> and
+<a href="http://www.zigbee.org/zigbee-for-developers/network-specifications/zigbeeip/">ZigBee IP</a>.</p>
+<p>The LoWPAN APIs provide a means by which applications can communicate
+with the lower-level wireless stack that provides LoWPAN network access.</p>
+
+<p>Some APIs may require the following user permissions:</p>
+<ul>
+  <li>{@link android.Manifest.permission#ACCESS_LOWPAN_STATE}</li>
+  <li>{@link android.Manifest.permission#CHANGE_LOWPAN_STATE}</li>
+  <li>TBD</li>
+</ul>
+
+<p class="note"><strong>Note:</strong> Not all Android-powered devices provide LoWPAN functionality.
+If your application uses these APIs, declare so with a <a
+href="{@docRoot}guide/topics/manifest/uses-feature-element.html">{@code <uses-feature>}</a>
+element in the manifest file:</p>
+<pre>
+&lt;manifest ...>
+    &lt;uses-feature android:name="android.hardware.lowpan" />
+    ...
+&lt;/manifest>
+</pre>
+</BODY>
+</HTML>