Merge changes from topic 'android-lowpan-next'

* changes:
  lowpan: AIDL refactor to no longer use property design pattern
  LowpanException: Refactor exception handling
  lowpan: Introduce new unit tests for data classes
  lowpan: Make various data classes Parcelable
  lowpan: Remove libandroid_net_lowpan from platform/frameworks/base
  LowpanEnergyScanResult: Remove `public` designation from setChannel/setMaxRssi
diff --git a/lowpan/Android.mk b/lowpan/Android.mk
deleted file mode 100644
index 0079958..0000000
--- a/lowpan/Android.mk
+++ /dev/null
@@ -1,33 +0,0 @@
-#
-# 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)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
-endif
diff --git a/lowpan/java/android/net/lowpan/ILowpanInterface.aidl b/lowpan/java/android/net/lowpan/ILowpanInterface.aidl
index 329e9fa..603dc3c 100644
--- a/lowpan/java/android/net/lowpan/ILowpanInterface.aidl
+++ b/lowpan/java/android/net/lowpan/ILowpanInterface.aidl
@@ -16,103 +16,39 @@
 
 package android.net.lowpan;
 
+import android.net.IpPrefix;
+import android.net.lowpan.ILowpanEnergyScanCallback;
 import android.net.lowpan.ILowpanInterfaceListener;
 import android.net.lowpan.ILowpanNetScanCallback;
-import android.net.lowpan.ILowpanEnergyScanCallback;
-import android.os.PersistableBundle;
-import android.net.IpPrefix;
+import android.net.lowpan.LowpanBeaconInfo;
+import android.net.lowpan.LowpanChannelInfo;
+import android.net.lowpan.LowpanCredential;
+import android.net.lowpan.LowpanIdentity;
+import android.net.lowpan.LowpanProvision;
 
 /** {@hide} */
 interface ILowpanInterface {
 
-    //////////////////////////////////////////////////////////////////////////
-    // Permission String Constants
-
-    /* These are here for the sake of C++ interface implementations. */
+    // 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
-
-    /** Type: Boolean */
-    const String KEY_INTERFACE_ENABLED      = "android.net.lowpan.property.INTERFACE_ENABLED";
-
-    /** Type: Boolean */
-    const String KEY_INTERFACE_UP           = "android.net.lowpan.property.INTERFACE_UP";
-
-    /** Type: Boolean */
-    const String KEY_INTERFACE_COMMISSIONED = "android.net.lowpan.property.INTERFACE_COMMISSIONED";
-
-    /** Type: Boolean */
-    const String KEY_INTERFACE_CONNECTED    = "android.net.lowpan.property.INTERFACE_CONNECTED";
-
-    /** Type: String */
-    const String KEY_INTERFACE_STATE        = "android.net.lowpan.property.INTERFACE_STATE";
-
-    /** Type: String */
-    const String KEY_NETWORK_NAME             = "android.net.lowpan.property.NETWORK_NAME";
-
-    /** Type: Integer */
-    const String KEY_NETWORK_TYPE             = "android.net.lowpan.property.NETWORK_TYPE";
-
-    /** Type: Integer */
-    const String KEY_NETWORK_PANID            = "android.net.lowpan.property.NETWORK_PANID";
-
-    /** Type: byte[] */
-    const String KEY_NETWORK_XPANID           = "android.net.lowpan.property.NETWORK_XPANID";
-
-    /** Type: String */
-    const String KEY_NETWORK_ROLE             = "android.net.lowpan.property.NETWORK_ROLE";
-
-    /** Type: byte[] */
-    const String KEY_NETWORK_MASTER_KEY       = "android.net.lowpan.property.NETWORK_MASTER_KEY";
-
-    /** Type: Integer */
-    const String KEY_NETWORK_MASTER_KEY_INDEX
-        = "android.net.lowpan.property.NETWORK_MASTER_KEY_INDEX";
-
-    /** Type: int[] */
-    const String KEY_SUPPORTED_CHANNELS = "android.net.lowpan.property.SUPPORTED_CHANNELS";
-
-    /** Type: Integer */
-    const String KEY_CHANNEL            = "android.net.lowpan.property.CHANNEL";
-
-    /** Type: int[] */
+    /**
+     * Channel mask key.
+     * Used for setting a channel mask when starting a scan.
+     * Type: int[]
+     * */
     const String KEY_CHANNEL_MASK       = "android.net.lowpan.property.CHANNEL_MASK";
 
-    /** Type: Integer */
+    /**
+     * Max Transmit Power Key.
+     * Used for setting the maximum transmit power when starting a network scan.
+     * Type: Integer
+     * */
     const String KEY_MAX_TX_POWER       = "android.net.lowpan.property.MAX_TX_POWER";
 
-    /** Type: Integer */
-    const String KEY_RSSI               = "android.net.lowpan.property.RSSI";
-
-    /** Type: Integer */
-    const String KEY_LQI                = "android.net.lowpan.property.LQI";
-
-    /** Type: byte[] */
-    const String KEY_BEACON_ADDRESS     = "android.net.lowpan.property.BEACON_ORIGIN_ADDRESS";
-
-    /** Type: Boolean */
-    const String KEY_BEACON_CAN_ASSIST  = "android.net.lowpan.property.BEACON_CAN_ASSIST";
-
-    /** Type: String */
-    const String DRIVER_VERSION         = "android.net.lowpan.property.DRIVER_VERSION";
-
-    /** Type: String */
-    const String NCP_VERSION            = "android.net.lowpan.property.NCP_VERSION";
-
-    /** Type: byte[]
-     * @hide */
-    const String KEY_EXTENDED_ADDRESS = "android.net.lowpan.property.EXTENDED_ADDRESS";
-
-    /** Type: byte[]
-     * @hide */
-    const String KEY_MAC_ADDRESS      = "android.net.lowpan.property.MAC_ADDRESS";
-
-    //////////////////////////////////////////////////////////////////////////
     // Interface States
 
     const String STATE_OFFLINE = "offline";
@@ -121,58 +57,87 @@
     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";
+    const String ROLE_LEADER = "leader";
+    const String ROLE_COORDINATOR = "coordinator";
+    const String ROLE_DETACHED = "detached";
 
-    //////////////////////////////////////////////////////////////////////////
+    const String NETWORK_TYPE_UNKNOWN = "unknown";
+
+    /**
+     * Network type for Thread 1.x networks.
+     *
+     * @see android.net.lowpan.LowpanIdentity#getType
+     * @see #getLowpanIdentity
+     */
+    const String NETWORK_TYPE_THREAD_V1 = "org.threadgroup.thread.v1";
+
     // 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;
+    const int ERROR_TIMEOUT = 5;
+    const int ERROR_IO_FAILURE = 6;
+    const int ERROR_NCP_PROBLEM = 7;
+    const int ERROR_BUSY = 8;
+    const int ERROR_ALREADY = 9;
+    const int ERROR_CANCELED = 10;
+    const int ERROR_FEATURE_NOT_SUPPORTED = 11;
+    const int ERROR_JOIN_FAILED_UNKNOWN = 12;
+    const int ERROR_JOIN_FAILED_AT_SCAN = 13;
+    const int ERROR_JOIN_FAILED_AT_AUTH = 14;
+    const int ERROR_FORM_FAILED_AT_SCAN = 15;
 
-    //////////////////////////////////////////////////////////////////////////
     // Methods
 
     @utf8InCpp String getName();
 
-    void join(in Map parameters);
-    void form(in Map parameters);
+    @utf8InCpp String getNcpVersion();
+    @utf8InCpp String getDriverVersion();
+    LowpanChannelInfo[] getSupportedChannels();
+    @utf8InCpp String[] getSupportedNetworkTypes();
+    byte[] getMacAddress();
+
+    boolean isEnabled();
+    void setEnabled(boolean enabled);
+
+    boolean isUp();
+    boolean isCommissioned();
+    boolean isConnected();
+    @utf8InCpp String getState();
+
+    @utf8InCpp String getRole();
+    @utf8InCpp String getPartitionId();
+    byte[] getExtendedAddress();
+
+    LowpanIdentity getLowpanIdentity();
+    LowpanCredential getLowpanCredential();
+
+    @utf8InCpp String[] getLinkAddresses();
+    IpPrefix[] getLinkNetworks();
+
+    void join(in LowpanProvision provision);
+    void form(in LowpanProvision provision);
+    void attach(in LowpanProvision provision);
     void leave();
     void reset();
 
+    void startCommissioningSession(in LowpanBeaconInfo beaconInfo);
+    void closeCommissioningSession();
+    oneway void sendToCommissioner(in byte[] packet);
+
     void beginLowPower();
-    void pollForData();
+    oneway 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);
 
@@ -182,14 +147,9 @@
     void startEnergyScan(in Map properties, ILowpanEnergyScanCallback listener);
     oneway void stopEnergyScan();
 
-    String[] copyLinkAddresses();
-    IpPrefix[] copyLinkNetworks();
-
     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
index 0ae01a1..5e4049a 100644
--- a/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl
+++ b/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl
@@ -17,14 +17,29 @@
 package android.net.lowpan;
 
 import android.net.IpPrefix;
+import android.net.lowpan.LowpanIdentity;
 
 /** {@hide} */
 interface ILowpanInterfaceListener {
-    oneway void onPropertiesChanged(in Map properties);
+    oneway void onEnabledChanged(boolean value);
 
-    oneway void onLinkNetworkAdded(in IpPrefix prefix);
-    oneway void onLinkNetworkRemoved(in IpPrefix prefix);
+    oneway void onConnectedChanged(boolean value);
 
-    oneway void onLinkAddressAdded(in String address);
-    oneway void onLinkAddressRemoved(in String address);
+    oneway void onUpChanged(boolean value);
+
+    oneway void onRoleChanged(@utf8InCpp String value);
+
+    oneway void onStateChanged(@utf8InCpp String value);
+
+    oneway void onLowpanIdentityChanged(in LowpanIdentity value);
+
+    oneway void onLinkNetworkAdded(in IpPrefix value);
+
+    oneway void onLinkNetworkRemoved(in IpPrefix value);
+
+    oneway void onLinkAddressAdded(@utf8InCpp String value);
+
+    oneway void onLinkAddressRemoved(@utf8InCpp String value);
+
+    oneway void onReceiveFromCommissioner(in byte[] packet);
 }
diff --git a/lowpan/java/android/net/lowpan/ILowpanNetScanCallback.aidl b/lowpan/java/android/net/lowpan/ILowpanNetScanCallback.aidl
index c20a6f8..9743fce 100644
--- a/lowpan/java/android/net/lowpan/ILowpanNetScanCallback.aidl
+++ b/lowpan/java/android/net/lowpan/ILowpanNetScanCallback.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * 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.
@@ -16,8 +16,10 @@
 
 package android.net.lowpan;
 
+import android.net.lowpan.LowpanBeaconInfo;
+
 /** {@hide} */
 interface ILowpanNetScanCallback {
-    oneway void onNetScanBeacon(in Map parameters);
+    oneway void onNetScanBeacon(in LowpanBeaconInfo beacon);
     oneway void onNetScanFinished();
 }
diff --git a/lowpan/java/android/net/lowpan/InterfaceDisabledException.java b/lowpan/java/android/net/lowpan/InterfaceDisabledException.java
new file mode 100644
index 0000000..e917d45
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/InterfaceDisabledException.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+/**
+ * Exception indicating this operation requires the interface to be enabled.
+ *
+ * @see LowpanInterface
+ * @hide
+ */
+// @SystemApi
+public class InterfaceDisabledException extends LowpanException {
+
+    public InterfaceDisabledException() {}
+
+    public InterfaceDisabledException(String message) {
+        super(message);
+    }
+
+    public InterfaceDisabledException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    protected InterfaceDisabledException(Exception cause) {
+        super(cause);
+    }
+}
diff --git a/lowpan/java/android/net/lowpan/JoinFailedAtAuthException.java b/lowpan/java/android/net/lowpan/JoinFailedAtAuthException.java
new file mode 100644
index 0000000..7aceb71
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/JoinFailedAtAuthException.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+/**
+ * Exception indicating the join operation was unable to find the given network.
+ *
+ * @see LowpanInterface
+ * @hide
+ */
+// @SystemApi
+public class JoinFailedAtAuthException extends JoinFailedException {
+
+    public JoinFailedAtAuthException() {}
+
+    public JoinFailedAtAuthException(String message) {
+        super(message);
+    }
+
+    public JoinFailedAtAuthException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public JoinFailedAtAuthException(Exception cause) {
+        super(cause);
+    }
+}
diff --git a/lowpan/java/android/net/lowpan/JoinFailedAtScanException.java b/lowpan/java/android/net/lowpan/JoinFailedAtScanException.java
new file mode 100644
index 0000000..a4346f98
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/JoinFailedAtScanException.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+/**
+ * Exception indicating the join operation was unable to find the given network.
+ *
+ * @see LowpanInterface
+ * @hide
+ */
+// @SystemApi
+public class JoinFailedAtScanException extends JoinFailedException {
+
+    public JoinFailedAtScanException() {}
+
+    public JoinFailedAtScanException(String message) {
+        super(message);
+    }
+
+    public JoinFailedAtScanException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public JoinFailedAtScanException(Exception cause) {
+        super(cause);
+    }
+}
diff --git a/lowpan/java/android/net/lowpan/JoinFailedException.java b/lowpan/java/android/net/lowpan/JoinFailedException.java
new file mode 100644
index 0000000..e51d382
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/JoinFailedException.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+/**
+ * Exception indicating the join operation has failed.
+ *
+ * @see LowpanInterface
+ * @hide
+ */
+// @SystemApi
+public class JoinFailedException extends LowpanException {
+
+    public JoinFailedException() {}
+
+    public JoinFailedException(String message) {
+        super(message);
+    }
+
+    public JoinFailedException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    protected JoinFailedException(Exception cause) {
+        super(cause);
+    }
+}
diff --git a/lowpan/java/android/net/lowpan/LowpanBeaconInfo.aidl b/lowpan/java/android/net/lowpan/LowpanBeaconInfo.aidl
new file mode 100644
index 0000000..9464fea
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanBeaconInfo.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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;
+
+parcelable LowpanBeaconInfo cpp_header "android/net/lowpan/LowpanBeaconInfo.h";
diff --git a/lowpan/java/android/net/lowpan/LowpanBeaconInfo.java b/lowpan/java/android/net/lowpan/LowpanBeaconInfo.java
index e18abd5..345a4c5 100644
--- a/lowpan/java/android/net/lowpan/LowpanBeaconInfo.java
+++ b/lowpan/java/android/net/lowpan/LowpanBeaconInfo.java
@@ -16,9 +16,12 @@
 
 package android.net.lowpan;
 
+import android.os.Parcel;
+import android.os.Parcelable;
 import com.android.internal.util.HexDump;
+import java.util.Arrays;
 import java.util.Collection;
-import java.util.Map;
+import java.util.Objects;
 import java.util.TreeSet;
 
 /**
@@ -27,70 +30,93 @@
  * @hide
  */
 // @SystemApi
