frameworks/opt/net/lowpan: Set LOCAL_SDK_VERSION where possible. am: 9a776a0133 am: 14c2f414cf
am: 6253f9d9e6

Change-Id: I948e72eaca03e21a953696063eb97e08075491c5
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..b76fad4
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1 @@
+rquattle@google.com
diff --git a/build/lowpan-hal-default.mk b/build/lowpan-hal-default.mk
new file mode 100644
index 0000000..923139f
--- /dev/null
+++ b/build/lowpan-hal-default.mk
@@ -0,0 +1,18 @@
+#
+## 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.
+
+PRODUCT_PACKAGES += \
+    android.hardware.lowpan@1.0-service
+
diff --git a/build/lowpan-service.mk b/build/lowpan-service.mk
new file mode 100644
index 0000000..fbc3fc4
--- /dev/null
+++ b/build/lowpan-service.mk
@@ -0,0 +1,25 @@
+#
+## 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.
+
+PRODUCT_COPY_FILES += \
+    frameworks/native/data/etc/android.hardware.lowpan.xml:system/etc/permissions/android.hardware.lowpan.xml
+
+PRODUCT_SYSTEM_SERVER_JARS += \
+    lowpan-service
+
+PRODUCT_PACKAGES += \
+    lowpan-service \
+    lowpanctl
+
diff --git a/build/lowpan.mk b/build/lowpan.mk
new file mode 100644
index 0000000..ebe855e
--- /dev/null
+++ b/build/lowpan.mk
@@ -0,0 +1,18 @@
+#
+## 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 frameworks/opt/net/lowpan/build/lowpan-service.mk
+include frameworks/opt/net/lowpan/build/lowpan-hal-default.mk
+include frameworks/opt/net/lowpan/build/wpantund.mk
diff --git a/build/wpantund.mk b/build/wpantund.mk
new file mode 100644
index 0000000..5a1999b
--- /dev/null
+++ b/build/wpantund.mk
@@ -0,0 +1,25 @@
+#
+## 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.
+
+LOWPAN_HAL_ENABLED := true
+
+PRODUCT_PACKAGES += \
+    wpantund
+
+PRODUCT_COPY_FILES += \
+    frameworks/opt/net/lowpan/build/wpantund.rc:system/etc/init/wpantund.rc
+
+PRODUCT_PACKAGES += \
+	lowpan_hdlc_adapter
diff --git a/build/wpantund.rc b/build/wpantund.rc
new file mode 100644
index 0000000..f63369e
--- /dev/null
+++ b/build/wpantund.rc
@@ -0,0 +1,13 @@
+service wpantund /system/bin/wpantund -s ${ro.lowpan.wpantund.socket:-system:/system/bin/lowpan_hdlc_adapter} -o Config:Daemon:ExternalNetifManagement 1 -I ${lowpan.interface:-wpan0}
+    disabled
+    class main
+    user lowpan
+    group lowpan inet vpn
+    capabilities NET_ADMIN NET_RAW
+    setenv SHELL /system/bin/sh
+
+on property:ro.lowpan.hal.device=*
+    enable lowpan_hal_1_0
+
+on property:init.svc.lowpan_hal_1_0=running
+    enable wpantund
diff --git a/command/java/com/android/commands/lowpan/LowpanCtl.java b/command/java/com/android/commands/lowpan/LowpanCtl.java
index 8e36841..89cf086 100644
--- a/command/java/com/android/commands/lowpan/LowpanCtl.java
+++ b/command/java/com/android/commands/lowpan/LowpanCtl.java
@@ -16,6 +16,7 @@
 
 package com.android.commands.lowpan;
 
+import android.net.LinkAddress;
 import android.net.lowpan.ILowpanInterface;
 import android.net.lowpan.LowpanBeaconInfo;
 import android.net.lowpan.LowpanCredential;
@@ -26,7 +27,6 @@
 import android.net.lowpan.LowpanManager;
 import android.net.lowpan.LowpanProvision;
 import android.net.lowpan.LowpanScanner;
-import android.net.LinkAddress;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.util.AndroidRuntimeException;
@@ -85,7 +85,6 @@
                         + "subcommand-options:\n"
                         + "       -r / --raw ........................ Print only key contents\n"
                         + "\n");
-
     }
 
     private class CommandErrorException extends AndroidRuntimeException {
@@ -94,21 +93,43 @@
         }
     }
 
+    private class ArgumentErrorException extends IllegalArgumentException {
+        public ArgumentErrorException(String desc) {
+            super(desc);
+        }
+    }
+
     private void throwCommandError(String desc) {
         throw new CommandErrorException(desc);
     }
 
