Merge "Updates and fixes to android.net.lowpan"
diff --git a/lowpan/java/android/net/lowpan/ILowpanInterface.aidl b/lowpan/java/android/net/lowpan/ILowpanInterface.aidl
index 647fcc1..329e9fa 100644
--- a/lowpan/java/android/net/lowpan/ILowpanInterface.aidl
+++ b/lowpan/java/android/net/lowpan/ILowpanInterface.aidl
@@ -37,41 +37,79 @@
     //////////////////////////////////////////////////////////////////////////
     // 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[] */
     const String KEY_CHANNEL_MASK       = "android.net.lowpan.property.CHANNEL_MASK";
+
+    /** 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";
 
-    const String KEY_LINK_ADDRESS_ARRAY = "android.net.lowpan.property.LINK_ADDRESS_ARRAY";
-    const String KEY_ROUTE_INFO_ARRAY   = "android.net.lowpan.property.ROUTE_INFO_ARRAY";
-
+    /** 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";
 
-    /** @hide */
+    /** Type: byte[]
+     * @hide */
     const String KEY_EXTENDED_ADDRESS = "android.net.lowpan.property.EXTENDED_ADDRESS";
 
-    /** @hide */
+    /** Type: byte[]
+     * @hide */
     const String KEY_MAC_ADDRESS      = "android.net.lowpan.property.MAC_ADDRESS";
 
     //////////////////////////////////////////////////////////////////////////
@@ -144,6 +182,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);
 
diff --git a/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl b/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl
index c99d732..0ae01a1 100644
--- a/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl
+++ b/lowpan/java/android/net/lowpan/ILowpanInterfaceListener.aidl
@@ -16,7 +16,15 @@
 
 package android.net.lowpan;
 
+import android.net.IpPrefix;
+
 /** {@hide} */
 interface ILowpanInterfaceListener {
     oneway void onPropertiesChanged(in Map properties);
+
+    oneway void onLinkNetworkAdded(in IpPrefix prefix);
+    oneway void onLinkNetworkRemoved(in IpPrefix prefix);
+
+    oneway void onLinkAddressAdded(in String address);
+    oneway void onLinkAddressRemoved(in String address);
 }