-public class LowpanBeaconInfo extends LowpanIdentity {
+public class LowpanBeaconInfo implements Parcelable {
+    public static final int UNKNOWN_RSSI = Integer.MAX_VALUE;
+    public static final int UNKNOWN_LQI = 0;
 
-    private int mRssi = UNKNOWN;
-    private int mLqi = UNKNOWN;
+    private LowpanIdentity mIdentity;
+    private int mRssi = UNKNOWN_RSSI;
+    private int mLqi = UNKNOWN_LQI;
     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();
+    /** @hide */
+    public static class Builder {
+        final LowpanIdentity.Builder mIdentityBuilder = new LowpanIdentity.Builder();
+        final LowpanBeaconInfo mBeaconInfo = new LowpanBeaconInfo();
+
+        public Builder setLowpanIdentity(LowpanIdentity x) {
+            mIdentityBuilder.setLowpanIdentity(x);
+            return this;
+        }
+
+        public Builder setName(String x) {
+            mIdentityBuilder.setName(x);
+            return this;
+        }
+
+        public Builder setXpanid(byte x[]) {
+            mIdentityBuilder.setXpanid(x);
+            return this;
+        }
+
+        public Builder setPanid(int x) {
+            mIdentityBuilder.setPanid(x);
+            return this;
+        }
+
+        public Builder setChannel(int x) {
+            mIdentityBuilder.setChannel(x);
+            return this;
+        }
+
+        public Builder setType(String x) {
+            mIdentityBuilder.setType(x);
+            return this;
+        }
 
         public Builder setRssi(int x) {
-            identity.mRssi = x;
+            mBeaconInfo.mRssi = x;
             return this;
         }
 
         public Builder setLqi(int x) {
-            identity.mLqi = x;
+            mBeaconInfo.mLqi = x;
             return this;
         }
 
         public Builder setBeaconAddress(byte x[]) {
-            identity.mBeaconAddress = x.clone();
+            mBeaconInfo.mBeaconAddress = (x != null ? x.clone() : null);
             return this;
         }
 
         public Builder setFlag(int x) {
-            identity.mFlags.add(x);
+            mBeaconInfo.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);
+            mBeaconInfo.mFlags.addAll(x);
             return this;
         }
 
         public LowpanBeaconInfo build() {
-            return identity;
+            mBeaconInfo.mIdentity = mIdentityBuilder.build();
+            if (mBeaconInfo.mBeaconAddress == null) {
+                mBeaconInfo.mBeaconAddress = new byte[0];
+            }
+            return mBeaconInfo;
         }
     }
 
     private LowpanBeaconInfo() {}
 
+    public LowpanIdentity getLowpanIdentity() {
+        return mIdentity;
+    }
+
     public int getRssi() {
         return mRssi;
     }
@@ -112,26 +138,21 @@
     }
 
     @Override
-    void addToMap(Map<String, Object> parameters) {
-        super.addToMap(parameters);
-    }
-
-    @Override
     public String toString() {
         StringBuffer sb = new StringBuffer();
 
-        sb.append(super.toString());
+        sb.append(mIdentity.toString());
 
-        if (mRssi != UNKNOWN) {
-            sb.append(", RSSI: ").append(mRssi);
+        if (mRssi != UNKNOWN_RSSI) {
+            sb.append(", RSSI:").append(mRssi).append("dBm");
         }
 
-        if (mLqi != UNKNOWN) {
-            sb.append(", LQI: ").append(mLqi);
+        if (mLqi != UNKNOWN_LQI) {
+            sb.append(", LQI:").append(mLqi);
         }
 
-        if (mBeaconAddress != null) {
-            sb.append(", BeaconAddress: ").append(HexDump.toHexString(mBeaconAddress));
+        if (mBeaconAddress.length > 0) {
+            sb.append(", BeaconAddress:").append(HexDump.toHexString(mBeaconAddress));
         }
 
         for (Integer flag : mFlags) {
@@ -147,4 +168,67 @@
 
         return sb.toString();
     }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mIdentity, mRssi, mLqi, Arrays.hashCode(mBeaconAddress), mFlags);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof LowpanBeaconInfo)) {
+            return false;
+        }
+        LowpanBeaconInfo rhs = (LowpanBeaconInfo) obj;
+        return mIdentity.equals(rhs.mIdentity)
+                && Arrays.equals(mBeaconAddress, rhs.mBeaconAddress)
+                && mRssi == rhs.mRssi
+                && mLqi == rhs.mLqi
+                && mFlags.equals(rhs.mFlags);
+    }
+
+    /** Implement the Parcelable interface. */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Implement the Parcelable interface. */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        mIdentity.writeToParcel(dest, flags);
+        dest.writeInt(mRssi);
+        dest.writeInt(mLqi);
+        dest.writeByteArray(mBeaconAddress);
+
+        dest.writeInt(mFlags.size());
+        for (Integer val : mFlags) {
+            dest.writeInt(val);
+        }
+    }
+
+    /** Implement the Parcelable interface. */
+    public static final Creator<LowpanBeaconInfo> CREATOR =
+            new Creator<LowpanBeaconInfo>() {
+                public LowpanBeaconInfo createFromParcel(Parcel in) {
+                    Builder builder = new Builder();
+
+                    builder.setLowpanIdentity(LowpanIdentity.CREATOR.createFromParcel(in));
+
+                    builder.setRssi(in.readInt());
+                    builder.setLqi(in.readInt());
+
+                    builder.setBeaconAddress(in.createByteArray());
+
+                    for (int i = in.readInt(); i > 0; i--) {
+                        builder.setFlag(in.readInt());
+                    }
+
+                    return builder.build();
+                }
+
+                public LowpanBeaconInfo[] newArray(int size) {
+                    return new LowpanBeaconInfo[size];
+                }
+            };
 }
diff --git a/lowpan/java/android/net/lowpan/LowpanChannelInfo.aidl b/lowpan/java/android/net/lowpan/LowpanChannelInfo.aidl
new file mode 100644
index 0000000..0676deb
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanChannelInfo.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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;
+
+parcelable LowpanChannelInfo cpp_header "android/net/lowpan/LowpanChannelInfo.h";
diff --git a/lowpan/java/android/net/lowpan/LowpanChannelInfo.java b/lowpan/java/android/net/lowpan/LowpanChannelInfo.java
index 621affe..52b1c6d 100644
--- a/lowpan/java/android/net/lowpan/LowpanChannelInfo.java
+++ b/lowpan/java/android/net/lowpan/LowpanChannelInfo.java
@@ -16,22 +16,66 @@
 
 package android.net.lowpan;
 
-/** Provides detailed information about a given channel. */
+import android.os.Parcel;
+import android.os.Parcelable;
+import java.util.Objects;
+
+/**
+ * Provides detailed information about a given channel.
+ *
+ * @hide
+ */
 // @SystemApi
-public class LowpanChannelInfo {
+public class LowpanChannelInfo implements Parcelable {
 
     public static final int UNKNOWN_POWER = Integer.MAX_VALUE;
+    public static final float UNKNOWN_FREQUENCY = 0.0f;
+    public static final float UNKNOWN_BANDWIDTH = 0.0f;
 
-    // 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 String mName = null;
+    private float mSpectrumCenterFrequency = UNKNOWN_FREQUENCY;
+    private float mSpectrumBandwidth = UNKNOWN_BANDWIDTH;
     private int mMaxTransmitPower = UNKNOWN_POWER;
+    private boolean mIsMaskedByRegulatoryDomain = false;
 
-    // Public Getters and Setters
+    /** @hide */
+    public static LowpanChannelInfo getChannelInfoForIeee802154Page0(int index) {
+        LowpanChannelInfo info = new LowpanChannelInfo();
+
+        if (index < 0) {
+            info = null;
+
+        } else if (index == 0) {
+            info.mSpectrumCenterFrequency = 868300000.0f;
+            info.mSpectrumBandwidth = 600000.0f;
+
+        } else if (index < 11) {
+            info.mSpectrumCenterFrequency = 906000000.0f - (2000000.0f * 1) + 2000000.0f * (index);
+            info.mSpectrumBandwidth = 0; // Unknown
+
+        } else if (index < 26) {
+            info.mSpectrumCenterFrequency =
+                    2405000000.0f - (5000000.0f * 11) + 5000000.0f * (index);
+            info.mSpectrumBandwidth = 2000000.0f;
+
+        } else {
+            info = null;
+        }
+
+        info.mName = Integer.toString(index);
+
+        return info;
+    }
+
+    private LowpanChannelInfo() {}
+
+    private LowpanChannelInfo(int index, String name, float cf, float bw) {
+        mIndex = index;
+        mName = name;
+        mSpectrumCenterFrequency = cf;
+        mSpectrumBandwidth = bw;
+    }
 
     public String getName() {
         return mName;
@@ -63,22 +107,110 @@
 
         sb.append("Channel ").append(mIndex);
 
-        if (mName != null) {
+        if (mName != null && !mName.equals(Integer.toString(mIndex))) {
             sb.append(" (").append(mName).append(")");
         }
 
         if (mSpectrumCenterFrequency > 0.0f) {
-            sb.append(", SpectrumCenterFrequency: ").append(mSpectrumCenterFrequency).append("Hz");
+            if (mSpectrumCenterFrequency > 1000000000.0f) {
+                sb.append(", SpectrumCenterFrequency: ")
+                        .append(mSpectrumCenterFrequency / 1000000000.0f)
+                        .append("GHz");
+            } else if (mSpectrumCenterFrequency > 1000000.0f) {
+                sb.append(", SpectrumCenterFrequency: ")
+                        .append(mSpectrumCenterFrequency / 1000000.0f)
+                        .append("MHz");
+            } else {
+                sb.append(", SpectrumCenterFrequency: ")
+                        .append(mSpectrumCenterFrequency / 1000.0f)
+                        .append("kHz");
+            }
         }
 
         if (mSpectrumBandwidth > 0.0f) {
-            sb.append(", SpectrumBandwidth: ").append(mSpectrumBandwidth).append("Hz");
+            if (mSpectrumBandwidth > 1000000000.0f) {
+                sb.append(", SpectrumBandwidth: ")
+                        .append(mSpectrumBandwidth / 1000000000.0f)
+                        .append("GHz");
+            } else if (mSpectrumBandwidth > 1000000.0f) {
+                sb.append(", SpectrumBandwidth: ")
+                        .append(mSpectrumBandwidth / 1000000.0f)
+                        .append("MHz");
+            } else {
+                sb.append(", SpectrumBandwidth: ")
+                        .append(mSpectrumBandwidth / 1000.0f)
+                        .append("kHz");
+            }
         }
 
         if (mMaxTransmitPower != UNKNOWN_POWER) {
-            sb.append(", MaxTransmitPower: ").append(mMaxTransmitPower);
+            sb.append(", MaxTransmitPower: ").append(mMaxTransmitPower).append("dBm");
         }
 
         return sb.toString();
     }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof LowpanChannelInfo)) {
+            return false;
+        }
+        LowpanChannelInfo rhs = (LowpanChannelInfo) obj;
+        return Objects.equals(mName, rhs.mName)
+                && mIndex == rhs.mIndex
+                && mIsMaskedByRegulatoryDomain == rhs.mIsMaskedByRegulatoryDomain
+                && mSpectrumCenterFrequency == rhs.mSpectrumCenterFrequency
+                && mSpectrumBandwidth == rhs.mSpectrumBandwidth
+                && mMaxTransmitPower == rhs.mMaxTransmitPower;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+                mName,
+                mIndex,
+                mIsMaskedByRegulatoryDomain,
+                mSpectrumCenterFrequency,
+                mSpectrumBandwidth,
+                mMaxTransmitPower);
+    }
+
+    /** Implement the Parcelable interface. */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Implement the Parcelable interface. */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mIndex);
+        dest.writeString(mName);
+        dest.writeFloat(mSpectrumCenterFrequency);
+        dest.writeFloat(mSpectrumBandwidth);
+        dest.writeInt(mMaxTransmitPower);
+        dest.writeBoolean(mIsMaskedByRegulatoryDomain);
+    }
+
+    /** Implement the Parcelable interface. */
+    public static final Creator<LowpanChannelInfo> CREATOR =
+            new Creator<LowpanChannelInfo>() {
+
+                public LowpanChannelInfo createFromParcel(Parcel in) {
+                    LowpanChannelInfo info = new LowpanChannelInfo();
+
+                    info.mIndex = in.readInt();
+                    info.mName = in.readString();
+                    info.mSpectrumCenterFrequency = in.readFloat();
+                    info.mSpectrumBandwidth = in.readFloat();
+                    info.mMaxTransmitPower = in.readInt();
+                    info.mIsMaskedByRegulatoryDomain = in.readBoolean();
+
+                    return info;
+                }
+
+                public LowpanChannelInfo[] newArray(int size) {
+                    return new LowpanChannelInfo[size];
+                }
+            };
 }
diff --git a/lowpan/java/android/net/lowpan/LowpanCommissioningSession.java b/lowpan/java/android/net/lowpan/LowpanCommissioningSession.java
index ee75f1a..8f75e8d 100644
--- a/lowpan/java/android/net/lowpan/LowpanCommissioningSession.java
+++ b/lowpan/java/android/net/lowpan/LowpanCommissioningSession.java
@@ -18,9 +18,11 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.net.Network;
+import android.net.IpPrefix;
+import android.os.DeadObjectException;
 import android.os.Handler;
-import java.net.InetSocketAddress;
+import android.os.Looper;
+import android.os.RemoteException;
 
 /**
  * Commissioning Session.
@@ -31,8 +33,15 @@
  * @hide
  */
 // @SystemApi