+    private void throwArgumentError(String desc) {
+        throw new ArgumentErrorException(desc);
+    }
+
+    private LowpanManager getLowpanManager() {
+        if (mLowpanManager == null) {
+            mLowpanManager = LowpanManager.getManager();
+
+            if (mLowpanManager == null) {
+                System.err.println(NO_SYSTEM_ERROR_CODE);
+                throwCommandError("Can't connect to LoWPAN service; is the service running?");
+            }
+        }
+        return mLowpanManager;
+    }
+
     private LowpanInterface getLowpanInterface() {
         if (mLowpanInterface == null) {
             if (mLowpanInterfaceName == null) {
-                String interfaceArray[] = mLowpanManager.getInterfaceList();
+                String interfaceArray[] = getLowpanManager().getInterfaceList();
                 if (interfaceArray.length != 0) {
                     mLowpanInterfaceName = interfaceArray[0];
                 } else {
                     throwCommandError("No LoWPAN interfaces are present");
                 }
             }
-            mLowpanInterface = mLowpanManager.getInterface(mLowpanInterfaceName);
+            mLowpanInterface = getLowpanManager().getInterface(mLowpanInterfaceName);
 
             if (mLowpanInterface == null) {
                 throwCommandError("Unknown LoWPAN interface \"" + mLowpanInterfaceName + "\"");
@@ -126,20 +147,16 @@
 
     @Override
     public void onRun() throws Exception {
-        mLowpanManager = LowpanManager.getManager();
-
-        if (mLowpanManager == null) {
-            System.err.println(NO_SYSTEM_ERROR_CODE);
-            throwCommandError("Can't connect to LoWPAN service; is the service running?");
-        }
-
         try {
             String op;
             while ((op = nextArgRequired()) != null) {
                 if (op.equals("-I") || op.equals("--interface")) {
                     mLowpanInterfaceName = nextArgRequired();
+                } else if (op.equals("-h") || op.equals("--help") || op.equals("help")) {
+                    onShowUsage(System.out);
+                    break;
                 } else if (op.startsWith("-")) {
-                    throwCommandError("Unrecognized argument \"" + op + "\"");
+                    throwArgumentError("Unrecognized argument \"" + op + "\"");
                 } else if (op.equals("status") || op.equals("stat")) {
                     runStatus();
                     break;
@@ -177,15 +194,31 @@
                     runReset();
                     break;
                 } else {
-                    showError("Error: unknown command '" + op + "'");
+                    throwArgumentError("Unrecognized argument \"" + op + "\"");
                     break;
                 }
             }
         } catch (ServiceSpecificException x) {
             System.out.println(
                     "ServiceSpecificException: " + x.errorCode + ": " + x.getLocalizedMessage());
+            throw x;
+
+        } catch (ArgumentErrorException x) {
+            // Rethrow so we get syntax help.
+            throw x;
+
+        } catch (IllegalArgumentException x) {
+            // This was an argument exception that wasn't an
+            // argument error. We dump our stack trace immediately
+            // because this might not be from a command line argument.
+            x.printStackTrace(System.err);
+            System.exit(1);
+
         } catch (CommandErrorException x) {
+            // Command errors are normal errors that just
+            // get printed out without any fanfare.
             System.out.println("error: " + x.getLocalizedMessage());
+            System.exit(1);
         }
     }
 
@@ -225,10 +258,8 @@
                 masterKey = HexDump.hexStringToByteArray(nextArgRequired());
             } else if (arg.equals("--master-key-index")) {
                 masterKeyIndex = Integer.decode(nextArgRequired());
-            } else if (arg.equals("--help")) {
-                throwCommandError("");
             } else if (arg.startsWith("-") || hasName) {
-                throwCommandError("Unrecognized argument \"" + arg + "\"");
+                throwArgumentError("Unrecognized argument \"" + arg + "\"");
             } else {
                 // This is the network name
                 identityBuilder.setName(arg);
@@ -247,7 +278,7 @@
         if (credential != null) {
             builder.setLowpanCredential(credential);
         } else if (credentialRequired) {
-            throwCommandError("No credential (like a master key) was specified!");
+            throwArgumentError("No credential (like a master key) was specified!");
         }
 
         return builder.setLowpanIdentity(identityBuilder.build()).build();
@@ -284,9 +315,7 @@
 
         if (provision.getLowpanCredential() != null) {
             System.out.println(
-                    "Forming "
-                            + provision.getLowpanIdentity()
-                            + " with provided credential");
+                    "Forming " + provision.getLowpanIdentity() + " with provided credential");
         } else {
             System.out.println("Forming " + provision.getLowpanIdentity());
         }
@@ -302,26 +331,37 @@
 
         sb.append(iface.getName())
                 .append("\t")
-                .append(iface.getState() + " (" + iface.getRole() + ")");
+                .append(iface.getState());
 
-        if (iface.isUp()) {
-            sb.append(" UP");
-        }
+        if (!iface.isEnabled()) {
+            sb.append(" DISABLED");
 
-        if (iface.isConnected()) {
-            sb.append(" CONNECTED");
-        }
+        } else if (iface.getState() != LowpanInterface.STATE_FAULT) {
+            sb.append(" (" + iface.getRole() + ")");
 
-        if (iface.isCommissioned()) {
-            sb.append(" COMMISSIONED");
-        }
+            if (iface.isUp()) {
+                sb.append(" UP");
+            }
 
-        sb
-            .append("\n\t")
-            .append(getLowpanInterface().getLowpanIdentity());
+            if (iface.isConnected()) {
+                sb.append(" CONNECTED");
+            }
 
-        for (LinkAddress addr : iface.getLinkAddresses()) {
-            sb.append("\n\t").append(addr);
+            if (iface.isCommissioned()) {
+                sb.append(" COMMISSIONED");
+
+                LowpanIdentity identity = getLowpanInterface().getLowpanIdentity();
+
+                if (identity != null) {
+                    sb.append("\n\t").append(identity);
+                }
+            }
+
+            if (iface.isUp()) {
+                for (LinkAddress addr : iface.getLinkAddresses()) {
+                    sb.append("\n\t").append(addr);
+                }
+            }
         }
 
         sb.append("\n");
@@ -336,7 +376,7 @@
             if (arg.equals("--raw") || arg.equals("-r")) {
                 raw = true;
             } else {
-                throwCommandError("Unrecognized argument \"" + arg + "\"");
+                throwArgumentError("Unrecognized argument \"" + arg + "\"");
             }
         }
 
@@ -344,13 +384,12 @@
         if (raw) {
             System.out.println(HexDump.toHexString(credential.getMasterKey()));
         } else {
-            System.out.println(
-                iface.getName() + "\t" + credential.toSensitiveString());
+            System.out.println(iface.getName() + "\t" + credential.toSensitiveString());
         }
     }
 
     private void runListInterfaces() {
-        for (String name : mLowpanManager.getInterfaceList()) {
+        for (String name : getLowpanManager().getInterfaceList()) {
             System.out.println(name);
         }
     }
@@ -363,7 +402,7 @@
             if (arg.equals("-c") || arg.equals("--channel")) {
                 scanner.addChannel(Integer.decode(nextArgRequired()));
             } else {
-                throwCommandError("Unrecognized argument \"" + arg + "\"");
+                throwArgumentError("Unrecognized argument \"" + arg + "\"");
             }
         }
 
@@ -401,7 +440,7 @@
             if (arg.equals("-c") || arg.equals("--channel")) {
                 scanner.addChannel(Integer.decode(nextArgRequired()));
             } else {
-                throwCommandError("Unrecognized argument \"" + arg + "\"");
+                throwArgumentError("Unrecognized argument \"" + arg + "\"");
             }
         }
 
diff --git a/libandroid_net_lowpan/tests/Android.mk b/libandroid_net_lowpan/tests/Android.mk
index b914d81..0db6dea 100644
--- a/libandroid_net_lowpan/tests/Android.mk
+++ b/libandroid_net_lowpan/tests/Android.mk
@@ -82,7 +82,7 @@
     libhwbinder \
     android.hidl.token@1.0
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
 
 LOCAL_PACKAGE_NAME := FrameworksLowpanApiNativeTests
 LOCAL_PRIVATE_PLATFORM_APIS := true