diff --git a/lowpan/java/android/net/lowpan/ILowpanManager.aidl b/lowpan/java/android/net/lowpan/ILowpanManager.aidl
index 5a8d7dc..326aa65 100644
--- a/lowpan/java/android/net/lowpan/ILowpanManager.aidl
+++ b/lowpan/java/android/net/lowpan/ILowpanManager.aidl
@@ -21,6 +21,7 @@
 /** {@hide} */
 interface ILowpanManager {
 
+    /* Keep this in sync with Context.LOWPAN_SERVICE */
     const String LOWPAN_SERVICE_NAME = "lowpan";
 
     ILowpanInterface getInterface(@utf8InCpp String name);
diff --git a/lowpan/java/android/net/lowpan/LowpanException.java b/lowpan/java/android/net/lowpan/LowpanException.java
index b43b2fe..5a1f729 100644
--- a/lowpan/java/android/net/lowpan/LowpanException.java
+++ b/lowpan/java/android/net/lowpan/LowpanException.java
@@ -16,8 +16,6 @@
 
 package android.net.lowpan;
 
-import android.os.DeadObjectException;
-import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.util.AndroidException;
 
@@ -49,92 +47,76 @@
     public static final int LOWPAN_JOIN_FAILED_AT_AUTH = 16;
     public static final int LOWPAN_FORM_FAILED_AT_SCAN = 17;
 
-    /**
-     * Convert ServiceSpecificExceptions and Binder RemoteExceptions from LoWPAN binder interfaces
-     * into the correct public exceptions.
-     *
-     * @hide
-     */
-    public static void throwAsPublicException(Throwable t) throws LowpanException {
-        if (t instanceof ServiceSpecificException) {
-            ServiceSpecificException e = (ServiceSpecificException) t;
-            int reason;
-            switch (e.errorCode) {
-                case ILowpanInterface.ERROR_INVALID_ARGUMENT:
-                case ILowpanInterface.ERROR_INVALID_TYPE:
-                case ILowpanInterface.ERROR_INVALID_VALUE:
-                    throw new IllegalArgumentException(e.getMessage(), e);
+    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_PERMISSION_DENIED:
+                throw new SecurityException(e.getMessage(), e);
 
-                case ILowpanInterface.ERROR_DISABLED:
-                    reason = LowpanException.LOWPAN_DISABLED;
-                    break;
+            case ILowpanInterface.ERROR_DISABLED:
+                reason = LowpanException.LOWPAN_DISABLED;
+                break;
 
-                case ILowpanInterface.ERROR_WRONG_STATE:
-                    reason = LowpanException.LOWPAN_WRONG_STATE;
-                    break;
+            case ILowpanInterface.ERROR_WRONG_STATE:
+                reason = LowpanException.LOWPAN_WRONG_STATE;
+                break;
 
-                case ILowpanInterface.ERROR_BUSY:
-                    reason = LowpanException.LOWPAN_BUSY;
-                    break;
+            case ILowpanInterface.ERROR_BUSY:
+                reason = LowpanException.LOWPAN_BUSY;
+                break;
 
-                case ILowpanInterface.ERROR_ALREADY:
-                    reason = LowpanException.LOWPAN_ALREADY;
-                    break;
+            case ILowpanInterface.ERROR_ALREADY:
+                reason = LowpanException.LOWPAN_ALREADY;
+                break;
 
-                case ILowpanInterface.ERROR_CANCELED:
-                    reason = LowpanException.LOWPAN_CANCELED;
-                    break;
+            case ILowpanInterface.ERROR_CANCELED:
+                reason = LowpanException.LOWPAN_CANCELED;
+                break;
 
-                case ILowpanInterface.ERROR_CREDENTIAL_NEEDED:
-                    reason = LowpanException.LOWPAN_CREDENTIAL_NEEDED;
-                    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_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_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_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_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_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_FORM_FAILED_AT_SCAN:
+                reason = LowpanException.LOWPAN_FORM_FAILED_AT_SCAN;
+                break;
 
-                case ILowpanInterface.ERROR_TIMEOUT:
-                case ILowpanInterface.ERROR_NCP_PROBLEM:
-                    reason = LowpanException.LOWPAN_NCP_PROBLEM;
-                    break;
-                case ILowpanInterface.ERROR_UNSPECIFIED:
-                default:
-                    reason = LOWPAN_ERROR;
-                    break;
-            }
-            throw new LowpanException(reason, e.getMessage(), e);
-        } else if (t instanceof DeadObjectException) {
-            throw new LowpanException(LOWPAN_DEAD, t);
-        } else if (t instanceof RemoteException) {
-            throw new UnsupportedOperationException(
-                    "An unknown RemoteException was thrown" + " which should never happen.", t);
-        } else if (t instanceof RuntimeException) {
-            RuntimeException e = (RuntimeException) t;
-            throw e;
+            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;
diff --git a/lowpan/java/android/net/lowpan/LowpanIdentity.java b/lowpan/java/android/net/lowpan/LowpanIdentity.java
index 9be45ef..2d36f7f 100644
--- a/lowpan/java/android/net/lowpan/LowpanIdentity.java
+++ b/lowpan/java/android/net/lowpan/LowpanIdentity.java
@@ -53,7 +53,7 @@
         }
 
         public Builder setXpanid(byte x[]) {
-            identity.mXpanid = x.clone();
+            identity.mXpanid = (x != null ? x.clone() : null);
             return this;
         }
 
@@ -115,7 +115,7 @@
     }
 
     public byte[] getXpanid() {
-        return mXpanid.clone();
+        return mXpanid != null ? mXpanid.clone() : null;
     }
 
     public int getPanid() {
diff --git a/lowpan/java/android/net/lowpan/LowpanInterface.java b/lowpan/java/android/net/lowpan/LowpanInterface.java
index 2bb4ecd..55bf399 100644
--- a/lowpan/java/android/net/lowpan/LowpanInterface.java
+++ b/lowpan/java/android/net/lowpan/LowpanInterface.java
@@ -18,9 +18,12 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.Context;
 import android.net.IpPrefix;
+import android.net.LinkAddress;
+import android.os.DeadObjectException;
 import android.os.Handler;
-import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.util.Log;
@@ -188,60 +191,35 @@
         public void onPropertiesChanged(@NonNull Map properties) {}
     }
 
-    private ILowpanInterface mBinder;
+    private final ILowpanInterface mBinder;
+    private final Looper mLooper;
     private final HashMap<Integer, ILowpanInterfaceListener> mListenerMap = new HashMap<>();
 
-    /** Map between IBinder identity hashes and LowpanInstance objects. */
-    private static final HashMap<Integer, LowpanInterface> sInstanceMap = new HashMap<>();
+    /**
+     * Create a new LowpanInterface instance. Applications will almost always want to use {@link
+     * LowpanManager#getInterface LowpanManager.getInterface()} instead of this.
+     *
+     * @param context the application context
+     * @param service the Binder interface
+     * @param looper the Binder interface
+     * @hide
+     */
+    public LowpanInterface(Context context, ILowpanInterface service, Looper looper) {
+        /* We aren't currently using the context, but if we need
+         * it later on we can easily add it to the class.
+         */
 
-    private LowpanInterface(IBinder binder) {
-        mBinder = ILowpanInterface.Stub.asInterface(binder);
+        mBinder = service;
+        mLooper = looper;
     }
 
     /**
-     * Get the LowpanInterface object associated with this IBinder. Returns null if this IBinder
-     * does not implement the appropriate interface.
+     * Returns the ILowpanInterface object associated with this interface.
      *
      * @hide
      */
-    @NonNull
-    public static final LowpanInterface from(IBinder binder) {
-        Integer hashCode = Integer.valueOf(System.identityHashCode(binder));
-        LowpanInterface instance;
-
-        synchronized (sInstanceMap) {
-            instance = sInstanceMap.get(hashCode);
-
-            if (instance == null) {
-                instance = new LowpanInterface(binder);
-                sInstanceMap.put(hashCode, instance);
-            }
-        }
-
-        return instance;
-    }
-
-    /** {@hide} */
-    public static final LowpanInterface from(ILowpanInterface iface) {
-        return from(iface.asBinder());
-    }
-
-    /** {@hide} */
-    public static final LowpanInterface getInterfaceFromBinder(IBinder binder) {
-        return from(binder);
-    }
-
-    /**
-     * Returns the IBinder object associated with this interface.
-     *
-     * @hide
-     */
-    public IBinder getBinder() {
-        return mBinder.asBinder();
-    }
-
-    private static void throwAsPublicException(Throwable t) throws LowpanException {
-        LowpanException.throwAsPublicException(t);
+    public ILowpanInterface getService() {
+        return mBinder;
     }
 
     // Private Property Helpers
@@ -251,11 +229,10 @@
             mBinder.setProperties(properties);
 
         } catch (RemoteException x) {
-            // Catch and ignore all binder exceptions
-            Log.e(TAG, x.toString());
+            throw x.rethrowAsRuntimeException();
 
         } catch (ServiceSpecificException x) {
-            throwAsPublicException(x);
+            throw LowpanException.rethrowAsLowpanException(x);
         }
     }
 
@@ -263,13 +240,13 @@
     Map<String, Object> getProperties(String keys[]) throws LowpanException {
         try {
             return mBinder.getProperties(keys);
+
         } catch (RemoteException x) {
-            // Catch and ignore all binder exceptions
-            Log.e(TAG, x.toString());
+            throw x.rethrowAsRuntimeException();
+
         } catch (ServiceSpecificException x) {
-            throwAsPublicException(x);
+            throw LowpanException.rethrowAsLowpanException(x);
         }
-        return new HashMap();
     }
 
     /** @hide */
@@ -294,13 +271,13 @@
     <T> String getPropertyAsString(LowpanProperty<T> key) throws LowpanException {
         try {
             return mBinder.getPropertyAsString(key.getName());
+
         } catch (RemoteException x) {
-            // Catch and ignore all binder exceptions
-            Log.e(TAG, x.toString());
+            throw x.rethrowAsRuntimeException();
+
         } catch (ServiceSpecificException x) {
-            throwAsPublicException(x);
+            throw LowpanException.rethrowAsLowpanException(x);
         }
-        return null;
     }
 
     int getPropertyAsInt(LowpanProperty<Integer> key) throws LowpanException {
@@ -332,10 +309,12 @@
             Map<String, Object> parameters = new HashMap();
             provision.addToMap(parameters);
             mBinder.form(parameters);
+
         } catch (RemoteException x) {
-            throwAsPublicException(x);
+            throw x.rethrowAsRuntimeException();
+
         } catch (ServiceSpecificException x) {
-            throwAsPublicException(x);
+            throw LowpanException.rethrowAsLowpanException(x);
         }
     }
 