-public abstract class LowpanCommissioningSession {
-    public LowpanCommissioningSession() {}
+public class LowpanCommissioningSession {
+
+    private final ILowpanInterface mBinder;
+    private final LowpanBeaconInfo mBeaconInfo;
+    private final ILowpanInterfaceListener mInternalCallback = new InternalCallback();
+    private final Looper mLooper;
+    private Handler mHandler;
+    private Callback mCallback = null;
+    private volatile boolean mIsClosed = false;
 
     /**
      * Callback base class for {@link LowpanCommissioningSession}
@@ -40,34 +49,175 @@
      * @hide
      */
     // @SystemApi
-    public static abstract class Callback {
+    public abstract static class Callback {
         public void onReceiveFromCommissioner(@NonNull byte[] packet) {};
 
         public void onClosed() {};
     }
 
+    private class InternalCallback extends ILowpanInterfaceListener.Stub {
+        @Override
+        public void onStateChanged(String value) {
+            if (!mIsClosed) {
+                switch (value) {
+                    case ILowpanInterface.STATE_OFFLINE:
+                    case ILowpanInterface.STATE_FAULT:
+                        synchronized (LowpanCommissioningSession.this) {
+                            lockedCleanup();
+                        }
+                }
+            }
+        }
+
+        @Override
+        public void onReceiveFromCommissioner(byte[] packet) {
+            mHandler.post(
+                    () -> {
+                        synchronized (LowpanCommissioningSession.this) {
+                            if (!mIsClosed && (mCallback != null)) {
+                                mCallback.onReceiveFromCommissioner(packet);
+                            }
+                        }
+                    });
+        }
+
+        // We ignore all other callbacks.
+        @Override
+        public void onEnabledChanged(boolean value) {}
+
+        @Override
+        public void onConnectedChanged(boolean value) {}
+
+        @Override
+        public void onUpChanged(boolean value) {}
+
+        @Override
+        public void onRoleChanged(String value) {}
+
+        @Override
+        public void onLowpanIdentityChanged(LowpanIdentity value) {}
+
+        @Override
+        public void onLinkNetworkAdded(IpPrefix value) {}
+
+        @Override
+        public void onLinkNetworkRemoved(IpPrefix value) {}
+
+        @Override
+        public void onLinkAddressAdded(String value) {}
+
+        @Override
+        public void onLinkAddressRemoved(String value) {}
+    }
+
+    LowpanCommissioningSession(
+            ILowpanInterface binder, LowpanBeaconInfo beaconInfo, Looper looper) {
+        mBinder = binder;
+        mBeaconInfo = beaconInfo;
+        mLooper = looper;
+
+        if (mLooper != null) {
+            mHandler = new Handler(mLooper);
+        } else {
+            mHandler = new Handler();
+        }
+
+        try {
+            mBinder.addListener(mInternalCallback);
+
+        } catch (RemoteException x) {
+            throw x.rethrowAsRuntimeException();
+        }
+    }
+
+    private void lockedCleanup() {
+        // Note: this method is only called from synchronized contexts.
+
+        if (!mIsClosed) {
+            try {
+                mBinder.removeListener(mInternalCallback);
+
+            } catch (DeadObjectException x) {
+                /* We don't care if we receive a DOE at this point.
+                 * DOE is as good as success as far as we are concerned.
+                 */
+
+            } catch (RemoteException x) {
+                throw x.rethrowAsRuntimeException();
+            }
+
+            if (mCallback != null) {
+                mHandler.post(() -> mCallback.onClosed());
+            }
+        }
+
+        mCallback = null;
+        mIsClosed = true;
+    }
+
     /** TODO: doc */
     @NonNull
-    public abstract LowpanBeaconInfo getBeaconInfo();
+    public LowpanBeaconInfo getBeaconInfo() {
+        return mBeaconInfo;
+    }
 
     /** TODO: doc */
-    public abstract void sendToCommissioner(@NonNull byte[] packet);
+    public void sendToCommissioner(@NonNull byte[] packet) {
+        if (!mIsClosed) {
+            try {
+                mBinder.sendToCommissioner(packet);
+
+            } catch (DeadObjectException x) {
+                /* This method is a best-effort delivery.
+                 * We don't care if we receive a DOE at this point.
+                 */
+
+            } catch (RemoteException x) {
+                throw x.rethrowAsRuntimeException();
+            }
+        }
+    }
 
     /** TODO: doc */
-    public abstract void setCallback(@Nullable Callback cb, @Nullable Handler handler);
+    public synchronized void setCallback(@Nullable Callback cb, @Nullable Handler handler) {
+        if (!mIsClosed) {
+            /* This class can be created with or without a default looper.
+             * Also, this method can be called with or without a specific
+             * handler. If a handler is specified, it is to always be used.
+             * Otherwise, if there was a Looper specified when this object
+             * was created, we create a new handle based on that looper.
+             * Otherwise we just create a default handler object. Since we
+             * don't really know how the previous handler was created, we
+             * end up always replacing it here. This isn't a huge problem
+             * because this method should be called infrequently.
+             */
+            if (handler != null) {
+                mHandler = handler;
+            } else if (mLooper != null) {
+                mHandler = new Handler(mLooper);
+            } else {
+                mHandler = new Handler();
+            }
+            mCallback = cb;
+        }
+    }
 
     /** TODO: doc */
-    public abstract void close();
+    public synchronized void close() {
+        if (!mIsClosed) {
+            try {
+                mBinder.closeCommissioningSession();
 
-    /**
-     * 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();
+                lockedCleanup();
 
-    public abstract @Nullable Network getNetwork();
+            } catch (DeadObjectException x) {
+                /* We don't care if we receive a DOE at this point.
+                 * DOE is as good as success as far as we are concerned.
+                 */
+
+            } catch (RemoteException x) {
+                throw x.rethrowAsRuntimeException();
+            }
+        }
+    }
 }
diff --git a/lowpan/java/android/net/lowpan/LowpanCredential.aidl b/lowpan/java/android/net/lowpan/LowpanCredential.aidl
new file mode 100644
index 0000000..af0c2d6
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanCredential.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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;
+
+parcelable LowpanCredential cpp_header "android/net/lowpan/LowpanCredential.h";
diff --git a/lowpan/java/android/net/lowpan/LowpanCredential.java b/lowpan/java/android/net/lowpan/LowpanCredential.java
index ca860215..e9126f9 100644
--- a/lowpan/java/android/net/lowpan/LowpanCredential.java
+++ b/lowpan/java/android/net/lowpan/LowpanCredential.java
@@ -16,7 +16,11 @@
 
 package android.net.lowpan;
 
-import java.util.Map;
+import android.os.Parcel;
+import android.os.Parcelable;
+import com.android.internal.util.HexDump;
+import java.util.Arrays;
+import java.util.Objects;
 
 /**
  * Describes a credential for a LoWPAN network.
@@ -24,7 +28,7 @@
  * @hide
  */
 // @SystemApi
-public class LowpanCredential {
+public class LowpanCredential implements Parcelable {
 
     public static final int UNSPECIFIED_KEY_INDEX = 0;
 
@@ -49,18 +53,18 @@
         return new LowpanCredential(masterKey, keyIndex);
     }
 
-    public void setMasterKey(byte[] masterKey) {
+    void setMasterKey(byte[] masterKey) {
         if (masterKey != null) {
             masterKey = masterKey.clone();
         }
         mMasterKey = masterKey;
     }
 
-    public void setMasterKeyIndex(int keyIndex) {
+    void setMasterKeyIndex(int keyIndex) {
         mMasterKeyIndex = keyIndex;
     }
 
-    public void setMasterKey(byte[] masterKey, int keyIndex) {
+    void setMasterKey(byte[] masterKey, int keyIndex) {
         setMasterKey(masterKey);
         setMasterKeyIndex(keyIndex);
     }
@@ -80,12 +84,89 @@
         return mMasterKey != null;
     }
 
-    void addToMap(Map<String, Object> parameters) throws LowpanException {
+    public String toSensitiveString() {
+        StringBuffer sb = new StringBuffer();
+
+        sb.append("<LowpanCredential");
+
         if (isMasterKey()) {
-            LowpanProperties.KEY_NETWORK_MASTER_KEY.putInMap(parameters, getMasterKey());
-            LowpanProperties.KEY_NETWORK_MASTER_KEY_INDEX.putInMap(parameters, getMasterKeyIndex());
+            sb.append(" MasterKey:").append(HexDump.toHexString(mMasterKey));
+            if (mMasterKeyIndex != UNSPECIFIED_KEY_INDEX) {
+                sb.append(", Index:").append(mMasterKeyIndex);
+            }
         } else {
-            throw new LowpanException("Unsupported Network Credential");
+            sb.append(" empty");
         }
+
+        sb.append(">");
+
+        return sb.toString();
     }
+
+    @Override
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+
+        sb.append("<LowpanCredential");
+
+        if (isMasterKey()) {
+            // We don't print out the contents of the key here,
+            // we only do that in toSensitiveString.
+            sb.append(" MasterKey");
+            if (mMasterKeyIndex != UNSPECIFIED_KEY_INDEX) {
+                sb.append(", Index:").append(mMasterKeyIndex);
+            }
+        } else {
+            sb.append(" empty");
+        }
+
+        sb.append(">");
+
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof LowpanCredential)) {
+            return false;
+        }
+        LowpanCredential rhs = (LowpanCredential) obj;
+        return Arrays.equals(mMasterKey, rhs.mMasterKey) && mMasterKeyIndex == rhs.mMasterKeyIndex;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(Arrays.hashCode(mMasterKey), mMasterKeyIndex);
+    }
+
+    /** Implement the Parcelable interface. */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Implement the Parcelable interface. */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeByteArray(mMasterKey);
+        dest.writeInt(mMasterKeyIndex);
+    }
+
+    /** Implement the Parcelable interface. */
+    public static final Creator<LowpanCredential> CREATOR =
+            new Creator<LowpanCredential>() {
+
+                public LowpanCredential createFromParcel(Parcel in) {
+                    LowpanCredential credential = new LowpanCredential();
+
+                    credential.mMasterKey = in.createByteArray();
+                    credential.mMasterKeyIndex = in.readInt();
+
+                    return credential;
+                }
+
+                public LowpanCredential[] newArray(int size) {
+                    return new LowpanCredential[size];
+                }
+            };
 }
diff --git a/lowpan/java/android/net/lowpan/LowpanEnergyScanResult.java b/lowpan/java/android/net/lowpan/LowpanEnergyScanResult.java
index 91ed19c..da87752 100644
--- a/lowpan/java/android/net/lowpan/LowpanEnergyScanResult.java
+++ b/lowpan/java/android/net/lowpan/LowpanEnergyScanResult.java
@@ -28,7 +28,7 @@
     private int mChannel = UNKNOWN;
     private int mMaxRssi = UNKNOWN;
 
-    public LowpanEnergyScanResult() {}
+    LowpanEnergyScanResult() {}
 
     public int getChannel() {
         return mChannel;
@@ -38,11 +38,11 @@
         return mMaxRssi;
     }
 
-    public void setChannel(int x) {
+    void setChannel(int x) {
         mChannel = x;
     }
 
-    public void setMaxRssi(int x) {
+    void setMaxRssi(int x) {
         mMaxRssi = x;
     }
 
diff --git a/lowpan/java/android/net/lowpan/LowpanException.java b/lowpan/java/android/net/lowpan/LowpanException.java
index 5a1f729..5dfce48 100644
--- a/lowpan/java/android/net/lowpan/LowpanException.java
+++ b/lowpan/java/android/net/lowpan/LowpanException.java
@@ -28,245 +28,65 @@
  */
 // @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;
-
-    public static LowpanException rethrowAsLowpanException(ServiceSpecificException e)
-            throws LowpanException {
-        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);
-    }
-
-    private final int mReason;
-
-    public final int getReason() {
-        return mReason;
-    }
-
-    public LowpanException(int problem) {
-        super(getDefaultMessage(problem));
-        mReason = problem;
-    }
+    public LowpanException() {}
 
     public LowpanException(String message) {
-        super(getCombinedMessage(LOWPAN_ERROR, message));
-        mReason = LOWPAN_ERROR;
+        super(message);
     }
 
-    public LowpanException(int problem, String message, Throwable cause) {
-        super(getCombinedMessage(problem, message), cause);
-        mReason = problem;
+    public LowpanException(String message, Throwable cause) {
+        super(message, cause);
     }
 
-    public LowpanException(int problem, Throwable cause) {
-        super(getDefaultMessage(problem), cause);
-        mReason = problem;
+    public LowpanException(Exception cause) {
+        super(cause);
     }
 
-    /** @hide */
-    public static String getDefaultMessage(int problem) {
-        String problemString;
+    /* This method returns LowpanException so that the caller
+     * can add "throw" before the invocation of this method.
+     * This might seem superfluous, but it is actually to
+     * help provide a hint to the java compiler that this
+     * function will not return.
+     */
+    static LowpanException rethrowFromServiceSpecificException(ServiceSpecificException e)
+            throws LowpanException {
+        switch (e.errorCode) {
+            case ILowpanInterface.ERROR_DISABLED:
+                throw new InterfaceDisabledException(e);
 
-        // TODO: Does this need localization?
+            case ILowpanInterface.ERROR_WRONG_STATE:
+                throw new WrongStateException(e);
 
-        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:
+            case ILowpanInterface.ERROR_CANCELED:
+                throw new OperationCanceledException(e);
+
+            case ILowpanInterface.ERROR_JOIN_FAILED_UNKNOWN:
+                throw new JoinFailedException(e);
+
+            case ILowpanInterface.ERROR_JOIN_FAILED_AT_SCAN:
+                throw new JoinFailedAtScanException(e);
+
+            case ILowpanInterface.ERROR_JOIN_FAILED_AT_AUTH:
+                throw new JoinFailedAtAuthException(e);
+
+            case ILowpanInterface.ERROR_FORM_FAILED_AT_SCAN:
+                throw new NetworkAlreadyExistsException(e);
+
+            case ILowpanInterface.ERROR_FEATURE_NOT_SUPPORTED:
+                throw new LowpanException(
+                        e.getMessage() != null ? e.getMessage() : "Feature not supported", e);
+
+            case ILowpanInterface.ERROR_NCP_PROBLEM:
+                throw new LowpanRuntimeException(
+                        e.getMessage() != null ? e.getMessage() : "NCP problem", e);
+
+            case ILowpanInterface.ERROR_INVALID_ARGUMENT:
+                throw new LowpanRuntimeException(
+                        e.getMessage() != null ? e.getMessage() : "Invalid argument", e);
+
+            case ILowpanInterface.ERROR_UNSPECIFIED:
             default:
-                problemString = "The requested LoWPAN operation failed";
-                break;
+                throw new LowpanRuntimeException(e);
         }
-
-        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.aidl b/lowpan/java/android/net/lowpan/LowpanIdentity.aidl
new file mode 100644
index 0000000..fcef98f
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanIdentity.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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;
+
+parcelable LowpanIdentity cpp_header "android/net/lowpan/LowpanIdentity.h";
diff --git a/lowpan/java/android/net/lowpan/LowpanIdentity.java b/lowpan/java/android/net/lowpan/LowpanIdentity.java
index 2d36f7f..6cb1f98 100644
--- a/lowpan/java/android/net/lowpan/LowpanIdentity.java
+++ b/lowpan/java/android/net/lowpan/LowpanIdentity.java
@@ -16,8 +16,16 @@
 
 package android.net.lowpan;
 
+import android.annotation.NonNull;
+import android.icu.text.StringPrep;
+import android.icu.text.StringPrepParseException;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
 import com.android.internal.util.HexDump;
-import java.util.Map;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Objects;
 
 /**
  * Describes an instance of a LoWPAN network.
@@ -25,76 +33,100 @@
  * @hide
  */
 // @SystemApi
-public class LowpanIdentity {
+public class LowpanIdentity implements Parcelable {
+    private static final String TAG = LowpanIdentity.class.getSimpleName();
 
     // 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;
-
+    public static final int UNSPECIFIED_CHANNEL = -1;
+    public static final int UNSPECIFIED_PANID = 0xFFFFFFFF;
     // Builder
 
     /** @hide */
     // @SystemApi
     public static class Builder {
-        private final LowpanIdentity identity = new LowpanIdentity();
+        private static final StringPrep stringPrep =
+                StringPrep.getInstance(StringPrep.RFC3920_RESOURCEPREP);
 
-        public Builder setName(String x) {
-            identity.mName = x;
+        final LowpanIdentity mIdentity = new LowpanIdentity();
+
+        private static String escape(@NonNull byte[] bytes) {
+            StringBuffer sb = new StringBuffer();
+            for (byte b : bytes) {
+                if (b >= 32 && b <= 126) {
+                    sb.append((char) b);
+                } else {
+                    sb.append(String.format("\\0x%02x", b & 0xFF));
+                }
+            }
+            return sb.toString();
+        }
+
+        public Builder setLowpanIdentity(@NonNull LowpanIdentity x) {
+            Objects.requireNonNull(x);
+            setRawName(x.getRawName());
+            setXpanid(x.getXpanid());
+            setPanid(x.getPanid());
+            setChannel(x.getChannel());
+            setType(x.getType());
+            return this;
+        }
+
+        public Builder setName(@NonNull String name) {
+            Objects.requireNonNull(name);
+            try {
+                mIdentity.mName = stringPrep.prepare(name, StringPrep.DEFAULT);
+                mIdentity.mRawName = mIdentity.mName.getBytes(StandardCharsets.UTF_8);
+                mIdentity.mIsNameValid = true;
+            } catch (StringPrepParseException x) {
+                Log.w(TAG, x.toString());
+                setRawName(name.getBytes(StandardCharsets.UTF_8));
+            }
+            return this;
+        }
+
+        public Builder setRawName(@NonNull byte[] name) {
+            Objects.requireNonNull(name);
+            mIdentity.mRawName = name.clone();
+            mIdentity.mName = new String(name, StandardCharsets.UTF_8);
+            try {
+                String nameCheck = stringPrep.prepare(mIdentity.mName, StringPrep.DEFAULT);
+                mIdentity.mIsNameValid =
+                        Arrays.equals(nameCheck.getBytes(StandardCharsets.UTF_8), name);
+            } catch (StringPrepParseException x) {
+                Log.w(TAG, x.toString());
+                mIdentity.mIsNameValid = false;
+            }
+
+            // Non-normal names must be rendered differently to avoid confusion.
+            if (!mIdentity.mIsNameValid) {
+                mIdentity.mName = "«" + escape(name) + "»";
+            }
+
             return this;
         }
 
         public Builder setXpanid(byte x[]) {
-            identity.mXpanid = (x != null ? x.clone() : null);
+            mIdentity.mXpanid = (x != null ? x.clone() : null);
             return this;
         }
 
         public Builder setPanid(int x) {
-            identity.mPanid = x;
+            mIdentity.mPanid = x;
             return this;
         }
 
-        /** @hide */
-        public Builder setType(int x) {
-            identity.mType = x;
+        public Builder setType(@NonNull String x) {
+            mIdentity.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));
-            }
+            mIdentity.mChannel = x;
             return this;
         }
 
         public LowpanIdentity build() {
-            return identity;
+            return mIdentity;
         }
     }
 
@@ -102,28 +134,37 @@
 
     // Instance Variables
 
-    private String mName = null;
-    private byte[] mXpanid = null;
-    private int mType = UNKNOWN;
-    private int mPanid = UNKNOWN;
-    private int mChannel = UNKNOWN;
+    private String mName = "";
+    private boolean mIsNameValid = true;
+    private byte[] mRawName = new byte[0];
+    private String mType = "";
+    private byte[] mXpanid = new byte[0];
+    private int mPanid = UNSPECIFIED_PANID;
+    private int mChannel = UNSPECIFIED_CHANNEL;
 
-    // Public Getters and Setters
+    // Public Getters
 
     public String getName() {
         return mName;
     }
 
+    public boolean isNameValid() {
+        return mIsNameValid;
+    }
+
+    public byte[] getRawName() {
+        return mRawName.clone();
+    }
+
     public byte[] getXpanid() {
-        return mXpanid != null ? mXpanid.clone() : null;
+        return mXpanid.clone();
     }
 
     public int getPanid() {
         return mPanid;
     }
 
-    /** @hide */
-    public int getType() {
+    public String getType() {
         return mType;
     }
 
@@ -131,43 +172,84 @@
         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);
+        sb.append("Name:").append(getName());
 
-        if (mXpanid != null) {
-            sb.append(", XPANID: ").append(HexDump.toHexString(mXpanid));
+        if (mType.length() > 0) {
+            sb.append(", Type:").append(mType);
         }
 
-        if (mPanid != UNKNOWN) {
-            sb.append(", PANID: ").append(String.format("0x%04X", mPanid));
+        if (mXpanid.length > 0) {
+            sb.append(", XPANID:").append(HexDump.toHexString(mXpanid));
         }
 
-        if (mChannel != UNKNOWN) {
-            sb.append(", Channel: ").append(mChannel);
+        if (mPanid != UNSPECIFIED_PANID) {
+            sb.append(", PANID:").append(String.format("0x%04X", mPanid));
+        }
+
+        if (mChannel != UNSPECIFIED_CHANNEL) {
+            sb.append(", Channel:").append(mChannel);
         }
 
         return sb.toString();
     }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof LowpanIdentity)) {
+            return false;
+        }
+        LowpanIdentity rhs = (LowpanIdentity) obj;
+        return Arrays.equals(mRawName, rhs.mRawName)
+                && Arrays.equals(mXpanid, rhs.mXpanid)
+                && mType.equals(rhs.mType)
+                && mPanid == rhs.mPanid
+                && mChannel == rhs.mChannel;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+                Arrays.hashCode(mRawName), mType, Arrays.hashCode(mXpanid), mPanid, mChannel);
+    }
+
+    /** Implement the Parcelable interface. */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Implement the Parcelable interface. */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeByteArray(mRawName);
+        dest.writeString(mType);
+        dest.writeByteArray(mXpanid);
+        dest.writeInt(mPanid);
+        dest.writeInt(mChannel);
+    }
+
+    /** Implement the Parcelable interface. */
+    public static final Creator<LowpanIdentity> CREATOR =
+            new Creator<LowpanIdentity>() {
+
+                public LowpanIdentity createFromParcel(Parcel in) {
+                    Builder builder = new Builder();
+
+                    builder.setRawName(in.createByteArray());
+                    builder.setType(in.readString());
+                    builder.setXpanid(in.createByteArray());
+                    builder.setPanid(in.readInt());
+                    builder.setChannel(in.readInt());
+
+                    return builder.build();
+                }
+
+                public LowpanIdentity[] newArray(int size) {
+                    return new LowpanIdentity[size];
+                }
+            };
 }
diff --git a/lowpan/java/android/net/lowpan/LowpanInterface.java b/lowpan/java/android/net/lowpan/LowpanInterface.java
index 55bf399..57e9135 100644
--- a/lowpan/java/android/net/lowpan/LowpanInterface.java
+++ b/lowpan/java/android/net/lowpan/LowpanInterface.java
@@ -28,8 +28,6 @@
 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.
@@ -41,13 +39,13 @@
     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";
+    public static final String ROLE_DETACHED = ILowpanInterface.ROLE_DETACHED;
 
     /** End-device role. End devices do not route traffic for other nodes. */
-    public static final String ROLE_END_DEVICE = "end-device";
+    public static final String ROLE_END_DEVICE = ILowpanInterface.ROLE_END_DEVICE;
 
     /** Router role. Routers help route traffic around the mesh network. */
-    public static final String ROLE_ROUTER = "router";
+    public static final String ROLE_ROUTER = ILowpanInterface.ROLE_ROUTER;
 
     /**
      * Sleepy End-Device role.
@@ -57,7 +55,7 @@
      * 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";
+    public static final String ROLE_SLEEPY_END_DEVICE = ILowpanInterface.ROLE_SLEEPY_END_DEVICE;
 
     /**
      * Sleepy-router role.
@@ -65,13 +63,13 @@
      * <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";
+    public static final String ROLE_SLEEPY_ROUTER = ILowpanInterface.ROLE_SLEEPY_ROUTER;
 
     /** TODO: doc */
-    public static final String ROLE_LEADER = "leader";
+    public static final String ROLE_LEADER = ILowpanInterface.ROLE_LEADER;
 
     /** TODO: doc */
-    public static final String ROLE_COORDINATOR = "coordinator";
+    public static final String ROLE_COORDINATOR = ILowpanInterface.ROLE_COORDINATOR;
 
     /**
      * Offline state.
@@ -86,7 +84,7 @@
      * @see #getState()
      * @see #STATE_FAULT
      */
-    public static final String STATE_OFFLINE = "offline";
+    public static final String STATE_OFFLINE = ILowpanInterface.STATE_OFFLINE;
 
     /**
      * Commissioning state.
@@ -98,7 +96,7 @@
      * @see #getState()
      * @hide
      */
-    public static final String STATE_COMMISSIONING = "commissioning";
+    public static final String STATE_COMMISSIONING = ILowpanInterface.STATE_COMMISSIONING;
 
     /**
      * Attaching state.
@@ -116,7 +114,7 @@
      * @see #STATE_ATTACHED
      * @see #getState()
      */
-    public static final String STATE_ATTACHING = "attaching";
+    public static final String STATE_ATTACHING = ILowpanInterface.STATE_ATTACHING;
 
     /**
      * Attached state.
@@ -127,7 +125,7 @@
      * @see #STATE_ATTACHING
      * @see #getState()
      */
-    public static final String STATE_ATTACHED = "attached";
+    public static final String STATE_ATTACHED = ILowpanInterface.STATE_ATTACHED;
 
     /**
      * Fault state.
@@ -141,7 +139,7 @@
      * @see #getState
      * @see #STATE_OFFLINE
      */
-    public static final String STATE_FAULT = "fault";
+    public static final String STATE_FAULT = ILowpanInterface.STATE_FAULT;
 
     /**
      * Network type for Thread 1.x networks.
@@ -150,23 +148,9 @@
      * @see #getLowpanIdentity
      * @hide
      */
-    public static final String NETWORK_TYPE_THREAD = "org.threadgroup.thread.v1";
+    public static final String NETWORK_TYPE_THREAD_V1 = ILowpanInterface.NETWORK_TYPE_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()
-    };
+    public static final String EMPTY_PARTITION_ID = "";
 
     /**
      * Callback base class for LowpanInterface
@@ -187,8 +171,13 @@
 
         public void onLowpanIdentityChanged(@NonNull LowpanIdentity value) {}
 
-        /** @hide */
-        public void onPropertiesChanged(@NonNull Map properties) {}
+        public void onLinkNetworkAdded(IpPrefix prefix) {}
+
+        public void onLinkNetworkRemoved(IpPrefix prefix) {}
+
+        public void onLinkAddressAdded(LinkAddress address) {}
+
+        public void onLinkAddressRemoved(LinkAddress address) {}
     }
 
     private final ILowpanInterface mBinder;
@@ -222,74 +211,6 @@
         return mBinder;
     }
 