diff --git a/libandroid_net_lowpan/tests/AndroidTest.xml b/libandroid_net_lowpan/tests/AndroidTest.xml
index 55e5e7f..34489fc 100644
--- a/libandroid_net_lowpan/tests/AndroidTest.xml
+++ b/libandroid_net_lowpan/tests/AndroidTest.xml
@@ -21,7 +21,7 @@
     <option name="test-suite-tag" value="apct" />
     <option name="test-tag" value="FrameworksLowpanApiNativeTests" />
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="android.net.lowpan.testnative" />
+        <option name="package" value="android.net.lowpan.test" />
         <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
     </test>
 </configuration>
diff --git a/lowpan_hdlc_adapter/Android.mk b/lowpan_hdlc_adapter/Android.mk
new file mode 100644
index 0000000..bce5b67
--- /dev/null
+++ b/lowpan_hdlc_adapter/Android.mk
@@ -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.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+ifeq ($(LOWPAN_HAL_ENABLED),true)
+include $(CLEAR_VARS)
+LOCAL_MODULE := lowpan_hdlc_adapter
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := \
+    lowpan_hdlc_adapter.cpp \
+    hdlc_lite.c
+
+LOCAL_SHARED_LIBRARIES := \
+	liblog \
+	libcutils \
+	libdl \
+	libbase \
+	libutils \
+	libhardware
+
+LOCAL_SHARED_LIBRARIES += \
+	libhidlbase \
+	libhidltransport \
+	android.hardware.lowpan@1.0
+
+include $(BUILD_EXECUTABLE)
+endif
diff --git a/lowpan_hdlc_adapter/hdlc_lite.c b/lowpan_hdlc_adapter/hdlc_lite.c
new file mode 100644
index 0000000..39cd202
--- /dev/null
+++ b/lowpan_hdlc_adapter/hdlc_lite.c
@@ -0,0 +1,97 @@
+/*
+ * 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 "hdlc_lite.h"
+
+uint16_t hdlc_crc16(uint16_t aFcs, uint8_t aByte)
+{
+    // CRC-16/CCITT, CRC-16/CCITT-TRUE, CRC-CCITT
+    // width=16 poly=0x1021 init=0x0000 refin=true refout=true xorout=0x0000 check=0x2189 name="KERMIT"
+    // http://reveng.sourceforge.net/crc-catalogue/16.htm#crc.cat.kermit
+    static const uint16_t sFcsTable[256] =
+    {
+        0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
+        0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
+        0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
+        0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
+        0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
+        0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
+        0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
+        0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
+        0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
+        0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
+        0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
+        0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
+        0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
+        0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
+        0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
+        0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
+        0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
+        0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
+        0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
+        0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
+        0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
+        0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
+        0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
+        0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
+        0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
+        0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
+        0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
+        0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
+        0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
+        0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
+        0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
+        0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
+    };
+    return (aFcs >> 8) ^ sFcsTable[(aFcs ^ aByte) & 0xff];
+}
+
+uint16_t hdlc_crc16_finalize(uint16_t fcs)
+{
+    return fcs ^ 0xFFFF;
+}
+
+bool hdlc_byte_needs_escape(uint8_t byte)
+{
+    switch(byte)
+    {
+    case HDLC_BYTE_SPECIAL:
+    case HDLC_BYTE_ESC:
+    case HDLC_BYTE_FLAG:
+    case HDLC_BYTE_XOFF:
+    case HDLC_BYTE_XON:
+        return true;
+
+    default:
+        return false;
+    }
+}
+
+int hdlc_write_byte(uint8_t* out_buffer, uint8_t byte)
+{
+    int ret = 1;
+
+    if (hdlc_byte_needs_escape(byte))
+    {
+        *out_buffer++ = HDLC_BYTE_ESC;
+        ret++;
+        byte = byte ^ HDLC_ESCAPE_XFORM;
+    }
+
+    *out_buffer = byte;
+
+    return ret;
+}
diff --git a/lowpan_hdlc_adapter/hdlc_lite.h b/lowpan_hdlc_adapter/hdlc_lite.h
new file mode 100644
index 0000000..4d4d554
--- /dev/null
+++ b/lowpan_hdlc_adapter/hdlc_lite.h
@@ -0,0 +1,57 @@
+/*
+ * 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 HEADER_HDLC_LITE_H_INCLUDED
+#define HEADER_HDLC_LITE_H_INCLUDED 1
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+static const uint8_t kHdlcResetSignal[] = { 0x7E, 0x13, 0x11, 0x7E };
+static const uint16_t kHdlcCrcCheckValue = 0xf0b8;
+static const uint16_t kHdlcCrcResetValue = 0xffff;
+
+#define HDLC_BYTE_FLAG             0x7E
+#define HDLC_BYTE_ESC              0x7D
+#define HDLC_BYTE_XON              0x11
+#define HDLC_BYTE_XOFF             0x13
+#define HDLC_BYTE_SPECIAL          0xF8
+#define HDLC_ESCAPE_XFORM          0x20
+
+/** HDLC CRC function */
+extern uint16_t hdlc_crc16(uint16_t fcs, uint8_t byte);
+
+/** HDLC CRC Finalize function */
+extern uint16_t hdlc_crc16_finalize(uint16_t fcs);
+
+/** Returns true if the byte needs to be escaped, false otherwise */
+extern bool hdlc_byte_needs_escape(uint8_t byte);
+
+/**
+ * Writes one or two HDLC-encoded bytes to out_buffer,
+ * and returns how many bytes were written.
+ */
+extern int hdlc_write_byte(uint8_t* out_buffer, uint8_t byte);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif // HEADER_HDLC_LITE_H_INCLUDED
diff --git a/lowpan_hdlc_adapter/lowpan_hdlc_adapter.cpp b/lowpan_hdlc_adapter/lowpan_hdlc_adapter.cpp
new file mode 100644
index 0000000..6986b2f
--- /dev/null
+++ b/lowpan_hdlc_adapter/lowpan_hdlc_adapter.cpp
@@ -0,0 +1,319 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "lowpan-hdlc-adapter"
+
+#include "hdlc_lite.h"
+
+#include <unistd.h>
+
+#include <mutex>
+#include <condition_variable>
+
+#include <hidl/HidlTransportSupport.h>
+#include <hidl/ServiceManagement.h>
+#include <hidl/Status.h>
+#include <hardware/hardware.h>
+#include <utils/Thread.h>
+#include <utils/Errors.h>
+#include <utils/StrongPointer.h>
+#include <log/log.h>
+#include <android/hardware/lowpan/1.0/ILowpanDevice.h>
+#include <android/hidl/manager/1.0/IServiceManager.h>
+
+#define LOWPAN_HDLC_ADAPTER_MAX_FRAME_SIZE 2048
+
+using ::android::hardware::Return;
+using ::android::hardware::Void;
+using ::android::hardware::configureRpcThreadpool;
+using ::android::hardware::hidl_death_recipient;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::joinRpcThreadpool;
+using ::android::sp;
+using namespace ::android::hardware::lowpan::V1_0;
+using namespace ::android;
+
+struct LowpanDeathRecipient : hidl_death_recipient {
+    LowpanDeathRecipient() = default;
+    virtual void serviceDied(uint64_t /*cookie*/, const wp<::android::hidl::base::V1_0::IBase>& /*who*/) {
+        ALOGE("LowpanDevice died");
+        exit(EXIT_FAILURE);
+    }
+};
+
+struct LowpanDeviceCallback : public ILowpanDeviceCallback {
+    int mFd;
+    std::mutex mMutex;
+    std::condition_variable mConditionVariable;
+    int mOpenError;
+    static const uint32_t kMaxFrameSize = LOWPAN_HDLC_ADAPTER_MAX_FRAME_SIZE;
+public:
+    LowpanDeviceCallback(int fd): mFd(fd), mOpenError(-1) {}
+    virtual ~LowpanDeviceCallback() = default;
+
+    int waitForOpenStatus() {
+        std::unique_lock<std::mutex> lock(mMutex);
+        if (mOpenError == -1) {
+            mConditionVariable.wait(lock);
+        }
+        return mOpenError;
+    }
+
+    Return<void> onReceiveFrame(const hidl_vec<uint8_t>& data)  override {
+        if (data.size() > kMaxFrameSize) {
+            ALOGE("TOOBIG: Frame received from device is too big");
+            return Return<void>();
+        }
+
+        int bufferIndex = 0;
+        uint16_t fcs = kHdlcCrcResetValue;
+        uint8_t buffer[kMaxFrameSize*2 + 5]; // every character escaped, escaped crc, and frame marker
+        uint8_t c;
+
+        for (size_t i = 0; i < data.size(); i++)
+        {
+            c = data[i];
+            fcs = hdlc_crc16(fcs, c);
+            bufferIndex += hdlc_write_byte(buffer + bufferIndex, c);
+        }
+
+        fcs = hdlc_crc16_finalize(fcs);
+
+        bufferIndex += hdlc_write_byte(buffer + bufferIndex, uint8_t(fcs & 0xFF));
+        bufferIndex += hdlc_write_byte(buffer + bufferIndex, uint8_t((fcs >> 8) & 0xFF));
+
+        buffer[bufferIndex++] = HDLC_BYTE_FLAG;
+
+        std::unique_lock<std::mutex> lock(mMutex);
+
+        if (write(mFd, buffer, bufferIndex) != bufferIndex) {
+            ALOGE("IOFAIL: write: %s (%d)", strerror(errno), errno);
+            exit(EXIT_FAILURE);
+        }
+
+        return Return<void>();
+    }
+
+    Return<void> onEvent(LowpanEvent event, LowpanStatus status)  override {
+        std::unique_lock<std::mutex> lock(mMutex);
+
+        switch (event) {
+        case LowpanEvent::OPENED:
+            if (mOpenError == -1) {
+                mOpenError = 0;
+                mConditionVariable.notify_all();
+            }
+            ALOGI("Device opened");
+            break;
+
+        case LowpanEvent::CLOSED:
+            ALOGI("Device closed");
+            exit(EXIT_SUCCESS);
+            break;
+
+        case LowpanEvent::RESET:
+            ALOGI("Device reset");
+            break;
+
+        case LowpanEvent::ERROR:
+            if (mOpenError == -1) {
+                mOpenError = int(status);
+                mConditionVariable.notify_all();
+            }
+            switch (status) {
+            case LowpanStatus::IOFAIL:
+                ALOGE("IOFAIL: Input/Output error from device. Terminating.");
+                exit(EXIT_FAILURE);
+                break;
+            case LowpanStatus::GARBAGE:
+                ALOGW("GARBAGE: Bad frame from device.");
+                break;
+            case LowpanStatus::TOOBIG:
+                ALOGW("TOOBIG: Device sending frames that are too large.");
+                break;
+            default:
+                ALOGW("Unknown error %d", status);
+                break;
+            }
+            break;
+        }
+        return Return<void>();
+    }
+};
+
+class ReadThread : public Thread {
+    int kReadThreadBufferSize;
+
+    sp<ILowpanDevice> mService;
+    int mFd;
+    uint8_t mBuffer[LOWPAN_HDLC_ADAPTER_MAX_FRAME_SIZE];
+    int mBufferIndex;
+    bool mUnescapeNextByte;
+    uint16_t mFcs;
+    sp<LowpanDeviceCallback> mCallback;
+
+public:
+    ReadThread(sp<ILowpanDevice> service, int fd, sp<LowpanDeviceCallback> callback):
+            Thread(false /*canCallJava*/),
+            kReadThreadBufferSize(service->getMaxFrameSize()),
+            mService(service),
+            mFd(fd),
+            mBufferIndex(0),
+            mUnescapeNextByte(false),
+            mFcs(kHdlcCrcResetValue),
+            mCallback(callback) {
+        if (kReadThreadBufferSize < 16) {
+            ALOGE("Device returned bad max frame size: %d bytes", kReadThreadBufferSize);
+            exit(EXIT_FAILURE);
+        }
+        if ((size_t)kReadThreadBufferSize > sizeof(mBuffer)) {
+            kReadThreadBufferSize = (int)sizeof(mBuffer);
+        }
+    }
+
+    virtual ~ReadThread() {}
+
+private:
+
+    bool threadLoop() override {
+        uint8_t buffer[LOWPAN_HDLC_ADAPTER_MAX_FRAME_SIZE];
+
+        if (int error = mCallback->waitForOpenStatus()) {
+            ALOGE("Call to `open()` failed: %d", error);
+            exit(EXIT_FAILURE);
+        }
+
+        while (!exitPending()) {
+            ssize_t bytesRead = read(mFd, buffer, sizeof(buffer));
+            if (exitPending()) {
+                break;
+            }
+
+            if (bytesRead < 0) {
+                ALOGE("IOFAIL: read: %s (%d)", strerror(errno), errno);
+                exit(EXIT_FAILURE);
+                break;
+            }
+            feedBytes(buffer, bytesRead);
+        }
+
+        return false;
+    }
+
+    void feedBytes(const uint8_t* dataPtr, ssize_t dataLen) {
+        while(dataLen--) {
+            feedByte(*dataPtr++);
+        }
+    }
+
+    void sendFrame(uint8_t* p_data, uint16_t data_len) {
+        hidl_vec<uint8_t> data;
+        data.setToExternal(p_data, data_len);
+        mService->sendFrame(data);
+    }
+
+    void feedByte(uint8_t byte) {
+        if (mBufferIndex >= kReadThreadBufferSize) {
+            ALOGE("TOOBIG: HDLC frame too big (Max: %d)", kReadThreadBufferSize);
+            mUnescapeNextByte = false;
+            mBufferIndex = 0;
+            mFcs = kHdlcCrcResetValue;
+
+        } else if (byte == HDLC_BYTE_FLAG) {
+            if (mBufferIndex <= 2) {
+                // Ignore really small frames.
+                // Don't remove this or we will underflow our
+                // index for onReceiveFrame(), below!
+
+            } else if (mUnescapeNextByte || (mFcs != kHdlcCrcCheckValue)) {
+                ALOGE("GARBAGE: HDLC frame with bad CRC (LEN:%d, mFcs:0x%04X)", mBufferIndex, mFcs);
+
+            } else {
+                // -2 for CRC
+                sendFrame(mBuffer, uint16_t(mBufferIndex - 2));
+            }
+
+            mUnescapeNextByte = false;
+            mBufferIndex = 0;
+            mFcs = kHdlcCrcResetValue;
+
+        } else if (byte == HDLC_BYTE_ESC) {
+            mUnescapeNextByte = true;
+
+        } else if (hdlc_byte_needs_escape(byte)) {
+            // Skip all other control codes.
+
+        } else {
+            if (mUnescapeNextByte) {
+                byte = byte ^ HDLC_ESCAPE_XFORM;
+                mUnescapeNextByte = false;
+            }
+
+            mFcs = hdlc_crc16(mFcs, byte);
+            mBuffer[mBufferIndex++] = byte;
+        }
+    }
+};
+
+int main(int argc, char* argv []) {
+    using ::android::hardware::defaultServiceManager;
+    using Transport = ::android::hidl::manager::V1_0::IServiceManager::Transport;
+
+    const char* serviceName = "default";
+
+    if (argc >= 2) {
+        serviceName = argv[1];
+    }
+
+    sp<ILowpanDevice> service = ILowpanDevice::getService(serviceName, false /* getStub */);
+
+    if (service == nullptr) {
+        ALOGE("Unable to find LowpanDevice named \"%s\"", serviceName);
+        exit(EXIT_FAILURE);
+    }
+
+    service->linkToDeath(new LowpanDeathRecipient(), 0 /*cookie*/);
+
+    configureRpcThreadpool(1, true /* callerWillJoin */);
+
+    sp<LowpanDeviceCallback> callback = new LowpanDeviceCallback(STDOUT_FILENO);
+
+    {
+        auto status = service->open(callback);
+        if (status.isOk()) {
+            if (status == LowpanStatus::OK) {
+                ALOGD("%s: open() ok.", serviceName);
+            } else {
+                ALOGE("%s: open() failed: (%d).", serviceName, LowpanStatus(status));
+                exit(EXIT_FAILURE);
+            }
+        } else {
+            ALOGE("%s: open() failed: transport error", serviceName);
+            exit(EXIT_FAILURE);
+        }
+    }
+
+    sp<Thread> readThread = new ReadThread(service, STDIN_FILENO, callback);
+
+    readThread->run("ReadThread");
+
+    joinRpcThreadpool();
+
+    ALOGI("Shutting down");
+
+    return EXIT_SUCCESS;
+}
diff --git a/service/java/com/android/server/lowpan/LowpanInterfaceTracker.java b/service/java/com/android/server/lowpan/LowpanInterfaceTracker.java
index 89b058c..69842c2 100644
--- a/service/java/com/android/server/lowpan/LowpanInterfaceTracker.java
+++ b/service/java/com/android/server/lowpan/LowpanInterfaceTracker.java
@@ -73,8 +73,6 @@
     /** The base for LoWPAN message codes */
     static final int BASE = Protocol.BASE_LOWPAN;
 