@@ -352,10 +331,12 @@
             Map<String, Object> parameters = new HashMap();
             provision.addToMap(parameters);
             mBinder.join(parameters);
+
         } catch (RemoteException x) {
-            throwAsPublicException(x);
+            throw x.rethrowAsRuntimeException();
+
         } catch (ServiceSpecificException x) {
-            throwAsPublicException(x);
+            throw LowpanException.rethrowAsLowpanException(x);
         }
     }
 
@@ -386,10 +367,12 @@
     public void leave() throws LowpanException {
         try {
             mBinder.leave();
+
         } catch (RemoteException x) {
-            throwAsPublicException(x);
+            throw x.rethrowAsRuntimeException();
+
         } catch (ServiceSpecificException x) {
-            throwAsPublicException(x);
+            throw LowpanException.rethrowAsLowpanException(x);
         }
     }
 
@@ -415,34 +398,26 @@
     public void reset() throws LowpanException {
         try {
             mBinder.reset();
+
         } catch (RemoteException x) {
-            throwAsPublicException(x);
+            throw x.rethrowAsRuntimeException();
+
         } catch (ServiceSpecificException x) {
-            throwAsPublicException(x);
+            throw LowpanException.rethrowAsLowpanException(x);
         }
     }
 
     // Public Getters and Setters
 