-    // Private Property Helpers
-
-    void setProperties(Map properties) throws LowpanException {
-        try {
-            mBinder.setProperties(properties);
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowAsLowpanException(x);
-        }
-    }
-
-    @NonNull
-    Map<String, Object> getProperties(String keys[]) throws LowpanException {
-        try {
-            return mBinder.getProperties(keys);
-
-        } catch (RemoteException x) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowAsLowpanException(x);
-        }
-    }
-
-    /** @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) {
-            throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowAsLowpanException(x);
-        }
-    }
-
-    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 : false;
-    }
-
     // Public Actions
 
     /**
@@ -306,15 +227,13 @@
      */
     public void form(@NonNull LowpanProvision provision) throws LowpanException {
         try {
-            Map<String, Object> parameters = new HashMap();
-            provision.addToMap(parameters);
-            mBinder.form(parameters);
+            mBinder.form(provision);
 
         } catch (RemoteException x) {
             throw x.rethrowAsRuntimeException();
 
         } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowAsLowpanException(x);
+            throw LowpanException.rethrowFromServiceSpecificException(x);
         }
     }
 
@@ -328,15 +247,13 @@
      */
     public void join(@NonNull LowpanProvision provision) throws LowpanException {
         try {
-            Map<String, Object> parameters = new HashMap();
-            provision.addToMap(parameters);
-            mBinder.join(parameters);
+            mBinder.join(provision);
 
         } catch (RemoteException x) {
             throw x.rethrowAsRuntimeException();
 
         } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowAsLowpanException(x);
+            throw LowpanException.rethrowFromServiceSpecificException(x);
         }
     }
 
@@ -348,13 +265,14 @@
      * <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);
+        try {
+            mBinder.attach(provision);
+
+        } catch (RemoteException x) {
+            throw x.rethrowAsRuntimeException();
+
+        } catch (ServiceSpecificException x) {
+            throw LowpanException.rethrowFromServiceSpecificException(x);
         }
     }
 
@@ -372,7 +290,7 @@
             throw x.rethrowAsRuntimeException();
 
         } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowAsLowpanException(x);
+            throw LowpanException.rethrowFromServiceSpecificException(x);
         }
     }
 
@@ -382,9 +300,17 @@
      */
     public @NonNull LowpanCommissioningSession startCommissioningSession(
             @NonNull LowpanBeaconInfo beaconInfo) throws LowpanException {
+        try {
+            mBinder.startCommissioningSession(beaconInfo);
 
-        /* TODO: Implement startCommissioningSession */
-        throw new LowpanException(LowpanException.LOWPAN_FEATURE_NOT_SUPPORTED);
+            return new LowpanCommissioningSession(mBinder, beaconInfo, mLooper);
+
+        } catch (RemoteException x) {
+            throw x.rethrowAsRuntimeException();
+
+        } catch (ServiceSpecificException x) {
+            throw LowpanException.rethrowFromServiceSpecificException(x);
+        }
     }
 
     /**
@@ -403,7 +329,7 @@
             throw x.rethrowAsRuntimeException();
 
         } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowAsLowpanException(x);
+            throw LowpanException.rethrowFromServiceSpecificException(x);
         }
     }
 
@@ -415,6 +341,9 @@
         try {
             return mBinder.getName();
 
+        } catch (DeadObjectException x) {
+            return "";
+
         } catch (RemoteException x) {
             throw x.rethrowAsRuntimeException();
         }
@@ -428,9 +357,13 @@
      */
     public boolean isEnabled() {
         try {
-            return getPropertyAsBoolean(LowpanProperties.KEY_INTERFACE_ENABLED);
-        } catch (LowpanException x) {
+            return mBinder.isEnabled();
+
+        } catch (DeadObjectException x) {
             return false;
+
+        } catch (RemoteException x) {
+            throw x.rethrowAsRuntimeException();
         }
     }
 
@@ -444,7 +377,15 @@
      * @hide
      */
     public void setEnabled(boolean enabled) throws LowpanException {
-        setProperty(LowpanProperties.KEY_INTERFACE_ENABLED, enabled);
+        try {
+            mBinder.setEnabled(enabled);
+
+        } catch (RemoteException x) {
+            throw x.rethrowAsRuntimeException();
+
+        } catch (ServiceSpecificException x) {
+            throw LowpanException.rethrowFromServiceSpecificException(x);
+        }
     }
 
     /**
@@ -454,22 +395,14 @@
      */
     public boolean isUp() {
         try {
-            return getPropertyAsBoolean(LowpanProperties.KEY_INTERFACE_UP);
-        } catch (LowpanException x) {
-            return false;
-        }
-    }
+            return mBinder.isUp();
 