-    static final int CMD_REGISTER = BASE + 1;
-    static final int CMD_UNREGISTER = BASE + 2;
     static final int CMD_START_NETWORK = BASE + 3;
     static final int CMD_STOP_NETWORK = BASE + 4;
     static final int CMD_STATE_CHANGE = BASE + 5;
@@ -89,7 +87,7 @@
     private LowpanInterface mLowpanInterface;
     private NetworkAgent mNetworkAgent;
     private NetworkFactory mNetworkFactory;
-    private final IpManager mIpManager;
+    private IpManager mIpManager;
     private final IpManager.Callback mIpManagerCallback = new IpManagerCallback();
 
     // Instance Variables
@@ -106,7 +104,6 @@
 
     final DefaultState mDefaultState = new DefaultState();
     final NormalState mNormalState = new NormalState();
-    final InitState mInitState = new InitState();
     final OfflineState mOfflineState = new OfflineState();
     final CommissioningState mCommissioningState = new CommissioningState();
     final AttachingState mAttachingState = new AttachingState();
@@ -154,30 +151,6 @@
 
     // State Definitions
 
-    class InitState extends State {
-        @Override
-        public void enter() {}
-
-        @Override
-        public boolean processMessage(Message message) {
-            switch (message.what) {
-                case CMD_REGISTER:
-                    if (DBG) {
-                        Log.i(TAG, "CMD_REGISTER");
-                    }
-                    transitionTo(mDefaultState);
-                    break;
-
-                default:
-                    return NOT_HANDLED;
-            }
-            return HANDLED;
-        }
-
-        @Override
-        public void exit() {}
-    }
-
     class DefaultState extends State {
         @Override
         public void enter() {
@@ -200,20 +173,29 @@
             boolean retValue = NOT_HANDLED;
 
             switch (message.what) {
-                case CMD_UNREGISTER:
-                    transitionTo(mInitState);
-                    retValue = HANDLED;
-                    break;
-
                 case CMD_START_NETWORK:
                     if (DBG) {
                         Log.i(TAG, "CMD_START_NETWORK");
                     }
+                    try {
+                        mLowpanInterface.setEnabled(true);
+                    } catch (LowpanException | LowpanRuntimeException x) {
+                        Log.e(TAG, "Exception while enabling: " + x);
+                        transitionTo(mFaultState);
+                        return HANDLED;
+                    }
                     break;
 
                 case CMD_STOP_NETWORK:
                     if (DBG) {
-                        Log.i(TAG, "CMD_START_NETWORK");
+                        Log.i(TAG, "CMD_STOP_NETWORK");
+                    }
+                    try {
+                        mLowpanInterface.setEnabled(false);
+                    } catch (LowpanException | LowpanRuntimeException x) {
+                        Log.e(TAG, "Exception while disabling: " + x);
+                        transitionTo(mFaultState);
+                        return HANDLED;
                     }
                     break;
 
@@ -225,7 +207,7 @@
                                     "LowpanInterface changed state from \""
                                             + mState
                                             + "\" to \""
-                                            + message.obj.toString()
+                                            + message.obj
                                             + "\".");
                         }
                         mState = (String) message.obj;
@@ -255,7 +237,6 @@
 
         @Override
         public void exit() {
-
             mLowpanInterface.unregisterCallback(mLocalLowpanCallback);
         }
     }