-    /**
-     * Returns the name of this network interface.
-     *
-     * <p>Will return empty string if this interface is no longer viable.
-     */
+    /** Returns the name of this network interface. */
     @NonNull
     public String getName() {
         try {
             return mBinder.getName();
+
         } catch (RemoteException x) {
-            // Catch and ignore all binder exceptions
-            // when fetching the name.
-            Log.e(TAG, x.toString());
-        } catch (ServiceSpecificException x) {
-            // Catch and ignore all service-specific exceptions
-            // when fetching the name.
-            Log.e(TAG, x.toString());
+            throw x.rethrowAsRuntimeException();
         }
-        return "";
     }
 
     /**
@@ -640,58 +615,77 @@
     public void registerCallback(@NonNull Callback cb, @Nullable Handler handler) {
         ILowpanInterfaceListener.Stub listenerBinder =
                 new ILowpanInterfaceListener.Stub() {
-                    public void onPropertiesChanged(Map properties) {
+                    private Handler mHandler;
+
+                    {
+                        if (handler != null) {
+                            mHandler = handler;
+                        } else if (mLooper != null) {
+                            mHandler = new Handler(mLooper);
+                        } else {
+                            mHandler = new Handler();
+                        }
+                    }
+
+                    @Override public void onPropertiesChanged(Map properties) {
                         Runnable runnable =
-                                new Runnable() {
-                                    @Override
-                                    public void run() {
-                                        for (String key : (Set<String>) properties.keySet()) {
-                                            Object value = properties.get(key);
-                                            switch (key) {
-                                                case ILowpanInterface.KEY_INTERFACE_ENABLED:
-                                                    cb.onEnabledChanged(
-                                                            ((Boolean) value).booleanValue());
-                                                    break;
-                                                case ILowpanInterface.KEY_INTERFACE_UP:
-                                                    cb.onUpChanged(
-                                                            ((Boolean) value).booleanValue());
-                                                    break;
-                                                case ILowpanInterface.KEY_INTERFACE_CONNECTED:
-                                                    cb.onConnectedChanged(
-                                                            ((Boolean) value).booleanValue());
-                                                    break;
-                                                case ILowpanInterface.KEY_INTERFACE_STATE:
-                                                    cb.onStateChanged((String) value);
-                                                    break;
-                                                case ILowpanInterface.KEY_NETWORK_NAME:
-                                                case ILowpanInterface.KEY_NETWORK_PANID:
-                                                case ILowpanInterface.KEY_NETWORK_XPANID:
-                                                case ILowpanInterface.KEY_CHANNEL:
-                                                    cb.onLowpanIdentityChanged(getLowpanIdentity());
-                                                    break;
-                                                case ILowpanInterface.KEY_NETWORK_ROLE:
-                                                    cb.onRoleChanged(value.toString());
-                                                    break;
-                                            }
+                                () -> {
+                                    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);
                                     }
+                                    cb.onPropertiesChanged(properties);
                                 };
 
-                        if (handler != null) {
-                            handler.post(runnable);
-                        } else {
-                            runnable.run();
-                        }
+                        mHandler.post(runnable);
+                    }
+
+                    @Override public void onLinkNetworkAdded(IpPrefix prefix) {
+                        // Support for this event isn't yet implemented.
+                    }
+
+                    @Override public void onLinkNetworkRemoved(IpPrefix prefix) {
+                        // Support for this event isn't yet implemented.
+                    }
+
+                    @Override public void onLinkAddressAdded(String address) {
+                        // Support for this event isn't yet implemented.
+                    }
+
+                    @Override public void onLinkAddressRemoved(String address) {
+                        // Support for this event isn't yet implemented.
                     }
                 };
         try {
             mBinder.addListener(listenerBinder);
         } catch (RemoteException x) {
-            // Log and ignore. If this happens, this interface
-            // is likely dead anyway.
-            Log.e(TAG, x.toString());
+            throw x.rethrowAsRuntimeException();
         }
+
         synchronized (mListenerMap) {
             mListenerMap.put(System.identityHashCode(cb), listenerBinder);
         }
@@ -728,9 +722,11 @@
 
                 try {
                     mBinder.removeListener(listenerBinder);
+                } catch (DeadObjectException x) {
+                    // We ignore a dead object exception because that
+                    // pretty clearly means our callback isn't registered.
                 } catch (RemoteException x) {
-                    // Catch and ignore all binder exceptions
-                    Log.e(TAG, x.toString());
+                    throw x.rethrowAsRuntimeException();
                 }
             }
         }
@@ -752,6 +748,46 @@
     // Route Management
 
     /**
+     * Makes a copy of the internal list of LinkAddresses.
+     *
+     * @hide
+     */
+    public LinkAddress[] copyLinkAddresses() throws LowpanException {
+        try {
+            String[] linkAddressStrings = mBinder.copyLinkAddresses();
+            LinkAddress[] ret = new LinkAddress[linkAddressStrings.length];
+            int i = 0;
+            for (String str : linkAddressStrings) {
+                ret[i++] = new LinkAddress(str);
+            }
+            return ret;
+
+        } catch (RemoteException x) {
+            throw x.rethrowAsRuntimeException();
+
+        } catch (ServiceSpecificException x) {
+            throw LowpanException.rethrowAsLowpanException(x);
+        }
+    }
+
+    /**
+     * Makes a copy of the internal list of networks reachable on via this link.
+     *
+     * @hide
+     */
+    public IpPrefix[] copyLinkNetworks() throws LowpanException {
+        try {
+            return mBinder.copyLinkNetworks();
+
+        } catch (RemoteException x) {
+            throw x.rethrowAsRuntimeException();
+
+        } catch (ServiceSpecificException x) {
+            throw LowpanException.rethrowAsLowpanException(x);
+        }
+    }
+
+    /**
      * Advertise the given IP prefix as an on-mesh prefix.
      *
      * @hide
@@ -759,10 +795,12 @@
     public void addOnMeshPrefix(IpPrefix prefix, int flags) throws LowpanException {
         try {
             mBinder.addOnMeshPrefix(prefix, flags);
+
         } catch (RemoteException x) {
-            throwAsPublicException(x);
+            throw x.rethrowAsRuntimeException();
+
         } catch (ServiceSpecificException x) {
-            throwAsPublicException(x);
+            throw LowpanException.rethrowAsLowpanException(x);
         }
     }
 
@@ -775,9 +813,10 @@
     public void removeOnMeshPrefix(IpPrefix prefix) {
         try {
             mBinder.removeOnMeshPrefix(prefix);
+
         } catch (RemoteException x) {
-            // Catch and ignore all binder exceptions
-            Log.e(TAG, x.toString());
+            throw x.rethrowAsRuntimeException();
+
         } catch (ServiceSpecificException x) {
             // Catch and ignore all service exceptions
             Log.e(TAG, x.toString());
@@ -793,10 +832,12 @@
     public void addExternalRoute(IpPrefix prefix, int flags) throws LowpanException {
         try {
             mBinder.addExternalRoute(prefix, flags);
+
         } catch (RemoteException x) {
-            throwAsPublicException(x);
+            throw x.rethrowAsRuntimeException();
+
         } catch (ServiceSpecificException x) {
-            throwAsPublicException(x);
+            throw LowpanException.rethrowAsLowpanException(x);
         }
     }
 
@@ -808,9 +849,10 @@
     public void removeExternalRoute(IpPrefix prefix) {
         try {
             mBinder.removeExternalRoute(prefix);
+
         } catch (RemoteException x) {
-            // Catch and ignore all binder exceptions
-            Log.e(TAG, x.toString());
+            throw x.rethrowAsRuntimeException();
+
         } catch (ServiceSpecificException x) {
             // Catch and ignore all service exceptions
             Log.e(TAG, x.toString());
diff --git a/lowpan/java/android/net/lowpan/LowpanManager.java b/lowpan/java/android/net/lowpan/LowpanManager.java
index ecdda49..2d974ee 100644
--- a/lowpan/java/android/net/lowpan/LowpanManager.java
+++ b/lowpan/java/android/net/lowpan/LowpanManager.java
@@ -19,14 +19,15 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.os.DeadObjectException;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.util.AndroidException;
-import android.util.Log;
+import java.lang.ref.WeakReference;
 import java.util.HashMap;
+import java.util.Map;
+import java.util.WeakHashMap;
 
 /**
  * Manager object for looking up LoWPAN interfaces.
@@ -45,72 +46,119 @@
         public void onInterfaceRemoved(LowpanInterface lowpanInterface) {}
     }
 
-    private Context mContext;
-    private ILowpanManager mManager;
-    private HashMap<Integer, ILowpanManagerListener> mListenerMap = new HashMap<>();
+    private final Map<Integer, ILowpanManagerListener> mListenerMap = new HashMap<>();
+    private final Map<String, LowpanInterface> mInterfaceCache = new HashMap<>();
 
-    private static LowpanManager sSingletonInstance;
+    /* This is a WeakHashMap because we don't want to hold onto
+     * a strong reference to ILowpanInterface, so that it can be
+     * garbage collected if it isn't being used anymore. Since
+     * the value class holds onto this specific ILowpanInterface,
+     * we also need to have a weak reference to the value.
+     * This design pattern allows us to skip removal of items
+     * from this Map without leaking memory.
+     */
+    private final Map<ILowpanInterface, WeakReference<LowpanInterface>> mBinderCache =
+            new WeakHashMap<>();
+
+    private final ILowpanManager mService;
+    private final Context mContext;
+    private final Looper mLooper;
 
     // Static Methods
 