-    /**
-     * 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);
+        } catch (DeadObjectException x) {
+            return false;
+
+        } catch (RemoteException x) {
+            throw x.rethrowAsRuntimeException();
+        }
     }
 
     /**
@@ -480,9 +413,13 @@
      */
     public boolean isConnected() {
         try {
-            return getPropertyAsBoolean(LowpanProperties.KEY_INTERFACE_CONNECTED);
-        } catch (LowpanException x) {
+            return mBinder.isConnected();
+
+        } catch (DeadObjectException x) {
             return false;
+
+        } catch (RemoteException x) {
+            throw x.rethrowAsRuntimeException();
         }
     }
 
@@ -492,9 +429,13 @@
      */
     public boolean isCommissioned() {
         try {
-            return getPropertyAsBoolean(LowpanProperties.KEY_INTERFACE_COMMISSIONED);
-        } catch (LowpanException x) {
+            return mBinder.isCommissioned();
+
+        } catch (DeadObjectException x) {
             return false;
+
+        } catch (RemoteException x) {
+            throw x.rethrowAsRuntimeException();
         }
     }
 
@@ -514,90 +455,89 @@
      */
     public String getState() {
         try {
-            return getProperty(LowpanProperties.KEY_INTERFACE_STATE);
-        } catch (LowpanException x) {
-            Log.e(TAG, x.toString());
+            return mBinder.getState();
+
+        } catch (DeadObjectException x) {
             return STATE_FAULT;
+
+        } catch (RemoteException x) {
+            throw x.rethrowAsRuntimeException();
+        }
+    }
+
+    /** Get network partition/fragment identifier. */
+    public String getPartitionId() {
+        try {
+            return mBinder.getPartitionId();
+
+        } catch (DeadObjectException x) {
+            return EMPTY_PARTITION_ID;
+
+        } catch (RemoteException x) {
+            throw x.rethrowAsRuntimeException();
         }
     }
 
     /** 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 mBinder.getLowpanIdentity();
+
+        } catch (DeadObjectException x) {
+            return new LowpanIdentity();
+
+        } catch (RemoteException x) {
+            throw x.rethrowAsRuntimeException();
         }
-
-        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());
-        }
+            return mBinder.getRole();
 
-        if (role == null) {
-            role = ROLE_DETACHED;
-        }
+        } catch (DeadObjectException x) {
+            return ROLE_DETACHED;
 
-        return role;
+        } catch (RemoteException x) {
+            throw x.rethrowAsRuntimeException();
+        }
     }
 
     /** TODO: doc */
     @Nullable
     public LowpanCredential getLowpanCredential() {
-        LowpanCredential credential = null;
-
         try {
-            Integer keyIndex = getProperty(LowpanProperties.KEY_NETWORK_MASTER_KEY_INDEX);
+            return mBinder.getLowpanCredential();
 
-            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());
+        } catch (RemoteException x) {
+            throw x.rethrowAsRuntimeException();
         }
-
-        return credential;
     }
 
-    /**
-     * TODO: doc
-     *
-     * @hide
-     */
-    public void setLowpanCredential(LowpanCredential networkCredential) throws LowpanException {
-        Map<String, Object> map = new HashMap();
-        networkCredential.addToMap(map);
-        setProperties(map);
+    public @NonNull String[] getSupportedNetworkTypes() throws LowpanException {
+        try {
+            return mBinder.getSupportedNetworkTypes();
+
+        } catch (RemoteException x) {
+            throw x.rethrowAsRuntimeException();
+
+        } catch (ServiceSpecificException x) {
+            throw LowpanException.rethrowFromServiceSpecificException(x);
+        }
+    }
+
+    public @NonNull LowpanChannelInfo[] getSupportedChannels() throws LowpanException {
+        try {
+            return mBinder.getSupportedChannels();
+
+        } catch (RemoteException x) {
+            throw x.rethrowAsRuntimeException();
+
+        } catch (ServiceSpecificException x) {
+            throw LowpanException.rethrowFromServiceSpecificException(x);
+        }
     }
 
     // Listener Support
@@ -627,57 +567,80 @@
                         }
                     }
 
-                    @Override public void onPropertiesChanged(Map properties) {
-                        Runnable runnable =
-                                () -> {
-                                    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);
-                                };
-
-                        mHandler.post(runnable);
+                    @Override
+                    public void onEnabledChanged(boolean value) {
+                        mHandler.post(() -> cb.onEnabledChanged(value));
                     }
 
-                    @Override public void onLinkNetworkAdded(IpPrefix prefix) {
-                        // Support for this event isn't yet implemented.
+                    @Override
+                    public void onConnectedChanged(boolean value) {
+                        mHandler.post(() -> cb.onConnectedChanged(value));
                     }
 
-                    @Override public void onLinkNetworkRemoved(IpPrefix prefix) {
-                        // Support for this event isn't yet implemented.
+                    @Override
+                    public void onUpChanged(boolean value) {
+                        mHandler.post(() -> cb.onUpChanged(value));
                     }
 
-                    @Override public void onLinkAddressAdded(String address) {
-                        // Support for this event isn't yet implemented.
+                    @Override
+                    public void onRoleChanged(String value) {
+                        mHandler.post(() -> cb.onRoleChanged(value));
                     }
 
-                    @Override public void onLinkAddressRemoved(String address) {
-                        // Support for this event isn't yet implemented.
+                    @Override
+                    public void onStateChanged(String value) {
+                        mHandler.post(() -> cb.onStateChanged(value));
+                    }
+
+                    @Override
+                    public void onLowpanIdentityChanged(LowpanIdentity value) {
+                        mHandler.post(() -> cb.onLowpanIdentityChanged(value));
+                    }
+
+                    @Override
+                    public void onLinkNetworkAdded(IpPrefix value) {
+                        mHandler.post(() -> cb.onLinkNetworkAdded(value));
+                    }
+
+                    @Override
+                    public void onLinkNetworkRemoved(IpPrefix value) {
+                        mHandler.post(() -> cb.onLinkNetworkRemoved(value));
+                    }
+
+                    @Override
+                    public void onLinkAddressAdded(String value) {
+                        LinkAddress la;
+                        try {
+                            la = new LinkAddress(value);
+                        } catch (IllegalArgumentException x) {
+                            Log.e(
+                                    TAG,
+                                    "onLinkAddressAdded: Bad LinkAddress \"" + value + "\", " + x);
+                            return;
+                        }
+                        mHandler.post(() -> cb.onLinkAddressAdded(la));
+                    }
+
+                    @Override
+                    public void onLinkAddressRemoved(String value) {
+                        LinkAddress la;
+                        try {
+                            la = new LinkAddress(value);
+                        } catch (IllegalArgumentException x) {
+                            Log.e(
+                                    TAG,
+                                    "onLinkAddressRemoved: Bad LinkAddress \""
+                                            + value
+                                            + "\", "
+                                            + x);
+                            return;
+                        }
+                        mHandler.post(() -> cb.onLinkAddressRemoved(la));
+                    }
+
+                    @Override
+                    public void onReceiveFromCommissioner(byte[] packet) {
+                        // This is only used by the LowpanCommissioningSession.
                     }
                 };
         try {
@@ -752,9 +715,9 @@
      *
      * @hide
      */
-    public LinkAddress[] copyLinkAddresses() throws LowpanException {
+    public LinkAddress[] getLinkAddresses() throws LowpanException {
         try {
-            String[] linkAddressStrings = mBinder.copyLinkAddresses();
+            String[] linkAddressStrings = mBinder.getLinkAddresses();
             LinkAddress[] ret = new LinkAddress[linkAddressStrings.length];
             int i = 0;
             for (String str : linkAddressStrings) {
@@ -766,7 +729,7 @@
             throw x.rethrowAsRuntimeException();
 
         } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowAsLowpanException(x);
+            throw LowpanException.rethrowFromServiceSpecificException(x);
         }
     }
 
@@ -775,15 +738,15 @@
      *
      * @hide
      */
-    public IpPrefix[] copyLinkNetworks() throws LowpanException {
+    public IpPrefix[] getLinkNetworks() throws LowpanException {
         try {
-            return mBinder.copyLinkNetworks();
+            return mBinder.getLinkNetworks();
 
         } catch (RemoteException x) {
             throw x.rethrowAsRuntimeException();
 
         } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowAsLowpanException(x);
+            throw LowpanException.rethrowFromServiceSpecificException(x);
         }
     }
 
@@ -800,7 +763,7 @@
             throw x.rethrowAsRuntimeException();
 
         } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowAsLowpanException(x);
+            throw LowpanException.rethrowFromServiceSpecificException(x);
         }
     }
 
@@ -837,7 +800,7 @@
             throw x.rethrowAsRuntimeException();
 
         } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowAsLowpanException(x);
+            throw LowpanException.rethrowFromServiceSpecificException(x);
         }
     }
 
diff --git a/lowpan/java/android/net/lowpan/LowpanProperties.java b/lowpan/java/android/net/lowpan/LowpanProperties.java
index f835260..cc45ff85 100644
--- a/lowpan/java/android/net/lowpan/LowpanProperties.java
+++ b/lowpan/java/android/net/lowpan/LowpanProperties.java
@@ -16,74 +16,14 @@
 
 package android.net.lowpan;
 
-import android.net.IpPrefix;
-import android.net.LinkAddress;
-
 /** {@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);
-
-    /** @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() {}
diff --git a/lowpan/java/android/net/lowpan/LowpanProvision.aidl b/lowpan/java/android/net/lowpan/LowpanProvision.aidl
new file mode 100644
index 0000000..100e9dc
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanProvision.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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;
+
+parcelable LowpanProvision cpp_header "android/net/lowpan/LowpanProvision.h";
diff --git a/lowpan/java/android/net/lowpan/LowpanProvision.java b/lowpan/java/android/net/lowpan/LowpanProvision.java
index 7028807..f126003 100644
--- a/lowpan/java/android/net/lowpan/LowpanProvision.java
+++ b/lowpan/java/android/net/lowpan/LowpanProvision.java
@@ -18,7 +18,9 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import java.util.Map;
+import android.os.Parcel;
+import android.os.Parcelable;
+import java.util.Objects;
 
 /**
  * Describes the information needed to describe a network
@@ -26,7 +28,7 @@
  * @hide
  */
 // @SystemApi
-public class LowpanProvision {
+public class LowpanProvision implements Parcelable {
 
     // Builder
 
@@ -69,20 +71,6 @@
         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();
@@ -90,11 +78,72 @@
         sb.append("LowpanProvision { identity => ").append(mIdentity.toString());
 
         if (mCredential != null) {
-            sb.append(", credential: ").append(mCredential.toString());
+            sb.append(", credential => ").append(mCredential.toString());
         }
 
         sb.append("}");
 
         return sb.toString();
     }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mIdentity, mCredential);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof LowpanProvision)) {
+            return false;
+        }
+        LowpanProvision rhs = (LowpanProvision) obj;
+
+        if (!mIdentity.equals(rhs.mIdentity)) {
+            return false;
+        }
+
+        if (!Objects.equals(mCredential, rhs.mCredential)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /** Implement the Parcelable interface. */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Implement the Parcelable interface. */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        mIdentity.writeToParcel(dest, flags);
+        if (mCredential == null) {
+            dest.writeBoolean(false);
+        } else {
+            dest.writeBoolean(true);
+            mCredential.writeToParcel(dest, flags);
+        }
+    }
+
+    /** Implement the Parcelable interface. */
+    public static final Creator<LowpanProvision> CREATOR =
+            new Creator<LowpanProvision>() {
+                public LowpanProvision createFromParcel(Parcel in) {
+                    Builder builder = new Builder();
+
+                    builder.setLowpanIdentity(LowpanIdentity.CREATOR.createFromParcel(in));
+
+                    if (in.readBoolean()) {
+                        builder.setLowpanCredential(LowpanCredential.CREATOR.createFromParcel(in));
+                    }
+
+                    return builder.build();
+                }
+
+                public LowpanProvision[] newArray(int size) {
+                    return new LowpanProvision[size];
+                }
+            };
 };
diff --git a/lowpan/java/android/net/lowpan/LowpanRuntimeException.java b/lowpan/java/android/net/lowpan/LowpanRuntimeException.java
new file mode 100644
index 0000000..71a5a13
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/LowpanRuntimeException.java
@@ -0,0 +1,42 @@
+/*
+ * 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.util.AndroidRuntimeException;
+
+/**
+ * Generic runtime exception for LoWPAN operations.
+ *
+ * @hide
+ */
+// @SystemApi
+public class LowpanRuntimeException extends AndroidRuntimeException {
+
+    public LowpanRuntimeException() {}
+
+    public LowpanRuntimeException(String message) {
+        super(message);
+    }
+
+    public LowpanRuntimeException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public LowpanRuntimeException(Exception cause) {
+        super(cause);
+    }
+}
diff --git a/lowpan/java/android/net/lowpan/LowpanScanner.java b/lowpan/java/android/net/lowpan/LowpanScanner.java
index b0557ee..59156c4 100644
--- a/lowpan/java/android/net/lowpan/LowpanScanner.java
+++ b/lowpan/java/android/net/lowpan/LowpanScanner.java
@@ -21,7 +21,6 @@
 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;
@@ -37,7 +36,7 @@
  */
 // @SystemApi
 public class LowpanScanner {
-    private static final String TAG = LowpanInterface.class.getSimpleName();
+    private static final String TAG = LowpanScanner.class.getSimpleName();
 
     // Public Classes
 
@@ -174,7 +173,7 @@
 
         ILowpanNetScanCallback binderListener =
                 new ILowpanNetScanCallback.Stub() {
-                    public void onNetScanBeacon(Map parameters) {
+                    public void onNetScanBeacon(LowpanBeaconInfo beaconInfo) {
                         Callback callback;
                         Handler handler;
 
@@ -187,12 +186,7 @@
                             return;
                         }
 
-                        Runnable runnable =
-                                () ->
-                                        callback.onNetScanBeacon(
-                                                new LowpanBeaconInfo.Builder()
-                                                        .updateFromMap(parameters)
-                                                        .build());
+                        Runnable runnable = () -> callback.onNetScanBeacon(beaconInfo);
 
                         if (handler != null) {
                             handler.post(runnable);
@@ -231,7 +225,7 @@
             throw x.rethrowAsRuntimeException();
 
         } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowAsLowpanException(x);
+            throw LowpanException.rethrowFromServiceSpecificException(x);
         }
     }
 
@@ -246,9 +240,6 @@
 
         } catch (RemoteException x) {
             throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            Log.e(TAG, x.toString());
         }
     }
 
@@ -315,7 +306,7 @@
             throw x.rethrowAsRuntimeException();
 
         } catch (ServiceSpecificException x) {
-            throw LowpanException.rethrowAsLowpanException(x);
+            throw LowpanException.rethrowFromServiceSpecificException(x);
         }
     }
 
@@ -330,9 +321,6 @@
 
         } catch (RemoteException x) {
             throw x.rethrowAsRuntimeException();
-
-        } catch (ServiceSpecificException x) {
-            Log.e(TAG, x.toString());
         }
     }
 }