@@ -267,15 +248,17 @@
                 Log.i(TAG, "NormalState.enter()");
             }
 
+            mIpManager = new IpManager(mContext, mInterfaceName, mIpManagerCallback);
+
             if (mHwAddr == null) {
                 byte[] hwAddr = null;
                 try {
                     hwAddr = mLowpanInterface.getService().getMacAddress();
 
                 } catch (RemoteException | ServiceSpecificException x) {
-                    // Don't let misbehavior of an interface service
+                    // Don't let misbehavior of the interface service
                     // crash the system service.
-                    Log.e(TAG, x.toString());
+                    Log.e(TAG, "Call to getMacAddress() failed: " + x);
                     transitionTo(mFaultState);
                 }
 
@@ -296,7 +279,13 @@
                             Log.i(TAG, "UNWANTED.");
                         }
 
-                        // TODO: Figure out how to properly handle this.
+                        try {
+                            mLowpanInterface.setEnabled(false);
+                        } catch (LowpanException | LowpanRuntimeException x) {
+                            Log.e(TAG, "Exception while disabling: " + x);
+                            transitionTo(mFaultState);
+                            return HANDLED;
+                        }
 
                         shutdownNetworkAgent();
                     }
@@ -313,7 +302,7 @@
                     break;
 
                 case CMD_PROVISIONING_FAILURE:
-                    Log.i(TAG, "Provisioning Failure: " + message.obj.toString());
+                    Log.i(TAG, "Provisioning Failure: " + message.obj);
                     break;
             }
 
@@ -324,6 +313,11 @@
         public void exit() {
             shutdownNetworkAgent();
             mNetworkFactory.unregister();
+
+            if (mIpManager != null) {
+                mIpManager.shutdown();
+            }
+            mIpManager = null;
         }
     }
 
@@ -332,6 +326,8 @@
         public void enter() {
             shutdownNetworkAgent();
             mNetworkInfo.setIsAvailable(true);
+
+            mIpManager.stop();
         }
 
         @Override
@@ -385,17 +381,15 @@
         public boolean processMessage(Message message) {
             switch (message.what) {
                 case CMD_STATE_CHANGE:
-                    if (!mState.equals(message.obj)) {
-                        if (!LowpanInterface.STATE_ATTACHED.equals(message.obj)) {
-                            return NOT_HANDLED;
-                        }
+                    if (!mState.equals(message.obj)
+                            && !LowpanInterface.STATE_ATTACHED.equals(message.obj)) {
+                        return NOT_HANDLED;
                     }
-                    break;
+                    return HANDLED;
 
                 default:
                     return NOT_HANDLED;
             }
-            return HANDLED;
         }
 
         @Override
@@ -439,7 +433,7 @@
                 if (x.getCause() instanceof RemoteException) {
                     // Don't let misbehavior of an interface service
                     // crash the system service.
-                    Log.e(TAG, x.toString());
+                    Log.e(TAG, "RuntimeException while populating InitialConfiguration: " + x);
                     transitionTo(mFaultState);
 
                 } else {
@@ -483,7 +477,7 @@
 
             switch (message.what) {
                 case CMD_PROVISIONING_SUCCESS:
-                    Log.i(TAG, "Provisioning Success: " + message.obj.toString());
+                    Log.i(TAG, "Provisioning Success: " + message.obj);
                     transitionTo(mConnectedState);
                     return HANDLED;
             }
@@ -555,7 +549,6 @@
         mNetworkCapabilities.setLinkDownstreamBandwidthKbps(100);
 
         // CHECKSTYLE:OFF IndentationCheck
-        addState(mInitState);
         addState(mDefaultState);
         addState(mFaultState, mDefaultState);
         addState(mNormalState, mDefaultState);
@@ -567,7 +560,7 @@
         addState(mConnectedState, mAttachedState);
         // CHECKSTYLE:ON IndentationCheck
 
-        setInitialState(mInitState);
+        setInitialState(mDefaultState);
 
         mNetworkFactory =
                 new NetworkFactory(looper, context, NETWORK_TYPE, mNetworkCapabilities) {
@@ -582,10 +575,6 @@
                     }
                 };
 
-        mIpManager = new IpManager(mContext, mInterfaceName, mIpManagerCallback);
-
-        start();
-
         if (DBG) {
             Log.i(TAG, "LowpanInterfaceTracker() end");
         }
@@ -625,13 +614,13 @@
         if (DBG) {
             Log.i(TAG, "register()");
         }
-        sendMessage(CMD_REGISTER);
+        start();
     }
 
     public void unregister() {
         if (DBG) {
             Log.i(TAG, "unregister()");
         }
-        sendMessage(CMD_UNREGISTER);
+        quit();
     }
 }