-    /** Returns a reference to the LowpanManager object, allocating it if necessary. */
-    public static LowpanManager getManager() {
-        return from(null);
+    public static LowpanManager from(Context context) {
+        return (LowpanManager) context.getSystemService(Context.LOWPAN_SERVICE);
     }
 
-    public static LowpanManager from(Context context) {
-        // TODO: Actually get this from the context!
+    /** @hide */
+    public static LowpanManager getManager() {
+        IBinder binder = ServiceManager.getService(Context.LOWPAN_SERVICE);
 
-        if (sSingletonInstance == null) {
-            sSingletonInstance = new LowpanManager();
+        if (binder != null) {
+            ILowpanManager service = ILowpanManager.Stub.asInterface(binder);
+            return new LowpanManager(service);
         }
-        return sSingletonInstance;
+
+        return null;
     }
 
     // Constructors
 
-    /**
-     * Private LowpanManager constructor. Since we are a singleton, we do not allow external
-     * construction.
-     */
-    private LowpanManager() {}
-
-    // Private Methods
-
-    /**
-     * Returns a reference to the ILowpanManager interface, provided by the LoWPAN Manager Service.
-     */
-    @Nullable
-    private synchronized ILowpanManager getILowpanManager() {
-        // Use a local reference of this object for thread safety.
-        ILowpanManager manager = mManager;
-
-        if (manager == null) {
-            IBinder serviceBinder =
-                    new ServiceManager().getService(ILowpanManager.LOWPAN_SERVICE_NAME);
-
-            manager = ILowpanManager.Stub.asInterface(serviceBinder);
-
-            mManager = manager;
-
-            // Add any listeners
-            synchronized (mListenerMap) {
-                for (ILowpanManagerListener listener : mListenerMap.values()) {
-                    try {
-                        manager.addListener(listener);
-
-                    } catch (RemoteException x) {
-                        // Consider any failure here as implying the manager is defunct
-                        mManager = null;
-                        manager = null;
-                    }
-                }
-            }
-        }
-        return manager;
+    LowpanManager(ILowpanManager service) {
+        mService = service;
+        mContext = null;
+        mLooper = null;
     }
 
-    // Public Methods
+    /**
+     * Create a new LowpanManager instance. Applications will almost always want to use {@link
+     * android.content.Context#getSystemService Context.getSystemService()} to retrieve the standard
+     * {@link android.content.Context#LOWPAN_SERVICE Context.LOWPAN_SERVICE}.
+     *
+     * @param context the application context
+     * @param service the Binder interface
+     * @param looper the default Looper to run callbacks on
+     * @hide - hide this because it takes in a parameter of type ILowpanManager, which is a system
+     *     private class.
+     */
+    public LowpanManager(Context context, ILowpanManager service, Looper looper) {
+        mContext = context;
+        mService = service;
+        mLooper = looper;
+    }
+
+    /** @hide */
+    @Nullable
+    public LowpanInterface getInterface(@NonNull ILowpanInterface ifaceService) {
+        LowpanInterface iface = null;
+
+        try {
+            synchronized (mBinderCache) {
+                if (mBinderCache.containsKey(ifaceService)) {
+                    iface = mBinderCache.get(ifaceService).get();
+                }
+
+                if (iface == null) {
+                    String ifaceName = ifaceService.getName();
+
+                    iface = new LowpanInterface(mContext, ifaceService, mLooper);
+
+                    synchronized (mInterfaceCache) {
+                        mInterfaceCache.put(iface.getName(), iface);
+                    }
+
+                    mBinderCache.put(ifaceService, new WeakReference(iface));
+
+                    /* Make sure we remove the object from the
+                     * interface cache if the associated service
+                     * dies.
+                     */
+                    ifaceService
+                            .asBinder()
+                            .linkToDeath(
+                                    new IBinder.DeathRecipient() {
+                                        @Override
+                                        public void binderDied() {
+                                            synchronized (mInterfaceCache) {
+                                                LowpanInterface iface =
+                                                        mInterfaceCache.get(ifaceName);
+
+                                                if ((iface != null)
+                                                        && (iface.getService() == ifaceService)) {
+                                                    mInterfaceCache.remove(ifaceName);
+                                                }
+                                            }
+                                        }
+                                    },
+                                    0);
+                }
+            }
+        } catch (RemoteException x) {
+            throw x.rethrowAsRuntimeException();
+        }
+
+        return iface;
+    }
 
     /**
      * Returns a reference to the requested LowpanInterface object. If the given interface doesn't
@@ -118,27 +166,32 @@
      */
     @Nullable
     public LowpanInterface getInterface(@NonNull String name) {
-        LowpanInterface ret = null;
-        ILowpanManager manager = getILowpanManager();
+        LowpanInterface iface = null;
 
-        // Maximum number of tries is two. We should only try
-        // more than once if our manager has died or there
-        // was some sort of AIDL buffer full event.
-        for (int i = 0; i < 2 && manager != null; i++) {
-            try {
-                ILowpanInterface iface = manager.getInterface(name);
-                if (iface != null) {
-                    ret = LowpanInterface.getInterfaceFromBinder(iface.asBinder());
+        try {
+            /* This synchronized block covers both branches of the enclosed
+             * if() statement in order to avoid a race condition. Two threads
+             * calling getInterface() with the same name would race to create
+             * the associated LowpanInterface object, creating two of them.
+             * Having the whole block be synchronized avoids that race.
+             */
+            synchronized (mInterfaceCache) {
+                if (mInterfaceCache.containsKey(name)) {
+                    iface = mInterfaceCache.get(name);
+
+                } else {
+                    ILowpanInterface ifaceService = mService.getInterface(name);
+
+                    if (ifaceService != null) {
+                        iface = getInterface(ifaceService);
+                    }
                 }
-                break;
-            } catch (RemoteException x) {
-                // In all of the cases when we get this exception, we reconnect and try again
-                mManager = null;
-                manager = getILowpanManager();
             }
+        } catch (RemoteException x) {
+            throw x.rethrowFromSystemServer();
         }
 
-        return ret;
+        return iface;
     }
 
     /**
@@ -160,23 +213,11 @@
      */
     @NonNull
     public String[] getInterfaceList() {
-        ILowpanManager manager = getILowpanManager();
-
-        // Maximum number of tries is two. We should only try
-        // more than once if our manager has died or there
-        // was some sort of AIDL buffer full event.
-        for (int i = 0; i < 2 && manager != null; i++) {
-            try {
-                return manager.getInterfaceList();
-            } catch (RemoteException x) {
-                // In all of the cases when we get this exception, we reconnect and try again
-                mManager = null;
-                manager = getILowpanManager();
-            }
+        try {
+            return mService.getInterfaceList();
+        } catch (RemoteException x) {
+            throw x.rethrowFromSystemServer();
         }
-
-        // Return empty list if we have no service.
-        return new String[0];
     }
 
     /**
@@ -189,55 +230,52 @@
             throws LowpanException {
         ILowpanManagerListener.Stub listenerBinder =
                 new ILowpanManagerListener.Stub() {
-                    public void onInterfaceAdded(ILowpanInterface lowpanInterface) {
-                        Runnable runnable =
-                                new Runnable() {
-                                    @Override
-                                    public void run() {
-                                        cb.onInterfaceAdded(
-                                                LowpanInterface.getInterfaceFromBinder(
-                                                        lowpanInterface.asBinder()));
-                                    }
-                                };
+                    private Handler mHandler;
 
+                    {
                         if (handler != null) {
-                            handler.post(runnable);
+                            mHandler = handler;
+                        } else if (mLooper != null) {
+                            mHandler = new Handler(mLooper);
                         } else {
-                            runnable.run();
+                            mHandler = new Handler();
                         }
                     }
 
-                    public void onInterfaceRemoved(ILowpanInterface lowpanInterface) {
+                    @Override
+                    public void onInterfaceAdded(ILowpanInterface ifaceService) {
                         Runnable runnable =
-                                new Runnable() {
-                                    @Override
-                                    public void run() {
-                                        cb.onInterfaceRemoved(
-                                                LowpanInterface.getInterfaceFromBinder(
-                                                        lowpanInterface.asBinder()));
+                                () -> {
+                                    LowpanInterface iface = getInterface(ifaceService);
+
+                                    if (iface != null) {
+                                        cb.onInterfaceAdded(iface);
                                     }
                                 };
 
-                        if (handler != null) {
-                            handler.post(runnable);
-                        } else {
-                            runnable.run();
-                        }
+                        mHandler.post(runnable);
+                    }
+
+                    @Override
+                    public void onInterfaceRemoved(ILowpanInterface ifaceService) {
+                        Runnable runnable =
+                                () -> {
+                                    LowpanInterface iface = getInterface(ifaceService);
+
+                                    if (iface != null) {
+                                        cb.onInterfaceRemoved(iface);
+                                    }
+                                };
+
+                        mHandler.post(runnable);
                     }
                 };
-        ILowpanManager manager = getILowpanManager();
-        if (manager != null) {
-            try {
-                manager.addListener(listenerBinder);
-            } catch (DeadObjectException x) {
-                mManager = null;
-                // Tickle the ILowpanManager instance, which might
-                // get us added back.
-                getILowpanManager();
-            } catch (Throwable x) {
-                LowpanException.throwAsPublicException(x);
-            }
+        try {
+            mService.addListener(listenerBinder);
+        } catch (RemoteException x) {
+            throw x.rethrowFromSystemServer();
         }
+
         synchronized (mListenerMap) {
             mListenerMap.put(Integer.valueOf(System.identityHashCode(cb)), listenerBinder);
         }
@@ -253,20 +291,23 @@
      *
      * @hide
      */
-    public void unregisterCallback(@NonNull Callback cb) throws AndroidException {
+    public void unregisterCallback(@NonNull Callback cb) {
         Integer hashCode = Integer.valueOf(System.identityHashCode(cb));
-        ILowpanManagerListener listenerBinder = mListenerMap.get(hashCode);
+        ILowpanManagerListener listenerBinder = null;
+
+        synchronized (mListenerMap) {
+            listenerBinder = mListenerMap.get(hashCode);
+            mListenerMap.remove(hashCode);
+        }
+
         if (listenerBinder != null) {
-            synchronized (mListenerMap) {
-                mListenerMap.remove(hashCode);
+            try {
+                mService.removeListener(listenerBinder);
+            } catch (RemoteException x) {
+                throw x.rethrowFromSystemServer();
             }
-            if (getILowpanManager() != null) {
-                try {
-                    mManager.removeListener(listenerBinder);
-                } catch (DeadObjectException x) {
-                    mManager = null;
-                }
-            }
+        } else {
+            throw new RuntimeException("Attempt to unregister an unknown callback");
         }
     }
 }
diff --git a/lowpan/java/android/net/lowpan/LowpanProperties.java b/lowpan/java/android/net/lowpan/LowpanProperties.java
index 0d5acc2..f835260 100644
--- a/lowpan/java/android/net/lowpan/LowpanProperties.java
+++ b/lowpan/java/android/net/lowpan/LowpanProperties.java
@@ -16,9 +16,8 @@
 
 package android.net.lowpan;
 
+import android.net.IpPrefix;
 import android.net.LinkAddress;
-import android.net.RouteInfo;
-import java.util.List;
 
 /** {@hide} */
 public final class LowpanProperties {
@@ -77,14 +76,6 @@
     public static final LowpanProperty<String> KEY_NCP_VERSION =
             new LowpanStandardProperty("android.net.lowpan.property.NCP_VERSION", String.class);
 
-    public static final LowpanProperty<List<LinkAddress>> KEY_LINK_ADDRESS_ARRAY =
-            new LowpanStandardProperty(
-                    "android.net.lowpan.property.LINK_ADDRESS_ARRAY", LinkAddress[].class);
-
-    public static final LowpanProperty<List<RouteInfo>> KEY_ROUTE_INFO_ARRAY =
-            new LowpanStandardProperty(
-                    "android.net.lowpan.property.ROUTE_INFO_ARRAY", RouteInfo[].class);
-
     /** @hide */
     public static final LowpanProperty<byte[]> KEY_EXTENDED_ADDRESS =
             new LowpanStandardProperty(
diff --git a/lowpan/java/android/net/lowpan/LowpanScanner.java b/lowpan/java/android/net/lowpan/LowpanScanner.java
index e0df55d9..b0557ee 100644
--- a/lowpan/java/android/net/lowpan/LowpanScanner.java
+++ b/lowpan/java/android/net/lowpan/LowpanScanner.java
@@ -226,8 +226,12 @@
 
         try {
             mBinder.startNetScan(map, binderListener);
-        } catch (ServiceSpecificException | RemoteException x) {
-            LowpanException.throwAsPublicException(x);
+
+        } catch (RemoteException x) {
+            throw x.rethrowAsRuntimeException();
+
+        } catch (ServiceSpecificException x) {
+            throw LowpanException.rethrowAsLowpanException(x);
         }
     }
 
@@ -239,8 +243,11 @@
     public void stopNetScan() {
         try {
             mBinder.stopNetScan();
+
         } catch (RemoteException x) {
-            // Catch and ignore all binder exceptions
+            throw x.rethrowAsRuntimeException();
+
+        } catch (ServiceSpecificException x) {
             Log.e(TAG, x.toString());
         }
     }
@@ -303,10 +310,12 @@
 
         try {
             mBinder.startEnergyScan(map, binderListener);
+
         } catch (RemoteException x) {
-            LowpanException.throwAsPublicException(x);
+            throw x.rethrowAsRuntimeException();
+
         } catch (ServiceSpecificException x) {
-            LowpanException.throwAsPublicException(x);
+            throw LowpanException.rethrowAsLowpanException(x);
         }
     }
 
@@ -318,8 +327,11 @@
     public void stopEnergyScan() {
         try {
             mBinder.stopEnergyScan();
+
         } catch (RemoteException x) {
-            // Catch and ignore all binder exceptions
+            throw x.rethrowAsRuntimeException();
+
+        } catch (ServiceSpecificException x) {
             Log.e(TAG, x.toString());
         }
     }