diff --git a/lowpan/java/android/net/lowpan/NetworkAlreadyExistsException.java b/lowpan/java/android/net/lowpan/NetworkAlreadyExistsException.java
new file mode 100644
index 0000000..90ef498
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/NetworkAlreadyExistsException.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+/**
+ * Exception indicating the form operation found a network nearby with the same identity.
+ *
+ * @see LowpanInterface
+ * @hide
+ */
+// @SystemApi
+public class NetworkAlreadyExistsException extends LowpanException {
+
+    public NetworkAlreadyExistsException() {}
+
+    public NetworkAlreadyExistsException(String message) {
+        super(message, null);
+    }
+
+    public NetworkAlreadyExistsException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public NetworkAlreadyExistsException(Exception cause) {
+        super(cause);
+    }
+}
diff --git a/lowpan/java/android/net/lowpan/OperationCanceledException.java b/lowpan/java/android/net/lowpan/OperationCanceledException.java
new file mode 100644
index 0000000..fcafe3a
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/OperationCanceledException.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+/**
+ * Exception indicating this operation was canceled by the driver before it could finish.
+ *
+ * @see LowpanInterface
+ * @hide
+ */
+// @SystemApi
+public class OperationCanceledException extends LowpanException {
+
+    public OperationCanceledException() {}
+
+    public OperationCanceledException(String message) {
+        super(message);
+    }
+
+    public OperationCanceledException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    protected OperationCanceledException(Exception cause) {
+        super(cause);
+    }
+}
diff --git a/lowpan/java/android/net/lowpan/WrongStateException.java b/lowpan/java/android/net/lowpan/WrongStateException.java
new file mode 100644
index 0000000..3565419
--- /dev/null
+++ b/lowpan/java/android/net/lowpan/WrongStateException.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+/**
+ * Exception indicating the interface is the wrong state for an operation.
+ *
+ * @see LowpanInterface
+ * @hide
+ */
+// @SystemApi
+public class WrongStateException extends LowpanException {
+
+    public WrongStateException() {}
+
+    public WrongStateException(String message) {
+        super(message);
+    }
+
+    public WrongStateException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    protected WrongStateException(Exception cause) {
+        super(cause);
+    }
+}
diff --git a/lowpan/tests/Android.mk b/lowpan/tests/Android.mk
index bb0a944..1ea471a 100644
--- a/lowpan/tests/Android.mk
+++ b/lowpan/tests/Android.mk
@@ -54,8 +54,35 @@
 	mockito-target-minus-junit4 \
 	frameworks-base-testutils \
 
-LOCAL_JAVA_LIBRARIES := \
-	android.test.runner \
+LOCAL_JNI_SHARED_LIBRARIES += libframeworkslowpantestsjni
+LOCAL_JNI_SHARED_LIBRARIES += libandroid_net_lowpan
+LOCAL_JNI_SHARED_LIBRARIES += \
+    libbacktrace \
+    libbase \
+    libbinder \
+    libc++ \
+    libcutils \
+    liblog \
+    liblzma \
+    libnativehelper \
+    libnetdaidl \
+    libui \
+    libunwind \
+    libutils \
+    libvndksupport \
+    libcrypto \
+    libhidl-gen-utils \
+    libhidlbase \
+    libhidltransport \
+    libpackagelistparser \
+    libpcre2 \
+    libselinux \
+    libtinyxml2 \
+    libvintf \
+    libhwbinder \
+    android.hidl.token@1.0
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
 
 LOCAL_PACKAGE_NAME := FrameworksLowpanApiTests
 LOCAL_COMPATIBILITY_SUITE := device-tests
@@ -63,3 +90,25 @@
 LOCAL_CERTIFICATE := platform
 LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
 include $(BUILD_PACKAGE)
+
+#########################################################################
+# Build JNI Shared Library
+#########################################################################
+
+LOCAL_PATH:= $(LOCAL_PATH)/jni
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_CFLAGS := -Wall -Wextra -Werror
+
+LOCAL_SRC_FILES := $(call all-cpp-files-under)
+
+LOCAL_SHARED_LIBRARIES += libandroid_net_lowpan
+LOCAL_SHARED_LIBRARIES += libbinder
+LOCAL_SHARED_LIBRARIES += liblog
+
+LOCAL_MODULE := libframeworkslowpantestsjni
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/lowpan/tests/java/android/net/lowpan/LowpanBeaconInfoTest.java b/lowpan/tests/java/android/net/lowpan/LowpanBeaconInfoTest.java
new file mode 100644
index 0000000..6dbb3ed
--- /dev/null
+++ b/lowpan/tests/java/android/net/lowpan/LowpanBeaconInfoTest.java
@@ -0,0 +1,134 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertArrayEquals;
+
+import android.os.Parcel;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class LowpanBeaconInfoTest {
+
+    static {
+        System.loadLibrary("frameworkslowpantestsjni");
+    }
+
+    private static native byte[] readAndWriteNative(byte[] inParcel);
+
+    public void testNativeParcelUnparcel(LowpanBeaconInfo original) {
+        byte[] inParcel = marshall(original);
+        byte[] outParcel = readAndWriteNative(inParcel);
+        LowpanBeaconInfo roundTrip = unmarshall(outParcel);
+
+        assertEquals(original, roundTrip);
+        assertArrayEquals(inParcel, outParcel);
+    }
+
+    @Test
+    public void testNativeParcelUnparcel() {
+        testNativeParcelUnparcel(
+                new LowpanBeaconInfo.Builder()
+                        .setName("TestNet1")
+                        .setPanid(0x1234)
+                        .setXpanid(
+                                new byte[] {
+                                    (byte) 0x00,
+                                    (byte) 0x11,
+                                    (byte) 0x22,
+                                    (byte) 0x33,
+                                    (byte) 0x44,
+                                    (byte) 0x55,
+                                    (byte) 0x66,
+                                    (byte) 0x77
+                                })
+                        .setType(LowpanInterface.NETWORK_TYPE_THREAD_V1)
+                        .setChannel(15)
+                        .setBeaconAddress(
+                                new byte[] {
+                                    (byte) 0x88,
+                                    (byte) 0x99,
+                                    (byte) 0xaa,
+                                    (byte) 0xbb,
+                                    (byte) 0xcc,
+                                    (byte) 0xdd,
+                                    (byte) 0xee,
+                                    (byte) 0xff
+                                })
+                        .build());
+
+        testNativeParcelUnparcel(
+                new LowpanBeaconInfo.Builder()
+                        .setName("TestNet2")
+                        .setPanid(0x5678)
+                        .setXpanid(
+                                new byte[] {
+                                    (byte) 0x88,
+                                    (byte) 0x99,
+                                    (byte) 0xaa,
+                                    (byte) 0xbb,
+                                    (byte) 0xcc,
+                                    (byte) 0xdd,
+                                    (byte) 0xee,
+                                    (byte) 0xff
+                                })
+                        .setType("bork-bork-bork")
+                        .setChannel(16)
+                        .setBeaconAddress(
+                                new byte[] {
+                                    (byte) 0x00,
+                                    (byte) 0x11,
+                                    (byte) 0x22,
+                                    (byte) 0x33,
+                                    (byte) 0x44,
+                                    (byte) 0x55,
+                                    (byte) 0x66,
+                                    (byte) 0x77
+                                })
+                        .setFlag(LowpanBeaconInfo.FLAG_CAN_ASSIST)
+                        .build());
+    }
+
+    /**
+     * Write a {@link LowpanBeaconInfo} into an empty parcel and return the underlying data.
+     *
+     * @see unmarshall(byte[])
+     */
+    private static byte[] marshall(LowpanBeaconInfo addr) {
+        Parcel p = Parcel.obtain();
+        addr.writeToParcel(p, /* flags */ 0);
+        p.setDataPosition(0);
+        return p.marshall();
+    }
+
+    /**
+     * Read raw bytes into a parcel, and read a {@link LowpanBeaconInfo} back out of them.
+     *
+     * @see marshall(LowpanBeaconInfo)
+     */
+    private static LowpanBeaconInfo unmarshall(byte[] data) {
+        Parcel p = Parcel.obtain();
+        p.unmarshall(data, 0, data.length);
+        p.setDataPosition(0);
+        return LowpanBeaconInfo.CREATOR.createFromParcel(p);
+    }
+}
diff --git a/lowpan/tests/java/android/net/lowpan/LowpanChannelInfoTest.java b/lowpan/tests/java/android/net/lowpan/LowpanChannelInfoTest.java
new file mode 100644
index 0000000..eac4398
--- /dev/null
+++ b/lowpan/tests/java/android/net/lowpan/LowpanChannelInfoTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertArrayEquals;
+
+import android.os.Parcel;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class LowpanChannelInfoTest {
+
+    static {
+        System.loadLibrary("frameworkslowpantestsjni");
+    }
+
+    private static native byte[] readAndWriteNative(byte[] inParcel);
+
+    public void testNativeParcelUnparcel(LowpanChannelInfo original) {
+        byte[] inParcel = marshall(original);
+        byte[] outParcel = readAndWriteNative(inParcel);
+        LowpanChannelInfo roundTrip = unmarshall(outParcel);
+
+        assertEquals(original, roundTrip);
+        assertArrayEquals(inParcel, outParcel);
+    }
+
+    @Test
+    public void testNativeParcelUnparcel() {
+        int i;
+        for (i = 1; i < 26; i++) {
+            testNativeParcelUnparcel(LowpanChannelInfo.getChannelInfoForIeee802154Page0(i));
+        }
+    }
+
+    /**
+     * Write a {@link LowpanChannelInfo} into an empty parcel and return the underlying data.
+     *
+     * @see unmarshall(byte[])
+     */
+    private static byte[] marshall(LowpanChannelInfo addr) {
+        Parcel p = Parcel.obtain();
+        addr.writeToParcel(p, /* flags */ 0);
+        p.setDataPosition(0);
+        return p.marshall();
+    }
+
+    /**
+     * Read raw bytes into a parcel, and read a {@link LowpanChannelInfo} back out of them.
+     *
+     * @see marshall(LowpanChannelInfo)
+     */
+    private static LowpanChannelInfo unmarshall(byte[] data) {
+        Parcel p = Parcel.obtain();
+        p.unmarshall(data, 0, data.length);
+        p.setDataPosition(0);
+        return LowpanChannelInfo.CREATOR.createFromParcel(p);
+    }
+}
diff --git a/lowpan/tests/java/android/net/lowpan/LowpanCredentialTest.java b/lowpan/tests/java/android/net/lowpan/LowpanCredentialTest.java
new file mode 100644
index 0000000..26e3334
--- /dev/null
+++ b/lowpan/tests/java/android/net/lowpan/LowpanCredentialTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertArrayEquals;
+
+import android.os.Parcel;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class LowpanCredentialTest {
+
+    static {
+        System.loadLibrary("frameworkslowpantestsjni");
+    }
+
+    private static native byte[] readAndWriteNative(byte[] inParcel);
+
+    public void testNativeParcelUnparcel(LowpanCredential original) {
+        byte[] inParcel = marshall(original);
+        byte[] outParcel = readAndWriteNative(inParcel);
+        LowpanCredential roundTrip = unmarshall(outParcel);
+
+        assertEquals(original, roundTrip);
+        assertArrayEquals(inParcel, outParcel);
+    }
+
+    @Test
+    public void testNativeParcelUnparcel() {
+        testNativeParcelUnparcel(
+                LowpanCredential.createMasterKey(
+                        new byte[] {
+                            (byte) 0x88,
+                            (byte) 0x99,
+                            (byte) 0xaa,
+                            (byte) 0xbb,
+                            (byte) 0xcc,
+                            (byte) 0xdd,
+                            (byte) 0xee,
+                            (byte) 0xff
+                        }));
+        testNativeParcelUnparcel(
+                LowpanCredential.createMasterKey(
+                        new byte[] {
+                            (byte) 0x88, (byte) 0x99, (byte) 0xaa, (byte) 0xbb, (byte) 0xcc
+                        },
+                        15));
+    }
+
+    /**
+     * Write a {@link LowpanCredential} into an empty parcel and return the underlying data.
+     *
+     * @see unmarshall(byte[])
+     */
+    private static byte[] marshall(LowpanCredential addr) {
+        Parcel p = Parcel.obtain();
+        addr.writeToParcel(p, /* flags */ 0);
+        p.setDataPosition(0);
+        return p.marshall();
+    }
+
+    /**
+     * Read raw bytes into a parcel, and read a {@link LowpanCredential} back out of them.
+     *
+     * @see marshall(LowpanCredential)
+     */
+    private static LowpanCredential unmarshall(byte[] data) {
+        Parcel p = Parcel.obtain();
+        p.unmarshall(data, 0, data.length);
+        p.setDataPosition(0);
+        return LowpanCredential.CREATOR.createFromParcel(p);
+    }
+}
diff --git a/lowpan/tests/java/android/net/lowpan/LowpanIdentityTest.java b/lowpan/tests/java/android/net/lowpan/LowpanIdentityTest.java
new file mode 100644
index 0000000..1242e55
--- /dev/null
+++ b/lowpan/tests/java/android/net/lowpan/LowpanIdentityTest.java
@@ -0,0 +1,199 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class LowpanIdentityTest {
+
+    static {
+        System.loadLibrary("frameworkslowpantestsjni");
+    }
+
+    private static native byte[] readAndWriteNative(byte[] inParcel);
+
+    public void testNativeParcelUnparcel(LowpanIdentity original) {
+        byte[] inParcel = marshall(original);
+        byte[] outParcel = readAndWriteNative(inParcel);
+        LowpanIdentity roundTrip = unmarshall(outParcel);
+
+        assertEquals(original, roundTrip);
+        assertEquals(original.hashCode(), roundTrip.hashCode());
+        assertEquals(original.getName(), roundTrip.getName());
+        assertArrayEquals(inParcel, outParcel);
+    }
+
+    @Test
+    public void testNativeParcelUnparcel1() {
+        testNativeParcelUnparcel(
+                new LowpanIdentity.Builder()
+                        .setName("TestNet1")
+                        .setPanid(0x1234)
+                        .setXpanid(
+                                new byte[] {
+                                    (byte) 0x00,
+                                    (byte) 0x11,
+                                    (byte) 0x22,
+                                    (byte) 0x33,
+                                    (byte) 0x44,
+                                    (byte) 0x55,
+                                    (byte) 0x66,
+                                    (byte) 0x77
+                                })
+                        .setType(LowpanInterface.NETWORK_TYPE_THREAD_V1)
+                        .setChannel(15)
+                        .build());
+    }
+
+    @Test
+    public void testNativeParcelUnparcel2() {
+        testNativeParcelUnparcel(
+                new LowpanIdentity.Builder()
+                        .setName("TestNet2")
+                        .setPanid(0x5678)
+                        .setXpanid(
+                                new byte[] {
+                                    (byte) 0x88,
+                                    (byte) 0x99,
+                                    (byte) 0xaa,
+                                    (byte) 0xbb,
+                                    (byte) 0xcc,
+                                    (byte) 0xdd,
+                                    (byte) 0xee,
+                                    (byte) 0xff
+                                })
+                        .setType("bork-bork-bork")
+                        .setChannel(16)
+                        .build());
+    }
+
+    @Test
+    public void testNativeParcelUnparcel3() {
+        testNativeParcelUnparcel(new LowpanIdentity.Builder().setName("TestNet3").build());
+    }
+
+    @Test
+    public void testNativeParcelUnparcel4() {
+        testNativeParcelUnparcel(new LowpanIdentity.Builder().build());
+    }
+
+    @Test
+    public void testNativeParcelUnparcel5() {
+        testNativeParcelUnparcel(
+                new LowpanIdentity.Builder()
+                        .setRawName(
+                                new byte[] {
+                                    (byte) 0x66,
+                                    (byte) 0x6F,
+                                    (byte) 0x6F,
+                                    (byte) 0xC2,
+                                    (byte) 0xAD,
+                                    (byte) 0xCD,
+                                    (byte) 0x8F,
+                                    (byte) 0xE1,
+                                    (byte) 0xA0,
+                                    (byte) 0x86,
+                                    (byte) 0xE1,
+                                    (byte) 0xA0,
+                                    (byte) 0x8B
+                                })
+                        .build());
+    }
+
+    @Test
+    public void testStringPrep1() {
+        LowpanIdentity identity =
+                new LowpanIdentity.Builder()
+                        .setRawName(
+                                new byte[] {
+                                    (byte) 0x66,
+                                    (byte) 0x6F,
+                                    (byte) 0x6F,
+                                    (byte) 0x20,
+                                    (byte) 0xC2,
+                                    (byte) 0xAD,
+                                    (byte) 0xCD,
+                                    (byte) 0x8F,
+                                    (byte) 0xE1,
+                                    (byte) 0xA0,
+                                    (byte) 0x86,
+                                    (byte) 0xE1,
+                                    (byte) 0xA0,
+                                    (byte) 0x8B
+                                })
+                        .build();
+
+        assertFalse(identity.isNameValid());
+    }
+
+    @Test
+    public void testStringPrep2() {
+        LowpanIdentity identity =
+                new LowpanIdentity.Builder()
+                        .setRawName(
+                                new byte[] {
+                                    (byte) 0x66, (byte) 0x6F, (byte) 0x6F, (byte) 0x20, (byte) 0x6F
+                                })
+                        .build();
+
+        assertEquals("foo o", identity.getName());
+        assertTrue(identity.isNameValid());
+    }
+
+    @Test
+    public void testStringPrep3() {
+        LowpanIdentity identity = new LowpanIdentity.Builder().setName("foo o").build();
+
+        assertTrue(identity.isNameValid());
+        assertEquals("foo o", identity.getName());
+    }
+
+    /**
+     * Write a {@link LowpanIdentity} into an empty parcel and return the underlying data.
+     *
+     * @see unmarshall(byte[])
+     */
+    private static byte[] marshall(LowpanIdentity addr) {
+        Parcel p = Parcel.obtain();
+        addr.writeToParcel(p, /* flags */ 0);
+        p.setDataPosition(0);
+        return p.marshall();
+    }
+
+    /**
+     * Read raw bytes into a parcel, and read a {@link LowpanIdentity} back out of them.
+     *
+     * @see marshall(LowpanIdentity)
+     */
+    private static LowpanIdentity unmarshall(byte[] data) {
+        Parcel p = Parcel.obtain();
+        p.unmarshall(data, 0, data.length);
+        p.setDataPosition(0);
+        return LowpanIdentity.CREATOR.createFromParcel(p);
+    }
+}
diff --git a/lowpan/tests/java/android/net/lowpan/LowpanInterfaceTest.java b/lowpan/tests/java/android/net/lowpan/LowpanInterfaceTest.java
new file mode 100644
index 0000000..f26a37e
--- /dev/null
+++ b/lowpan/tests/java/android/net/lowpan/LowpanInterfaceTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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 static org.mockito.Mockito.*;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.net.LinkAddress;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.test.TestLooper;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for android.net.lowpan.LowpanInterface. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class LowpanInterfaceTest {
+    private static final String TEST_PACKAGE_NAME = "TestPackage";
+
+    @Mock Context mContext;
+    @Mock ILowpanInterface mLowpanInterfaceService;
+    @Mock IBinder mLowpanInterfaceBinder;
+    @Mock ApplicationInfo mApplicationInfo;
+    @Mock IBinder mAppBinder;
+    @Mock LowpanInterface.Callback mLowpanInterfaceCallback;
+
+    private Handler mHandler;
+    private final TestLooper mTestLooper = new TestLooper();
+    private ILowpanInterfaceListener mInterfaceListener;
+    private LowpanInterface mLowpanInterface;
+    private Map<String, Object> mPropertyMap;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
+        when(mContext.getOpPackageName()).thenReturn(TEST_PACKAGE_NAME);
+        when(mLowpanInterfaceService.getName()).thenReturn("wpan0");
+        when(mLowpanInterfaceService.asBinder()).thenReturn(mLowpanInterfaceBinder);
+
+        mLowpanInterface =
+                new LowpanInterface(mContext, mLowpanInterfaceService, mTestLooper.getLooper());
+    }
+
+    @Test
+    public void testStateChangedCallback() throws Exception {
+        // Register our callback
+        mLowpanInterface.registerCallback(mLowpanInterfaceCallback);
+
+        // Verify a listener was added
+        verify(mLowpanInterfaceService)
+                .addListener(
+                        argThat(
+                                listener -> {
+                                    mInterfaceListener = listener;
+                                    return listener instanceof ILowpanInterfaceListener;
+                                }));
+
+        // Change some properties
+        mInterfaceListener.onStateChanged(LowpanInterface.STATE_OFFLINE);
+        mTestLooper.dispatchAll();
+
+        // Verify that the property was changed
+        verify(mLowpanInterfaceCallback)
+                .onStateChanged(
+                        argThat(stateString -> stateString.equals(LowpanInterface.STATE_OFFLINE)));
+    }
+
+    @Test
+    public void testLinkAddressCallback() throws Exception {
+        // Register our callback
+        mLowpanInterface.registerCallback(mLowpanInterfaceCallback);
+
+        // Verify a listener was added
+        verify(mLowpanInterfaceService)
+                .addListener(
+                        argThat(
+                                listener -> {
+                                    mInterfaceListener = listener;
+                                    return listener instanceof ILowpanInterfaceListener;
+                                }));
+
+        final LinkAddress linkAddress = new LinkAddress("fe80::1/64");
+
+        // Change some properties
+        mInterfaceListener.onLinkAddressAdded(linkAddress.toString());
+        mTestLooper.dispatchAll();
+
+        // Verify that the property was changed
+        verify(mLowpanInterfaceCallback)
+                .onLinkAddressAdded(argThat(addr -> addr.equals(linkAddress)));
+
+        // Change some properties
+        mInterfaceListener.onLinkAddressRemoved(linkAddress.toString());
+        mTestLooper.dispatchAll();
+
+        // Verify that the property was changed
+        verify(mLowpanInterfaceCallback)
+                .onLinkAddressRemoved(argThat(addr -> addr.equals(linkAddress)));
+    }
+
+    @Test
+    public void testBogusLinkAddressCallback() throws Exception {
+        // Register our callback
+        mLowpanInterface.registerCallback(mLowpanInterfaceCallback);
+
+        // Verify a listener was added
+        verify(mLowpanInterfaceService)
+                .addListener(
+                        argThat(
+                                listener -> {
+                                    mInterfaceListener = listener;
+                                    return listener instanceof ILowpanInterfaceListener;
+                                }));
+
+        // Change some properties
+        mInterfaceListener.onLinkAddressAdded("fe80:::1/640");
+        mTestLooper.dispatchAll();
+
+        // Verify that no callback was called.
+        verifyNoMoreInteractions(mLowpanInterfaceCallback);
+
+        // Change some properties
+        mInterfaceListener.onLinkAddressRemoved("fe80:::1/640");
+        mTestLooper.dispatchAll();
+
+        // Verify that no callback was called.
+        verifyNoMoreInteractions(mLowpanInterfaceCallback);
+    }
+}
diff --git a/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java b/lowpan/tests/java/android/net/lowpan/LowpanManagerTest.java
similarity index 99%
rename from lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java
rename to lowpan/tests/java/android/net/lowpan/LowpanManagerTest.java
index 481ad76..3dd7504 100644
--- a/lowpan/tests/src/android/net/lowpan/LowpanManagerTest.java
+++ b/lowpan/tests/java/android/net/lowpan/LowpanManagerTest.java
@@ -16,10 +16,9 @@
 
 package android.net.lowpan;
 
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.*;
 
 import android.content.Context;