diff --git a/service/java/com/android/server/lowpan/LowpanServiceImpl.java b/service/java/com/android/server/lowpan/LowpanServiceImpl.java
index 07677e3..9450089 100644
--- a/service/java/com/android/server/lowpan/LowpanServiceImpl.java
+++ b/service/java/com/android/server/lowpan/LowpanServiceImpl.java
@@ -16,10 +16,15 @@
 
 package com.android.server.lowpan;
 
+import android.content.pm.PackageManager;
 import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
 import android.net.lowpan.ILowpanInterface;
 import android.net.lowpan.ILowpanManager;
 import android.net.lowpan.ILowpanManagerListener;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
 import android.os.Binder;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -27,11 +32,11 @@
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.util.Log;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * LowpanService handles remote LoWPAN operation requests by implementing the ILowpanManager
@@ -46,9 +51,11 @@
     private final Context mContext;
     private final HandlerThread mHandlerThread = new HandlerThread("LowpanServiceThread");
     private final AtomicBoolean mStarted = new AtomicBoolean(false);
+    private final boolean mIsAndroidThings;
 
     public LowpanServiceImpl(Context context) {
         mContext = context;
+        mIsAndroidThings = context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED);
     }
 
     public Looper getLooper() {
@@ -61,6 +68,25 @@
         return looper;
     }
 
+    public void createOutstandingNetworkRequest() {
+        final ConnectivityManager cm =
+                (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+
+        if (cm == null) {
+            throw new IllegalStateException("Bad luck, ConnectivityService not started.");
+        }
+
+        NetworkRequest request = new NetworkRequest.Builder()
+                .clearCapabilities()
+                .addTransportType(NetworkCapabilities.TRANSPORT_LOWPAN)
+                .build();
+
+        // Note that this method only ever gets called once,
+        // so we don't need to bother with worrying about unregistering.
+
+        cm.requestNetwork(request, new NetworkCallback());
+    }
+
     public void checkAndStartLowpan() {
         synchronized (mInterfaceMap) {
             if (mStarted.compareAndSet(false, true)) {
@@ -70,18 +96,35 @@
             }
         }
 
+        createOutstandingNetworkRequest();
+
         // TODO: Bring up any daemons(like wpantund)?
     }
 
     private void enforceAccessPermission() {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.ACCESS_LOWPAN_STATE, "LowpanService");
+        try {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.ACCESS_LOWPAN_STATE, "LowpanService");
+        } catch (SecurityException x) {
+            if (!mIsAndroidThings) {
+                throw x;
+            }
+            mContext.enforceCallingOrSelfPermission(
+                    "com.google.android.things.permission.ACCESS_LOWPAN_STATE", "LowpanService");
+        }
     }
 
     private void enforceManagePermission() {
-        // TODO: Change to android.Manifest.permission.MANAGE_lowpanInterfaceS
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CHANGE_LOWPAN_STATE, "LowpanService");
+        try {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.MANAGE_LOWPAN_INTERFACES, "LowpanService");
+        } catch (SecurityException x) {
+            if (!mIsAndroidThings) {
+                throw x;
+            }
+            mContext.enforceCallingOrSelfPermission(
+                    "com.google.android.things.permission.MANAGE_LOWPAN_INTERFACES", "LowpanService");
+        }
     }
 
     public ILowpanInterface getInterface(String name) {
diff --git a/tests/commandtest.sh b/tests/commandtest.sh
index fb0fa0e..b80feb6 100755
--- a/tests/commandtest.sh
+++ b/tests/commandtest.sh
@@ -14,32 +14,45 @@
   exit 1
 fi
 
-./prepdevice.sh || die "Unable to prepare device"
+adb wait-for-device || die
 
+echo "Running form command test. . ."
 sleep 2
 
-echo "Running tests. . ."
-
-set -x # print commands
-
+# Clobber any existing instance of wpantund
 adb shell killall wpantund 2> /dev/null
 
-adb shell wpantund -s 'system:ot-ncp\ 1' -o Config:Daemon:ExternalNetifManagement 1 &
+# Start wpantund
+echo "+ adb shell wpantund -I wpan5 -s 'system:ot-ncp\ 1' -o Config:Daemon:ExternalNetifManagement 1 &"
+adb shell wpantund -I wpan5 -s 'system:ot-ncp\ 1' -o Config:Daemon:ExternalNetifManagement 1 &
 WPANTUND_PID=$!
 trap "kill -HUP $WPANTUND_PID 2> /dev/null" EXIT INT TERM
 
+# Verify wpantund started properly
 sleep 2
-
 kill -0 $WPANTUND_PID || die "wpantund failed to start"
-
 sleep 2
 
-adb shell lowpanctl status || die
-adb shell lowpanctl form blahnet || die
-adb shell lowpanctl status || die
-adb shell ifconfig wpan0 || die
+echo "+ adb shell lowpanctl -I wpan5 status"
+adb shell lowpanctl -I wpan5 status || die
+echo "+ adb shell lowpanctl -I wpan5 form blahnet"
+adb shell lowpanctl -I wpan5 form blahnet || die
+echo "+ adb shell lowpanctl -I wpan5 status"
+adb shell lowpanctl -I wpan5 status || die
+echo "+ adb shell ifconfig wpan5"
+adb shell ifconfig wpan5 || die
+echo "+ adb shell dumpsys netd"
+adb shell dumpsys netd || die
+echo "+ adb shell ip -6 rule"
+adb shell ip -6 rule || die
+echo "+ adb shell ip -6 route list table wpan5"
+adb shell ip -6 route list table wpan5 || die
 
-set +x # Turn off printing commands
+if [ "shell" = "$1" ]
+then
+	echo "+ adb shell"
+	adb shell
+fi
 
-echo Finished.
+echo "Finished form command test."
 
diff --git a/tests/jointest.sh b/tests/jointest.sh
new file mode 100755
index 0000000..3a41dcc
--- /dev/null
+++ b/tests/jointest.sh
@@ -0,0 +1,84 @@
+#!/usr/bin/env bash
+
+cd "`dirname $0`"
+
+if [ "shell" = "$1" ]
+then
+	WANTS_SHELL=1
+fi
+
+possibly_enter_shell() {
+	if [ "$WANTS_SHELL" = "1" ]
+	then
+		echo " *** Entering adb shell:"
+		adb shell
+	fi
+}
+
+die () {
+	set +x # Turn off printing commands
+	echo ""
+	echo " *** fatal error: $*"
+	possibly_enter_shell
+	exit 1
+}
+
+if [ -z $ANDROID_BUILD_TOP ]; then
+  echo "You need to source and lunch before you can use this script"
+  exit 1
+fi
+
+adb wait-for-device || die
+
+echo "Running join command test. . ."
+sleep 2
+
+adb shell killall wpantund 2> /dev/null
+
+adb shell wpantund -I wpan5 -s 'system:ot-ncp\ 1' -o Config:Daemon:ExternalNetifManagement 1 &
+WPANTUND_1_PID=$!
+adb shell wpantund -I wpan6 -s 'system:ot-ncp\ 2' -o Config:Daemon:ExternalNetifManagement 1 &
+WPANTUND_2_PID=$!
+trap "kill -HUP $WPANTUND_1_PID $WPANTUND_2_PID 2> /dev/null" EXIT INT TERM
+
+sleep 2
+
+kill -0 $WPANTUND_1_PID  || die "wpantund failed to start"
+kill -0 $WPANTUND_2_PID  || die "wpantund failed to start"
+
+sleep 2
+
+echo "+ adb shell lowpanctl -I wpan5 status"
+adb shell lowpanctl -I wpan5 status || die
+echo "+ adb shell lowpanctl -I wpan5 form blahnet --panid 1234 --xpanid 0011223344556677 --channel 11"
+adb shell lowpanctl -I wpan5 form blahnet --panid 1234 --xpanid 0011223344556677 --channel 11 || die
+echo "+ adb shell lowpanctl -I wpan5 status"
+adb shell lowpanctl -I wpan5 status || die
+echo "+ adb shell lowpanctl -I wpan5 show-credential"
+adb shell lowpanctl -I wpan5 show-credential || die
+
+CREDENTIAL=`adb shell lowpanctl -I wpan5 show-credential -r` || die
+
+echo "+ adb shell lowpanctl -I wpan6 status"
+adb shell lowpanctl -I wpan6 status || die
+echo "+ adb shell lowpanctl -I wpan6 scan"
+adb shell lowpanctl -I wpan6 scan || die
+echo "+ adb shell lowpanctl -I wpan6 join blahnet --panid 1234 --xpanid 0011223344556677 --channel 11 --master-key ${CREDENTIAL}"
+adb shell lowpanctl -I wpan6 join blahnet --panid 1234 --xpanid 0011223344556677 --channel 11 --master-key ${CREDENTIAL} || die
+
+sleep 2
+
+echo "+ adb shell lowpanctl -I wpan6 status"
+adb shell lowpanctl -I wpan6 status || die
+
+WPAN5_LL_ADDR=`adb shell lowpanctl -I wpan5 status | grep fe80:: | sed -e 's:^[^a-f:0-9]*\([a-f:0-9]*\)/.*:\1:i'`
+WPAN6_LL_ADDR=`adb shell lowpanctl -I wpan6 status | grep fe80:: | sed -e 's:^[^a-f:0-9]*\([a-f:0-9]*\)/.*:\1:i'`
+
+echo "+ ping6 -c 4 -w 6 ${WPAN5_LL_ADDR}%wpan6"
+adb shell ping6 -c 4 -w 6 ${WPAN5_LL_ADDR}%wpan6 || die
+echo "+ ping6 -c 4 -w 6 ${WPAN6_LL_ADDR}%wpan5"
+adb shell ping6 -c 4 -w 6 ${WPAN6_LL_ADDR}%wpan5 || die
+
+possibly_enter_shell
+
+echo "Finished join command test."
diff --git a/tests/prepdevice.sh b/tests/prepdevice.sh
index 027d64d..b930f40 100755
--- a/tests/prepdevice.sh
+++ b/tests/prepdevice.sh
@@ -28,8 +28,7 @@
 adb wait-for-device || die
 adb remount || die
 adb shell stop || die
-adb disable-verity
-adb sync || die
+adb sync system || die
 adb shell start || die
 
 sleep 2
diff --git a/tests/scantest.sh b/tests/scantest.sh
new file mode 100755
index 0000000..aee9f1a
--- /dev/null
+++ b/tests/scantest.sh
@@ -0,0 +1,44 @@
+#!/usr/bin/env bash
+
+cd "`dirname $0`"
+
+die () {
+	set +x # Turn off printing commands
+	echo ""
+	echo " *** fatal error: $*"
+	exit 1
+}
+
+if [ -z $ANDROID_BUILD_TOP ]; then
+  echo "You need to source and lunch before you can use this script"
+  exit 1
+fi
+
+adb wait-for-device || die
+
+echo "Running scan command test. . ."
+sleep 2
+
+adb shell killall wpantund 2> /dev/null
+
+echo "+ adb shell wpantund -I wpan5 -s 'system:ot-ncp\ 1' -o Config:Daemon:ExternalNetifManagement 1 &"
+adb shell wpantund -I wpan5 -s 'system:ot-ncp\ 1' -o Config:Daemon:ExternalNetifManagement 1 &
+WPANTUND_1_PID=$!
+echo "+ adb shell wpantund -I wpan6 -s 'system:ot-ncp\ 2' -o Config:Daemon:ExternalNetifManagement 1 &"
+adb shell wpantund -I wpan6 -s 'system:ot-ncp\ 2' -o Config:Daemon:ExternalNetifManagement 1 &
+WPANTUND_2_PID=$!
+trap "kill -HUP $WPANTUND_1_PID $WPANTUND_2_PID 2> /dev/null" EXIT INT TERM
+
+sleep 2
+kill -0 $WPANTUND_1_PID  || die "wpantund failed to start"
+kill -0 $WPANTUND_2_PID  || die "wpantund failed to start"
+sleep 2
+
+echo "+ adb shell lowpanctl -I wpan5 form blahnet"
+adb shell lowpanctl -I wpan5 form blahnet || die
+echo "+ adb shell lowpanctl -I wpan5 status"
+adb shell lowpanctl -I wpan5 status || die
+echo "+ adb shell lowpanctl -I wpan6 scan"
+adb shell lowpanctl -I wpan6 scan || die
+
+echo "Finished scan command test."