diff --git a/lowpan/tests/java/android/net/lowpan/LowpanProvisionTest.java b/lowpan/tests/java/android/net/lowpan/LowpanProvisionTest.java
new file mode 100644
index 0000000..3be2f26
--- /dev/null
+++ b/lowpan/tests/java/android/net/lowpan/LowpanProvisionTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertArrayEquals;
+
+import android.os.Parcel;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class LowpanProvisionTest {
+
+    static {
+        System.loadLibrary("frameworkslowpantestsjni");
+    }
+
+    private static native byte[] readAndWriteNative(byte[] inParcel);
+
+    public void testNativeParcelUnparcel(LowpanProvision original) {
+        byte[] inParcel = marshall(original);
+        byte[] outParcel = readAndWriteNative(inParcel);
+        LowpanProvision roundTrip = unmarshall(outParcel);
+
+        assertEquals(original, roundTrip);
+        assertArrayEquals(inParcel, outParcel);
+    }
+
+    @Test
+    public void testNativeParcelUnparcel() {
+        testNativeParcelUnparcel(
+                new LowpanProvision.Builder()
+                        .setLowpanIdentity(
+                                new LowpanIdentity.Builder()
+                                        .setName("TestNet1")
+                                        .setPanid(0x1234)
+                                        .setXpanid(
+                                                new byte[] {
+                                                    (byte) 0x00,
+                                                    (byte) 0x11,
+                                                    (byte) 0x22,
+                                                    (byte) 0x33,
+                                                    (byte) 0x44,
+                                                    (byte) 0x55,
+                                                    (byte) 0x66,
+                                                    (byte) 0x77
+                                                })
+                                        .setType(LowpanInterface.NETWORK_TYPE_THREAD_V1)
+                                        .setChannel(15)
+                                        .build())
+                        .build());
+        testNativeParcelUnparcel(
+                new LowpanProvision.Builder()
+                        .setLowpanIdentity(
+                                new LowpanIdentity.Builder()
+                                        .setName("TestNet2")
+                                        .setPanid(0x5678)
+                                        .setXpanid(
+                                                new byte[] {
+                                                    (byte) 0x88,
+                                                    (byte) 0x99,
+                                                    (byte) 0xaa,
+                                                    (byte) 0xbb,
+                                                    (byte) 0xcc,
+                                                    (byte) 0xdd,
+                                                    (byte) 0xee,
+                                                    (byte) 0xff
+                                                })
+                                        .setType("bork-bork-bork")
+                                        .setChannel(16)
+                                        .build())
+                        .setLowpanCredential(
+                                LowpanCredential.createMasterKey(
+                                        new byte[] {
+                                            (byte) 0x88,
+                                            (byte) 0x99,
+                                            (byte) 0xaa,
+                                            (byte) 0xbb,
+                                            (byte) 0xcc,
+                                            (byte) 0xdd,
+                                            (byte) 0xee,
+                                            (byte) 0xff
+                                        }))
+                        .build());
+    }
+
+    /**
+     * Write a {@link LowpanProvision} into an empty parcel and return the underlying data.
+     *
+     * @see unmarshall(byte[])
+     */
+    private static byte[] marshall(LowpanProvision addr) {
+        Parcel p = Parcel.obtain();
+        addr.writeToParcel(p, /* flags */ 0);
+        p.setDataPosition(0);
+        return p.marshall();
+    }
+
+    /**
+     * Read raw bytes into a parcel, and read a {@link LowpanProvision} back out of them.
+     *
+     * @see marshall(LowpanProvision)
+     */
+    private static LowpanProvision unmarshall(byte[] data) {
+        Parcel p = Parcel.obtain();
+        p.unmarshall(data, 0, data.length);
+        p.setDataPosition(0);
+        return LowpanProvision.CREATOR.createFromParcel(p);
+    }
+}
diff --git a/lowpan/tests/jni/LowpanBeaconInfoTest.cpp b/lowpan/tests/jni/LowpanBeaconInfoTest.cpp
new file mode 100644
index 0000000..efc5f18
--- /dev/null
+++ b/lowpan/tests/jni/LowpanBeaconInfoTest.cpp
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+#include <memory>
+
+#include <binder/Parcel.h>
+
+#include "LowpanBeaconInfoTest.h"
+
+using android::net::lowpan::LowpanBeaconInfo;
+
+/**
+ * Reads exactly one LowpanBeaconInfo from 'parcelData' assuming that it is a Parcel. Any bytes afterward
+ * are ignored.
+ */
+static LowpanBeaconInfo unmarshall(JNIEnv* env, jbyteArray parcelData) {
+    const int length = env->GetArrayLength(parcelData);
+
+    std::unique_ptr<uint8_t> bytes(new uint8_t[length]);
+    env->GetByteArrayRegion(parcelData, 0, length, reinterpret_cast<jbyte*>(bytes.get()));
+
+    android::Parcel p;
+    p.setData(bytes.get(), length);
+
+    LowpanBeaconInfo value;
+    value.readFromParcel(&p);
+    return value;
+}
+
+/**
+ * Creates a Java byte[] array and writes the contents of 'addr' to it as a Parcel containing
+ * exactly one object.
+ *
+ * Every LowpanBeaconInfo maps to a unique parcel object, so both 'marshall(e, unmarshall(e, x))' and
+ * 'unmarshall(e, marshall(e, x))' should be fixed points.
+ */
+static jbyteArray marshall(JNIEnv* env, const LowpanBeaconInfo& addr) {
+    android::Parcel p;
+    addr.writeToParcel(&p);
+    const int length = p.dataSize();
+
+    jbyteArray parcelData = env->NewByteArray(length);
+    env->SetByteArrayRegion(parcelData, 0, length, reinterpret_cast<const jbyte*>(p.data()));
+
+    return parcelData;
+}
+
+extern "C"
+JNIEXPORT jbyteArray Java_android_net_lowpan_LowpanBeaconInfoTest_readAndWriteNative(JNIEnv* env, jclass,
+        jbyteArray inParcel) {
+    const LowpanBeaconInfo value = unmarshall(env, inParcel);
+    return marshall(env, value);
+}
diff --git a/lowpan/tests/jni/LowpanBeaconInfoTest.h b/lowpan/tests/jni/LowpanBeaconInfoTest.h
new file mode 100644
index 0000000..1ba8eaf
--- /dev/null
+++ b/lowpan/tests/jni/LowpanBeaconInfoTest.h
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+#ifndef _ANDROID_NET_LOWPANBEACONINFOTEST_H_
+#define _ANDROID_NET_LOWPANBEACONINFOTEST_H_
+
+#include <jni.h>
+#include <android/net/lowpan/LowpanBeaconInfo.h>
+
+extern "C"
+JNIEXPORT jbyteArray Java_android_net_lowpan_LowpanBeaconInfoTest_readAndWriteNative(JNIEnv* env, jclass,
+        jbyteArray inParcel);
+
+#endif  //  _ANDROID_NET_LOWPANBEACONINFOTEST_H_
diff --git a/lowpan/tests/jni/LowpanChannelInfoTest.cpp b/lowpan/tests/jni/LowpanChannelInfoTest.cpp
new file mode 100644
index 0000000..03bb72a
--- /dev/null
+++ b/lowpan/tests/jni/LowpanChannelInfoTest.cpp
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+#include <memory>
+
+#include <binder/Parcel.h>
+
+#include "LowpanChannelInfoTest.h"
+
+using android::net::lowpan::LowpanChannelInfo;
+
+/**
+ * Reads exactly one LowpanChannelInfo from 'parcelData' assuming that it is a Parcel. Any bytes afterward
+ * are ignored.
+ */
+static LowpanChannelInfo unmarshall(JNIEnv* env, jbyteArray parcelData) {
+    const int length = env->GetArrayLength(parcelData);
+
+    std::unique_ptr<uint8_t> bytes(new uint8_t[length]);
+    env->GetByteArrayRegion(parcelData, 0, length, reinterpret_cast<jbyte*>(bytes.get()));
+
+    android::Parcel p;
+    p.setData(bytes.get(), length);
+
+    LowpanChannelInfo value;
+    value.readFromParcel(&p);
+    return value;
+}
+
+/**
+ * Creates a Java byte[] array and writes the contents of 'addr' to it as a Parcel containing
+ * exactly one object.
+ *
+ * Every LowpanChannelInfo maps to a unique parcel object, so both 'marshall(e, unmarshall(e, x))' and
+ * 'unmarshall(e, marshall(e, x))' should be fixed points.
+ */
+static jbyteArray marshall(JNIEnv* env, const LowpanChannelInfo& addr) {
+    android::Parcel p;
+    addr.writeToParcel(&p);
+    const int length = p.dataSize();
+
+    jbyteArray parcelData = env->NewByteArray(length);
+    env->SetByteArrayRegion(parcelData, 0, length, reinterpret_cast<const jbyte*>(p.data()));
+
+    return parcelData;
+}
+
+extern "C"
+JNIEXPORT jbyteArray Java_android_net_lowpan_LowpanChannelInfoTest_readAndWriteNative(JNIEnv* env, jclass,
+        jbyteArray inParcel) {
+    const LowpanChannelInfo value = unmarshall(env, inParcel);
+    return marshall(env, value);
+}
diff --git a/lowpan/tests/jni/LowpanChannelInfoTest.h b/lowpan/tests/jni/LowpanChannelInfoTest.h
new file mode 100644
index 0000000..3b29a90
--- /dev/null
+++ b/lowpan/tests/jni/LowpanChannelInfoTest.h
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+#ifndef _ANDROID_NET_LOWPANCHANNELINFOTEST_H_
+#define _ANDROID_NET_LOWPANCHANNELINFOTEST_H_
+
+#include <jni.h>
+#include <android/net/lowpan/LowpanChannelInfo.h>
+
+extern "C"
+JNIEXPORT jbyteArray Java_android_net_lowpan_LowpanChannelInfoTest_readAndWriteNative(JNIEnv* env, jclass,
+        jbyteArray inParcel);
+
+#endif  //  _ANDROID_NET_LOWPANCHANNELINFOTEST_H_
diff --git a/lowpan/tests/jni/LowpanCredentialTest.cpp b/lowpan/tests/jni/LowpanCredentialTest.cpp
new file mode 100644
index 0000000..fc860b2
--- /dev/null
+++ b/lowpan/tests/jni/LowpanCredentialTest.cpp
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+#include <memory>
+
+#include <binder/Parcel.h>
+
+#include "LowpanCredentialTest.h"
+
+using android::net::lowpan::LowpanCredential;
+
+/**
+ * Reads exactly one LowpanCredential from 'parcelData' assuming that it is a Parcel. Any bytes afterward
+ * are ignored.
+ */
+static LowpanCredential unmarshall(JNIEnv* env, jbyteArray parcelData) {
+    const int length = env->GetArrayLength(parcelData);
+
+    std::unique_ptr<uint8_t> bytes(new uint8_t[length]);
+    env->GetByteArrayRegion(parcelData, 0, length, reinterpret_cast<jbyte*>(bytes.get()));
+
+    android::Parcel p;
+    p.setData(bytes.get(), length);
+
+    LowpanCredential value;
+    value.readFromParcel(&p);
+    return value;
+}
+
+/**
+ * Creates a Java byte[] array and writes the contents of 'addr' to it as a Parcel containing
+ * exactly one object.
+ *
+ * Every LowpanCredential maps to a unique parcel object, so both 'marshall(e, unmarshall(e, x))' and
+ * 'unmarshall(e, marshall(e, x))' should be fixed points.
+ */
+static jbyteArray marshall(JNIEnv* env, const LowpanCredential& addr) {
+    android::Parcel p;
+    addr.writeToParcel(&p);
+    const int length = p.dataSize();
+
+    jbyteArray parcelData = env->NewByteArray(length);
+    env->SetByteArrayRegion(parcelData, 0, length, reinterpret_cast<const jbyte*>(p.data()));
+
+    return parcelData;
+}
+
+extern "C"
+JNIEXPORT jbyteArray Java_android_net_lowpan_LowpanCredentialTest_readAndWriteNative(JNIEnv* env, jclass,
+        jbyteArray inParcel) {
+    const LowpanCredential value = unmarshall(env, inParcel);
+    return marshall(env, value);
+}
diff --git a/lowpan/tests/jni/LowpanCredentialTest.h b/lowpan/tests/jni/LowpanCredentialTest.h
new file mode 100644
index 0000000..9dd9889
--- /dev/null
+++ b/lowpan/tests/jni/LowpanCredentialTest.h
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+#ifndef _ANDROID_NET_LOWPANCREDENTIALTEST_H_
+#define _ANDROID_NET_LOWPANCREDENTIALTEST_H_
+
+#include <jni.h>
+#include <android/net/lowpan/LowpanCredential.h>
+
+extern "C"
+JNIEXPORT jbyteArray Java_android_net_lowpan_LowpanCredentialTest_readAndWriteNative(JNIEnv* env, jclass,
+        jbyteArray inParcel);
+
+#endif  //  _ANDROID_NET_LOWPANCREDENTIALTEST_H_
diff --git a/lowpan/tests/jni/LowpanIdentityTest.cpp b/lowpan/tests/jni/LowpanIdentityTest.cpp
new file mode 100644
index 0000000..1a9ad33
--- /dev/null
+++ b/lowpan/tests/jni/LowpanIdentityTest.cpp
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+#include <memory>
+
+#include <binder/Parcel.h>
+
+#include "LowpanIdentityTest.h"
+
+using android::net::lowpan::LowpanIdentity;
+
+/**
+ * Reads exactly one LowpanIdentity from 'parcelData' assuming that it is a Parcel. Any bytes afterward
+ * are ignored.
+ */
+static LowpanIdentity unmarshall(JNIEnv* env, jbyteArray parcelData) {
+    const int length = env->GetArrayLength(parcelData);
+
+    std::unique_ptr<uint8_t> bytes(new uint8_t[length]);
+    env->GetByteArrayRegion(parcelData, 0, length, reinterpret_cast<jbyte*>(bytes.get()));
+
+    android::Parcel p;
+    p.setData(bytes.get(), length);
+
+    LowpanIdentity value;
+    value.readFromParcel(&p);
+    return value;
+}
+
+/**
+ * Creates a Java byte[] array and writes the contents of 'addr' to it as a Parcel containing
+ * exactly one object.
+ *
+ * Every LowpanIdentity maps to a unique parcel object, so both 'marshall(e, unmarshall(e, x))' and
+ * 'unmarshall(e, marshall(e, x))' should be fixed points.
+ */
+static jbyteArray marshall(JNIEnv* env, const LowpanIdentity& addr) {
+    android::Parcel p;
+    addr.writeToParcel(&p);
+    const int length = p.dataSize();
+
+    jbyteArray parcelData = env->NewByteArray(length);
+    env->SetByteArrayRegion(parcelData, 0, length, reinterpret_cast<const jbyte*>(p.data()));
+
+    return parcelData;
+}
+
+extern "C"
+JNIEXPORT jbyteArray Java_android_net_lowpan_LowpanIdentityTest_readAndWriteNative(JNIEnv* env, jclass,
+        jbyteArray inParcel) {
+    const LowpanIdentity value = unmarshall(env, inParcel);
+    return marshall(env, value);
+}
diff --git a/lowpan/tests/jni/LowpanIdentityTest.h b/lowpan/tests/jni/LowpanIdentityTest.h
new file mode 100644
index 0000000..1d2c465
--- /dev/null
+++ b/lowpan/tests/jni/LowpanIdentityTest.h
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+#ifndef _ANDROID_NET_LOWPANIDENTITYTEST_H_
+#define _ANDROID_NET_LOWPANIDENTITYTEST_H_
+
+#include <jni.h>
+#include <android/net/lowpan/LowpanIdentity.h>
+
+extern "C"
+JNIEXPORT jbyteArray Java_android_net_lowpan_LowpanIdentityTest_readAndWriteNative(JNIEnv* env, jclass,
+        jbyteArray inParcel);
+
+#endif  //  _ANDROID_NET_LOWPANIDENTITYTEST_H_
diff --git a/lowpan/tests/jni/LowpanProvisionTest.cpp b/lowpan/tests/jni/LowpanProvisionTest.cpp
new file mode 100644
index 0000000..95f64b6
--- /dev/null
+++ b/lowpan/tests/jni/LowpanProvisionTest.cpp
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+#include <memory>
+
+#include <binder/Parcel.h>
+
+#include "LowpanProvisionTest.h"
+
+using android::net::lowpan::LowpanProvision;
+
+/**
+ * Reads exactly one LowpanProvision from 'parcelData' assuming that it is a Parcel. Any bytes afterward
+ * are ignored.
+ */
+static LowpanProvision unmarshall(JNIEnv* env, jbyteArray parcelData) {
+    const int length = env->GetArrayLength(parcelData);
+
+    std::unique_ptr<uint8_t> bytes(new uint8_t[length]);
+    env->GetByteArrayRegion(parcelData, 0, length, reinterpret_cast<jbyte*>(bytes.get()));
+
+    android::Parcel p;
+    p.setData(bytes.get(), length);
+
+    LowpanProvision value;
+    value.readFromParcel(&p);
+    return value;
+}
+
+/**
+ * Creates a Java byte[] array and writes the contents of 'addr' to it as a Parcel containing
+ * exactly one object.
+ *
+ * Every LowpanProvision maps to a unique parcel object, so both 'marshall(e, unmarshall(e, x))' and
+ * 'unmarshall(e, marshall(e, x))' should be fixed points.
+ */
+static jbyteArray marshall(JNIEnv* env, const LowpanProvision& addr) {
+    android::Parcel p;
+    addr.writeToParcel(&p);
+    const int length = p.dataSize();
+
+    jbyteArray parcelData = env->NewByteArray(length);
+    env->SetByteArrayRegion(parcelData, 0, length, reinterpret_cast<const jbyte*>(p.data()));
+
+    return parcelData;
+}
+
+extern "C"
+JNIEXPORT jbyteArray Java_android_net_lowpan_LowpanProvisionTest_readAndWriteNative(JNIEnv* env, jclass,
+        jbyteArray inParcel) {
+    const LowpanProvision value = unmarshall(env, inParcel);
+    return marshall(env, value);
+}
diff --git a/lowpan/tests/jni/LowpanProvisionTest.h b/lowpan/tests/jni/LowpanProvisionTest.h
new file mode 100644
index 0000000..49211b5
--- /dev/null
+++ b/lowpan/tests/jni/LowpanProvisionTest.h
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+#ifndef _ANDROID_NET_LOWPANPROVISIONTEST_H_
+#define _ANDROID_NET_LOWPANPROVISIONTEST_H_
+
+#include <jni.h>
+#include <android/net/lowpan/LowpanProvision.h>
+
+extern "C"
+JNIEXPORT jbyteArray Java_android_net_lowpan_LowpanProvisionTest_readAndWriteNative(JNIEnv* env, jclass,
+        jbyteArray inParcel);
+
+#endif  //  _ANDROID_NET_LOWPANPROVISIONTEST_H_
diff --git a/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java b/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java
deleted file mode 100644
index 455ee08..0000000
--- a/lowpan/tests/src/android/net/lowpan/LowpanInterfaceTest.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * 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 static org.junit.Assert.assertTrue;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.Mockito.*;
-
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.test.TestLooper;
-import android.support.test.runner.AndroidJUnit4;
-import android.test.suitebuilder.annotation.SmallTest;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import java.util.Map;
-import java.util.HashMap;
-
-/** Unit tests for android.net.lowpan.LowpanInterface. */
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class LowpanInterfaceTest {
-    private static final String TEST_PACKAGE_NAME = "TestPackage";
-
-    @Mock Context mContext;
-    @Mock ILowpanInterface mLowpanInterfaceService;
-    @Mock IBinder mLowpanInterfaceBinder;
-    @Mock ApplicationInfo mApplicationInfo;
-    @Mock IBinder mAppBinder;
-    @Mock LowpanInterface.Callback mLowpanInterfaceCallback;
-
-    private Handler mHandler;
-    private final TestLooper mTestLooper = new TestLooper();
-    private ILowpanInterfaceListener mInterfaceListener;
-    private LowpanInterface mLowpanInterface;
-    private Map<String, Object> mPropertyMap;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo);
-        when(mContext.getOpPackageName()).thenReturn(TEST_PACKAGE_NAME);
-        when(mLowpanInterfaceService.getName()).thenReturn("wpan0");
-        when(mLowpanInterfaceService.asBinder()).thenReturn(mLowpanInterfaceBinder);
-
-        mLowpanInterface = new LowpanInterface(mContext, mLowpanInterfaceService, mTestLooper.getLooper());
-    }
-
-    @Test
-    public void testStateChangedCallback() throws Exception {
-        // Register our callback
-        mLowpanInterface.registerCallback(mLowpanInterfaceCallback);
-
-        // Verify a listener was added
-        verify(mLowpanInterfaceService)
-                .addListener(
-                        argThat(
-                                listener -> {
-                                    mInterfaceListener = listener;
-                                    return listener instanceof ILowpanInterfaceListener;
-                                }));
-
-        // Build a changed property map
-        Map<String, Object> changedProperties = new HashMap<>();
-        LowpanProperties.KEY_INTERFACE_STATE.putInMap(changedProperties, LowpanInterface.STATE_OFFLINE);
-
-        // Change some properties
-        mInterfaceListener.onPropertiesChanged(changedProperties);
-        mTestLooper.dispatchAll();
-
-        // Verify that the property was changed
-        verify(mLowpanInterfaceCallback)
-                .onStateChanged(
-                        argThat(stateString -> stateString.equals(LowpanInterface.STATE_OFFLINE)));
-    }
-}