Manually merge commit 'b0b6afc' into nyc-dev-plus-aosp
diff --git a/Android.mk b/Android.mk
index bc4f335..69b0bcb 100644
--- a/Android.mk
+++ b/Android.mk
@@ -449,9 +449,9 @@
 	telephony/java/com/android/internal/telephony/ITelephonyRegistry.aidl \
 	telephony/java/com/android/internal/telephony/IWapPushManager.aidl \
 	wifi/java/android/net/wifi/IWifiManager.aidl \
-	wifi/java/android/net/wifi/nan/IWifiNanEventListener.aidl \
+	wifi/java/android/net/wifi/nan/IWifiNanEventCallback.aidl \
 	wifi/java/android/net/wifi/nan/IWifiNanManager.aidl \
-	wifi/java/android/net/wifi/nan/IWifiNanSessionListener.aidl \
+	wifi/java/android/net/wifi/nan/IWifiNanSessionCallback.aidl \
 	wifi/java/android/net/wifi/p2p/IWifiP2pManager.aidl \
 	wifi/java/android/net/wifi/IWifiScanner.aidl \
 	wifi/java/android/net/wifi/IRttManager.aidl \
@@ -540,10 +540,8 @@
 	frameworks/base/media/java/android/media/browse/MediaBrowser.aidl \
 	frameworks/base/wifi/java/android/net/wifi/ScanSettings.aidl \
 	frameworks/base/wifi/java/android/net/wifi/nan/ConfigRequest.aidl \
-	frameworks/base/wifi/java/android/net/wifi/nan/PublishData.aidl \
-	frameworks/base/wifi/java/android/net/wifi/nan/SubscribeData.aidl \
-	frameworks/base/wifi/java/android/net/wifi/nan/PublishSettings.aidl \
-	frameworks/base/wifi/java/android/net/wifi/nan/SubscribeSettings.aidl \
+	frameworks/base/wifi/java/android/net/wifi/nan/PublishConfig.aidl \
+	frameworks/base/wifi/java/android/net/wifi/nan/SubscribeConfig.aidl \
 	frameworks/base/wifi/java/android/net/wifi/p2p/WifiP2pInfo.aidl \
 	frameworks/base/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.aidl \
 	frameworks/base/wifi/java/android/net/wifi/p2p/WifiP2pConfig.aidl \
diff --git a/cmds/svc/src/com/android/commands/svc/WifiCommand.java b/cmds/svc/src/com/android/commands/svc/WifiCommand.java
index 94214ff..633dd97 100644
--- a/cmds/svc/src/com/android/commands/svc/WifiCommand.java
+++ b/cmds/svc/src/com/android/commands/svc/WifiCommand.java
@@ -52,7 +52,7 @@
                 IWifiManager wifiMgr
                         = IWifiManager.Stub.asInterface(ServiceManager.getService(Context.WIFI_SERVICE));
                 try {
-                    wifiMgr.setWifiEnabled(flag);
+                    wifiMgr.setWifiEnabled("com.android.shell", flag);
                 }
                 catch (RemoteException e) {
                     System.err.println("Wi-Fi operation failed: " + e);
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 55744b9..42ddf10 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -515,15 +515,15 @@
             }});
 
         registerService(Context.WIFI_NAN_SERVICE, WifiNanManager.class,
-                new StaticServiceFetcher<WifiNanManager>() {
+                new CachedServiceFetcher<WifiNanManager>() {
             @Override
-            public WifiNanManager createService() {
+            public WifiNanManager createService(ContextImpl ctx) {
                 IBinder b = ServiceManager.getService(Context.WIFI_NAN_SERVICE);
                 IWifiNanManager service = IWifiNanManager.Stub.asInterface(b);
                 if (service == null) {
                     return null;
                 }
-                return new WifiNanManager(service);
+                return new WifiNanManager(ctx.getOuterContext(), service);
             }});
 
         registerService(Context.WIFI_SCANNING_SERVICE, WifiScanner.class,
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 6107f37..fee927c 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3135,7 +3135,7 @@
     /**
      * Use with {@link #getSystemService} to retrieve a
      * {@link android.net.wifi.nan.WifiNanManager} for handling management of
-     * Wi-Fi NAN discovery and connections.
+     * Wi-Fi NAN.
      *
      * @see #getSystemService
      * @see android.net.wifi.nan.WifiNanManager
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b06568c..089a420 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2028,8 +2028,7 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
-     * {@link #hasSystemFeature}: The device supports Wi-Fi Aware (NAN)
-     * networking.
+     * {@link #hasSystemFeature}: The device supports Wi-Fi NAN.
      *
      * @hide PROPOSED_NAN_API
      */
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl
index 36ba696..e1dd02a 100644
--- a/core/java/android/os/INetworkManagementService.aidl
+++ b/core/java/android/os/INetworkManagementService.aidl
@@ -23,7 +23,6 @@
 import android.net.NetworkStats;
 import android.net.RouteInfo;
 import android.net.UidRange;
-import android.net.wifi.WifiConfiguration;
 import android.os.INetworkActivityListener;
 
 /**
@@ -217,27 +216,6 @@
     void detachPppd(String tty);
 
     /**
-     * Load firmware for operation in the given mode. Currently the three
-     * modes supported are "AP", "STA" and "P2P".
-     */
-    void wifiFirmwareReload(String wlanIface, String mode);
-
-    /**
-     * Start Wifi Access Point
-     */
-    void startAccessPoint(in WifiConfiguration wifiConfig, String iface);
-
-    /**
-     * Stop Wifi Access Point
-     */
-    void stopAccessPoint(String iface);
-
-    /**
-     * Set Access Point config
-     */
-    void setAccessPoint(in WifiConfiguration wifiConfig, String iface);
-
-    /**
      ** DATA USAGE RELATED
      **/
 
diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java
index 39fd36b..b0d45e1d1 100644
--- a/core/java/com/android/internal/util/StateMachine.java
+++ b/core/java/com/android/internal/util/StateMachine.java
@@ -29,8 +29,8 @@
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Collection;
-import java.util.Iterator;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Vector;
 
 /**
@@ -750,6 +750,14 @@
         /** The destination state when transitionTo has been invoked */
         private State mDestState;
 
+        /**
+         * Indicates if a transition is in progress
+         *
+         * This will be true for all calls of State.exit and all calls of State.enter except for the
+         * last enter call for the current destination state.
+         */
+        private boolean mTransitionInProgress = false;
+
         /** The list of deferred messages */
         private ArrayList<Message> mDeferredMessages = new ArrayList<Message>();
 
@@ -862,6 +870,8 @@
                      * invoke the exit methods then the enter methods.
                      */
                     StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
+                    // flag is cleared in invokeEnterMethods before entering the target state
+                    mTransitionInProgress = true;
                     invokeExitMethods(commonStateInfo);
                     int stateStackEnteringIndex = moveTempStateStackToStateStack();
                     invokeEnterMethods(stateStackEnteringIndex);
@@ -1017,10 +1027,15 @@
          */
         private final void invokeEnterMethods(int stateStackEnteringIndex) {
             for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {
+                if (stateStackEnteringIndex == mStateStackTopIndex) {
+                    // Last enter state for transition
+                    mTransitionInProgress = false;
+                }
                 if (mDbg) mSm.log("invokeEnterMethods: " + mStateStack[i].state.getName());
                 mStateStack[i].state.enter();
                 mStateStack[i].active = true;
             }
+            mTransitionInProgress = false; // ensure flag set to false if no methods called
         }
 
         /**
@@ -1196,6 +1211,10 @@
 
         /** @see StateMachine#transitionTo(IState) */
         private final void transitionTo(IState destState) {
+            if (mTransitionInProgress) {
+                Log.wtf(mSm.mName, "transitionTo called while transition already in progress to " +
+                        mDestState + ", new target state=" + destState);
+            }
             mDestState = (State) destState;
             if (mDbg) mSm.log("transitionTo: destState=" + mDestState.getName());
         }
@@ -1305,7 +1324,7 @@
      * @param state the state to add
      * @param parent the parent of state
      */
-    protected final void addState(State state, State parent) {
+    public final void addState(State state, State parent) {
         mSmHandler.addState(state, parent);
     }
 
@@ -1313,7 +1332,7 @@
      * Add a new state to the state machine, parent will be null
      * @param state to add
      */
-    protected final void addState(State state) {
+    public final void addState(State state) {
         mSmHandler.addState(state, null);
     }
 
@@ -1323,14 +1342,14 @@
      *
      * @param initialState is the state which will receive the first message.
      */
-    protected final void setInitialState(State initialState) {
+    public final void setInitialState(State initialState) {
         mSmHandler.setInitialState(initialState);
     }
 
     /**
      * @return current message
      */
-    protected final Message getCurrentMessage() {
+    public final Message getCurrentMessage() {
         // mSmHandler can be null if the state machine has quit.
         SmHandler smh = mSmHandler;
         if (smh == null) return null;
@@ -1340,7 +1359,7 @@
     /**
      * @return current state
      */
-    protected final IState getCurrentState() {
+    public final IState getCurrentState() {
         // mSmHandler can be null if the state machine has quit.
         SmHandler smh = mSmHandler;
         if (smh == null) return null;
@@ -1361,7 +1380,7 @@
      *
      * @param destState will be the state that receives the next message.
      */
-    protected final void transitionTo(IState destState) {
+    public final void transitionTo(IState destState) {
         mSmHandler.transitionTo(destState);
     }
 
@@ -1372,7 +1391,7 @@
      * for all subsequent messages haltedProcessMessage
      * will be called.
      */
-    protected final void transitionToHaltingState() {
+    public final void transitionToHaltingState() {
         mSmHandler.transitionTo(mSmHandler.mHaltingState);
     }
 
@@ -1385,7 +1404,7 @@
      *
      * @param msg is deferred until the next transition.
      */
-    protected final void deferMessage(Message msg) {
+    public final void deferMessage(Message msg) {
         mSmHandler.deferMessage(msg);
     }
 
@@ -1496,7 +1515,7 @@
      *
      * @param string
      */
-    protected void addLogRec(String string) {
+    public void addLogRec(String string) {
         // mSmHandler can be null if the state machine has quit.
         SmHandler smh = mSmHandler;
         if (smh == null) return;
@@ -1642,7 +1661,7 @@
      *
      * Message is ignored if state machine has quit.
      */
-    public final void sendMessage(int what) {
+    public void sendMessage(int what) {
         // mSmHandler can be null if the state machine has quit.
         SmHandler smh = mSmHandler;
         if (smh == null) return;
@@ -1655,7 +1674,7 @@
      *
      * Message is ignored if state machine has quit.
      */
-    public final void sendMessage(int what, Object obj) {
+    public void sendMessage(int what, Object obj) {
         // mSmHandler can be null if the state machine has quit.
         SmHandler smh = mSmHandler;
         if (smh == null) return;
@@ -1668,7 +1687,7 @@
      *
      * Message is ignored if state machine has quit.
      */
-    public final void sendMessage(int what, int arg1) {
+    public void sendMessage(int what, int arg1) {
         // mSmHandler can be null if the state machine has quit.
         SmHandler smh = mSmHandler;
         if (smh == null) return;
@@ -1681,7 +1700,7 @@
      *
      * Message is ignored if state machine has quit.
      */
-    public final void sendMessage(int what, int arg1, int arg2) {
+    public void sendMessage(int what, int arg1, int arg2) {
         // mSmHandler can be null if the state machine has quit.
         SmHandler smh = mSmHandler;
         if (smh == null) return;
@@ -1694,7 +1713,7 @@
      *
      * Message is ignored if state machine has quit.
      */
-    public final void sendMessage(int what, int arg1, int arg2, Object obj) {
+    public void sendMessage(int what, int arg1, int arg2, Object obj) {
         // mSmHandler can be null if the state machine has quit.
         SmHandler smh = mSmHandler;
         if (smh == null) return;
@@ -1707,7 +1726,7 @@
      *
      * Message is ignored if state machine has quit.
      */
-    public final void sendMessage(Message msg) {
+    public void sendMessage(Message msg) {
         // mSmHandler can be null if the state machine has quit.
         SmHandler smh = mSmHandler;
         if (smh == null) return;
@@ -1720,7 +1739,7 @@
      *
      * Message is ignored if state machine has quit.
      */
-    public final void sendMessageDelayed(int what, long delayMillis) {
+    public void sendMessageDelayed(int what, long delayMillis) {
         // mSmHandler can be null if the state machine has quit.
         SmHandler smh = mSmHandler;
         if (smh == null) return;
@@ -1733,7 +1752,7 @@
      *
      * Message is ignored if state machine has quit.
      */
-    public final void sendMessageDelayed(int what, Object obj, long delayMillis) {
+    public void sendMessageDelayed(int what, Object obj, long delayMillis) {
         // mSmHandler can be null if the state machine has quit.
         SmHandler smh = mSmHandler;
         if (smh == null) return;
@@ -1746,7 +1765,7 @@
      *
      * Message is ignored if state machine has quit.
      */
-    public final void sendMessageDelayed(int what, int arg1, long delayMillis) {
+    public void sendMessageDelayed(int what, int arg1, long delayMillis) {
         // mSmHandler can be null if the state machine has quit.
         SmHandler smh = mSmHandler;
         if (smh == null) return;
@@ -1759,7 +1778,7 @@
      *
      * Message is ignored if state machine has quit.
      */
-    public final void sendMessageDelayed(int what, int arg1, int arg2, long delayMillis) {
+    public void sendMessageDelayed(int what, int arg1, int arg2, long delayMillis) {
         // mSmHandler can be null if the state machine has quit.
         SmHandler smh = mSmHandler;
         if (smh == null) return;
@@ -1772,7 +1791,7 @@
      *
      * Message is ignored if state machine has quit.
      */
-    public final void sendMessageDelayed(int what, int arg1, int arg2, Object obj,
+    public void sendMessageDelayed(int what, int arg1, int arg2, Object obj,
             long delayMillis) {
         // mSmHandler can be null if the state machine has quit.
         SmHandler smh = mSmHandler;
@@ -1786,7 +1805,7 @@
      *
      * Message is ignored if state machine has quit.
      */
-    public final void sendMessageDelayed(Message msg, long delayMillis) {
+    public void sendMessageDelayed(Message msg, long delayMillis) {
         // mSmHandler can be null if the state machine has quit.
         SmHandler smh = mSmHandler;
         if (smh == null) return;
@@ -1947,7 +1966,7 @@
     /**
      * Quit the state machine after all currently queued up messages are processed.
      */
-    protected final void quit() {
+    public final void quit() {
         // mSmHandler can be null if the state machine is already stopped.
         SmHandler smh = mSmHandler;
         if (smh == null) return;
@@ -1958,7 +1977,7 @@
     /**
      * Quit the state machine immediately all currently queued messages will be discarded.
      */
-    protected final void quitNow() {
+    public final void quitNow() {
         // mSmHandler can be null if the state machine is already stopped.
         SmHandler smh = mSmHandler;
         if (smh == null) return;
diff --git a/core/java/com/android/internal/util/XmlUtils.java b/core/java/com/android/internal/util/XmlUtils.java
index 992cb4e..83d5ce3 100644
--- a/core/java/com/android/internal/util/XmlUtils.java
+++ b/core/java/com/android/internal/util/XmlUtils.java
@@ -392,10 +392,10 @@
         StringBuilder sb = new StringBuilder(val.length*2);
         for (int i=0; i<N; i++) {
             int b = val[i];
-            int h = b>>4;
-            sb.append(h >= 10 ? ('a'+h-10) : ('0'+h));
-            h = b&0xff;
-            sb.append(h >= 10 ? ('a'+h-10) : ('0'+h));
+            int h = (b >> 4) & 0x0f;
+            sb.append((char)(h >= 10 ? ('a'+h-10) : ('0'+h)));
+            h = b & 0x0f;
+            sb.append((char)(h >= 10 ? ('a'+h-10) : ('0'+h)));
         }
 
         out.text(sb.toString());
@@ -996,6 +996,73 @@
     }
 
     /**
+     * Read a byte[] object from an XmlPullParser.  The XML data could
+     * previously have been generated by writeByteArrayXml().  The XmlPullParser
+     * must be positioned <em>after</em> the tag that begins the list.
+     *
+     * @param parser The XmlPullParser from which to read the list data.
+     * @param endTag Name of the tag that will end the list, usually "list".
+     * @param name An array of one string, used to return the name attribute
+     *             of the list's tag.
+     *
+     * @return Returns a newly generated byte[].
+     *
+     * @see #writeByteArrayXml
+     */
+    public static final byte[] readThisByteArrayXml(XmlPullParser parser,
+            String endTag, String[] name)
+            throws XmlPullParserException, java.io.IOException {
+
+        int num;
+        try {
+            num = Integer.parseInt(parser.getAttributeValue(null, "num"));
+        } catch (NullPointerException e) {
+            throw new XmlPullParserException(
+                    "Need num attribute in byte-array");
+        } catch (NumberFormatException e) {
+            throw new XmlPullParserException(
+                    "Not a number in num attribute in byte-array");
+        }
+
+        byte[] array = new byte[num];
+
+        int eventType = parser.getEventType();
+        do {
+            if (eventType == parser.TEXT) {
+                if (num > 0) {
+                    String values = parser.getText();
+                    if (values == null || values.length() != num * 2) {
+                        throw new XmlPullParserException(
+                                "Invalid value found in byte-array: " + values);
+                    }
+                    // This is ugly, but keeping it to mirror the logic in #writeByteArrayXml.
+                    for (int i = 0; i < num; i ++) {
+                        char nibbleHighChar = values.charAt(2 * i);
+                        char nibbleLowChar = values.charAt(2 * i + 1);
+                        int nibbleHigh = nibbleHighChar > 'a' ? (nibbleHighChar - 'a' + 10)
+                                : (nibbleHighChar - '0');
+                        int nibbleLow = nibbleLowChar > 'a' ? (nibbleLowChar - 'a' + 10)
+                                : (nibbleLowChar - '0');
+                        array[i] = (byte) ((nibbleHigh & 0x0F) << 4 | (nibbleLow & 0x0F));
+                    }
+                }
+            } else if (eventType == parser.END_TAG) {
+                if (parser.getName().equals(endTag)) {
+                    return array;
+                } else {
+                    throw new XmlPullParserException(
+                            "Expected " + endTag + " end tag at: "
+                                    + parser.getName());
+                }
+            }
+            eventType = parser.next();
+        } while (eventType != parser.END_DOCUMENT);
+
+        throw new XmlPullParserException(
+                "Document ended before " + endTag + " end tag");
+    }
+
+    /**
      * Read an int[] object from an XmlPullParser.  The XML data could
      * previously have been generated by writeIntArrayXml().  The XmlPullParser
      * must be positioned <em>after</em> the tag that begins the list.
@@ -1018,10 +1085,10 @@
             num = Integer.parseInt(parser.getAttributeValue(null, "num"));
         } catch (NullPointerException e) {
             throw new XmlPullParserException(
-                    "Need num attribute in byte-array");
+                    "Need num attribute in int-array");
         } catch (NumberFormatException e) {
             throw new XmlPullParserException(
-                    "Not a number in num attribute in byte-array");
+                    "Not a number in num attribute in int-array");
         }
         parser.next();
 
@@ -1377,6 +1444,11 @@
                 "Unexpected end of document in <string>");
         } else if ((res = readThisPrimitiveValueXml(parser, tagName)) != null) {
             // all work already done by readThisPrimitiveValueXml
+        } else if (tagName.equals("byte-array")) {
+            res = readThisByteArrayXml(parser, "byte-array", name);
+            name[0] = valueName;
+            //System.out.println("Returning value for " + valueName + ": " + res);
+            return res;
         } else if (tagName.equals("int-array")) {
             res = readThisIntArrayXml(parser, "int-array", name);
             name[0] = valueName;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f289eeb..f712c34 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -294,6 +294,7 @@
     <protected-broadcast android:name="android.net.wifi.CONFIGURED_NETWORKS_CHANGE" />
     <protected-broadcast android:name="android.net.wifi.supplicant.CONNECTION_CHANGE" />
     <protected-broadcast android:name="android.net.wifi.supplicant.STATE_CHANGE" />
+    <protected-broadcast android:name="android.net.wifi.nan.STATE_CHANGED" />
     <protected-broadcast android:name="android.net.wifi.p2p.STATE_CHANGED" />
     <protected-broadcast android:name="android.net.wifi.p2p.DISCOVERY_STATE_CHANGE" />
     <protected-broadcast android:name="android.net.wifi.p2p.THIS_DEVICE_CHANGED" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 87cb2e8..e87d8de 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -427,8 +427,11 @@
     <!-- Boolean indicating whether or not wifi firmware debugging is enabled -->
     <bool translatable="false" name="config_wifi_enable_wifi_firmware_debugging">true</bool>
 
-    <!-- Integer size limit, in KB, for a single WifiLogger ringbuffer -->
-    <integer translatable="false" name="config_wifi_logger_ring_buffer_size_limit_kb">32</integer>
+    <!-- Integer size limit, in KB, for a single WifiLogger ringbuffer, in default logging mode -->
+    <integer translatable="false" name="config_wifi_logger_ring_buffer_default_size_limit_kb">32</integer>
+
+    <!-- Integer size limit, in KB, for a single WifiLogger ringbuffer, in verbose logging mode -->
+    <integer translatable="false" name="config_wifi_logger_ring_buffer_verbose_size_limit_kb">1024</integer>
 
     <!-- Boolean indicating whether or not wifi should turn off when emergency call is made -->
     <bool translatable="false" name="config_wifi_turn_off_during_emergency_call">false</bool>
@@ -2489,4 +2492,11 @@
     <string-array translatable="false" name="config_defaultPinnerServiceFiles">
     </string-array>
 
+    <!-- Specifies whether the permissions needed by a legacy app should be
+         reviewed before any of its components can run. A legacy app is one
+         with targetSdkVersion < 23, i.e apps using the old permission model.
+         If review is not required, permissions are reviewed before the app
+         is installed. -->
+    <bool name="config_permissionReviewRequired">false</bool>
+
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d154b03..a3dd91b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -297,7 +297,8 @@
   <java-symbol type="bool" name="config_wifi_enable_disconnection_debounce" />
   <java-symbol type="bool" name="config_wifi_revert_country_code_on_cellular_loss" />
   <java-symbol type="bool" name="config_wifi_enable_wifi_firmware_debugging" />
-  <java-symbol type="integer" name="config_wifi_logger_ring_buffer_size_limit_kb" />
+  <java-symbol type="integer" name="config_wifi_logger_ring_buffer_default_size_limit_kb" />
+  <java-symbol type="integer" name="config_wifi_logger_ring_buffer_verbose_size_limit_kb" />
   <java-symbol type="bool" name="config_wifi_turn_off_during_emergency_call" />
   <java-symbol type="bool" name="config_supportMicNearUltrasound" />
   <java-symbol type="bool" name="config_supportSpeakerNearUltrasound" />
@@ -2612,4 +2613,7 @@
 
   <java-symbol type="layout" name="unsupported_display_size_dialog_content" />
   <java-symbol type="string" name="unsupported_display_size_message" />
+
+  <java-symbol type="bool" name="config_permissionReviewRequired" />
+
 </resources>
diff --git a/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java b/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java
index 302aa87..d29b572 100644
--- a/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java
@@ -39,7 +39,6 @@
 /**
  * Test for StateMachine.
  */
-@Suppress // Failing
 public class StateMachineTest extends TestCase {
     private static final String ENTER = "enter";
     private static final String EXIT = "exit";
@@ -100,7 +99,7 @@
 
         @Override
         public void onQuitting() {
-            log("onQuitting");
+            tlog("onQuitting");
             addLogRec(ON_QUITTING);
             mLogRecs = mThisSm.copyLogRecs();
             synchronized (mThisSm) {
@@ -111,7 +110,7 @@
         class S1 extends State {
             @Override
             public void exit() {
-                log("S1.exit");
+                tlog("S1.exit");
                 addLogRec(EXIT);
             }
             @Override
@@ -119,13 +118,13 @@
                 switch(message.what) {
                     // Sleep and assume the other messages will be queued up.
                     case TEST_CMD_1: {
-                        log("TEST_CMD_1");
+                        tlog("TEST_CMD_1");
                         sleep(500);
                         quit();
                         break;
                     }
                     default: {
-                        log("default what=" + message.what);
+                        tlog("default what=" + message.what);
                         break;
                     }
                 }
@@ -201,7 +200,7 @@
 
         @Override
         public void onQuitting() {
-            log("onQuitting");
+            tlog("onQuitting");
             addLogRec(ON_QUITTING);
             // Get a copy of the log records since we're quitting and they will disappear
             mLogRecs = mThisSm.copyLogRecs();
@@ -214,7 +213,7 @@
         class S1 extends State {
             @Override
             public void exit() {
-                log("S1.exit");
+                tlog("S1.exit");
                 addLogRec(EXIT);
             }
             @Override
@@ -222,13 +221,13 @@
                 switch(message.what) {
                     // Sleep and assume the other messages will be queued up.
                     case TEST_CMD_1: {
-                        log("TEST_CMD_1");
+                        tlog("TEST_CMD_1");
                         sleep(500);
                         quitNow();
                         break;
                     }
                     default: {
-                        log("default what=" + message.what);
+                        tlog("default what=" + message.what);
                         break;
                     }
                 }
@@ -309,12 +308,12 @@
                 // Test transitions in enter on the initial state work
                 addLogRec(ENTER);
                 transitionTo(mS2);
-                log("S1.enter");
+                tlog("S1.enter");
             }
             @Override
             public void exit() {
                 addLogRec(EXIT);
-                log("S1.exit");
+                tlog("S1.exit");
             }
         }
 
@@ -322,7 +321,7 @@
             @Override
             public void enter() {
                 addLogRec(ENTER);
-                log("S2.enter");
+                tlog("S2.enter");
             }
             @Override
             public void exit() {
@@ -332,14 +331,14 @@
                 assertEquals(TEST_CMD_1, getCurrentMessage().what);
                 addLogRec(EXIT);
 
-                log("S2.exit");
+                tlog("S2.exit");
             }
             @Override
             public boolean processMessage(Message message) {
                 // Start a transition to S3 but it will be
                 // changed to a transition to S4 in exit
                 transitionTo(mS3);
-                log("S2.processMessage");
+                tlog("S2.processMessage");
                 return HANDLED;
             }
         }
@@ -348,12 +347,12 @@
             @Override
             public void enter() {
                 addLogRec(ENTER);
-                log("S3.enter");
+                tlog("S3.enter");
             }
             @Override
             public void exit() {
                 addLogRec(EXIT);
-                log("S3.exit");
+                tlog("S3.exit");
             }
         }
 
@@ -363,12 +362,12 @@
                 addLogRec(ENTER);
                 // Test that we can do halting in an enter/exit
                 transitionToHaltingState();
-                log("S4.enter");
+                tlog("S4.enter");
             }
             @Override
             public void exit() {
                 addLogRec(EXIT);
-                log("S4.exit");
+                tlog("S4.exit");
             }
         }
 
@@ -572,7 +571,7 @@
 
             // Set the initial state
             setInitialState(mS1);
-            if (DBG) log("StateMachine1: ctor X");
+            if (DBG) tlog("StateMachine1: ctor X");
         }
 
         class S1 extends State {
@@ -673,7 +672,7 @@
 
             // Set the initial state
             setInitialState(mS1);
-            if (DBG) log("StateMachine2: ctor X");
+            if (DBG) tlog("StateMachine2: ctor X");
         }
 
         class S1 extends State {
@@ -782,7 +781,7 @@
 
             // Set the initial state will be the child
             setInitialState(mChildState);
-            if (DBG) log("StateMachine3: ctor X");
+            if (DBG) tlog("StateMachine3: ctor X");
         }
 
         class ParentState extends State {
@@ -869,7 +868,7 @@
 
             // Set the initial state will be child 1
             setInitialState(mChildState1);
-            if (DBG) log("StateMachine4: ctor X");
+            if (DBG) tlog("StateMachine4: ctor X");
         }
 
         class ParentState extends State {
@@ -969,7 +968,7 @@
 
             // Set the initial state will be the child
             setInitialState(mChildState1);
-            if (DBG) log("StateMachine5: ctor X");
+            if (DBG) tlog("StateMachine5: ctor X");
         }
 
         class ParentState1 extends State {
@@ -1296,7 +1295,7 @@
 
             // Set the initial state
             setInitialState(mS1);
-            if (DBG) log("StateMachine6: ctor X");
+            if (DBG) tlog("StateMachine6: ctor X");
         }
 
         class S1 extends State {
@@ -1383,7 +1382,7 @@
 
             // Set the initial state
             setInitialState(mS1);
-            if (DBG) log("StateMachine7: ctor X");
+            if (DBG) tlog("StateMachine7: ctor X");
         }
 
         class S1 extends State {
@@ -1654,7 +1653,7 @@
 
         Hsm1(String name) {
             super(name);
-            log("ctor E");
+            tlog("ctor E");
 
             // Add states, use indentation to show hierarchy
             addState(mP1);
@@ -1664,22 +1663,22 @@
 
             // Set the initial state
             setInitialState(mS1);
-            log("ctor X");
+            tlog("ctor X");
         }
 
         class P1 extends State {
             @Override
             public void enter() {
-                log("P1.enter");
+                tlog("P1.enter");
             }
             @Override
             public void exit() {
-                log("P1.exit");
+                tlog("P1.exit");
             }
             @Override
             public boolean processMessage(Message message) {
                 boolean retVal;
-                log("P1.processMessage what=" + message.what);
+                tlog("P1.processMessage what=" + message.what);
                 switch(message.what) {
                 case CMD_2:
                     // CMD_2 will arrive in mS2 before CMD_3
@@ -1700,15 +1699,15 @@
         class S1 extends State {
             @Override
             public void enter() {
-                log("S1.enter");
+                tlog("S1.enter");
             }
             @Override
             public void exit() {
-                log("S1.exit");
+                tlog("S1.exit");
             }
             @Override
             public boolean processMessage(Message message) {
-                log("S1.processMessage what=" + message.what);
+                tlog("S1.processMessage what=" + message.what);
                 if (message.what == CMD_1) {
                     // Transition to ourself to show that enter/exit is called
                     transitionTo(mS1);
@@ -1723,16 +1722,16 @@
         class S2 extends State {
             @Override
             public void enter() {
-                log("S2.enter");
+                tlog("S2.enter");
             }
             @Override
             public void exit() {
-                log("S2.exit");
+                tlog("S2.exit");
             }
             @Override
             public boolean processMessage(Message message) {
                 boolean retVal;
-                log("S2.processMessage what=" + message.what);
+                tlog("S2.processMessage what=" + message.what);
                 switch(message.what) {
                 case(CMD_2):
                     sendMessage(CMD_4);
@@ -1754,16 +1753,16 @@
         class P2 extends State {
             @Override
             public void enter() {
-                log("P2.enter");
+                tlog("P2.enter");
                 sendMessage(CMD_5);
             }
             @Override
             public void exit() {
-                log("P2.exit");
+                tlog("P2.exit");
             }
             @Override
             public boolean processMessage(Message message) {
-                log("P2.processMessage what=" + message.what);
+                tlog("P2.processMessage what=" + message.what);
                 switch(message.what) {
                 case(CMD_3):
                     break;
@@ -1779,7 +1778,7 @@
 
         @Override
         protected void onHalting() {
-            log("halting");
+            tlog("halting");
             synchronized (this) {
                 this.notifyAll();
             }
@@ -1852,11 +1851,11 @@
         if (DBG) tlog("testStateMachineSharedThread X");
     }
 
-    private void tlog(String s) {
+    private static void tlog(String s) {
         Log.d(TAG, s);
     }
 
-    private void tloge(String s) {
+    private static void tloge(String s) {
         Log.e(TAG, s);
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java b/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java
index d368de9..151e0ea 100644
--- a/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/TetherUtil.java
@@ -16,18 +16,11 @@
 package com.android.settingslib;
 
 import android.content.Context;
-import android.net.wifi.WifiManager;
 import android.os.SystemProperties;
 import android.telephony.CarrierConfigManager;
 
 public class TetherUtil {
 
-    public static boolean setWifiTethering(boolean enable, Context context) {
-        final WifiManager wifiManager =
-                (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
-        return wifiManager.setWifiApEnabled(null, enable);
-    }
-
     private static boolean isEntitlementCheckRequired(Context context) {
         final CarrierConfigManager configManager = (CarrierConfigManager) context
              .getSystemService(Context.CARRIER_CONFIG_SERVICE);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 380fcd4..0a3f0c0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -36,6 +36,7 @@
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.support.annotation.NonNull;
 import android.text.Spannable;
@@ -43,12 +44,13 @@
 import android.text.TextUtils;
 import android.text.style.TtsSpan;
 import android.util.Log;
-import android.util.LruCache;
 
 import com.android.settingslib.R;
 
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 
 public class AccessPoint implements Comparable<AccessPoint> {
@@ -81,7 +83,9 @@
      *  For now this data is used only with Verbose Logging so as to show the band and number
      *  of BSSIDs on which that network is seen.
      */
-    public LruCache<String, ScanResult> mScanResultCache = new LruCache<String, ScanResult>(32);
+    private final ConcurrentHashMap<String, ScanResult> mScanResultCache =
+            new ConcurrentHashMap<String, ScanResult>(32);
+    private static final long MAX_SCAN_RESULT_AGE_MS = 15000;
 
     private static final String KEY_NETWORKINFO = "key_networkinfo";
     private static final String KEY_WIFIINFO = "key_wifiinfo";
@@ -149,7 +153,7 @@
         if (savedState.containsKey(KEY_SCANRESULTCACHE)) {
             ArrayList<ScanResult> scanResultArrayList =
                     savedState.getParcelableArrayList(KEY_SCANRESULTCACHE);
-            mScanResultCache.evictAll();
+            mScanResultCache.clear();
             for (ScanResult result : scanResultArrayList) {
                 mScanResultCache.put(result.BSSID, result);
             }
@@ -233,6 +237,17 @@
         return builder.append(')').toString();
     }
 
+    private void evictOldScanResults() {
+        long nowMs = SystemClock.elapsedRealtime();
+        for (Iterator<ScanResult> iter = mScanResultCache.values().iterator(); iter.hasNext(); ) {
+            ScanResult result = iter.next();
+            // result timestamp is in microseconds
+            if (nowMs - result.timestamp / 1000 > MAX_SCAN_RESULT_AGE_MS) {
+                iter.remove();
+            }
+        }
+    }
+
     public boolean matches(ScanResult result) {
         return ssid.equals(result.SSID) && security == getSecurity(result);
     }
@@ -268,8 +283,9 @@
     }
 
     public int getRssi() {
+        evictOldScanResults();
         int rssi = Integer.MIN_VALUE;
-        for (ScanResult result : mScanResultCache.snapshot().values()) {
+        for (ScanResult result : mScanResultCache.values()) {
             if (result.level > rssi) {
                 rssi = result.level;
             }
@@ -279,8 +295,9 @@
     }
 
     public long getSeen() {
+        evictOldScanResults();
         long seen = 0;
-        for (ScanResult result : mScanResultCache.snapshot().values()) {
+        for (ScanResult result : mScanResultCache.values()) {
             if (result.timestamp > seen) {
                 seen = result.timestamp;
             }
@@ -505,9 +522,9 @@
         int numBlackListed = 0;
         int n24 = 0; // Number scan results we included in the string
         int n5 = 0; // Number scan results we included in the string
-        Map<String, ScanResult> list = mScanResultCache.snapshot();
+        evictOldScanResults();
         // TODO: sort list by RSSI or age
-        for (ScanResult result : list.values()) {
+        for (ScanResult result : mScanResultCache.values()) {
 
             if (result.frequency >= LOWER_FREQ_5GHZ
                     && result.frequency <= HIGHER_FREQ_5GHZ) {
@@ -684,8 +701,9 @@
         savedState.putInt(KEY_PSKTYPE, pskType);
         if (mConfig != null) savedState.putParcelable(KEY_CONFIG, mConfig);
         savedState.putParcelable(KEY_WIFIINFO, mInfo);
+        evictOldScanResults();
         savedState.putParcelableArrayList(KEY_SCANRESULTCACHE,
-                new ArrayList<ScanResult>(mScanResultCache.snapshot().values()));
+                new ArrayList<ScanResult>(mScanResultCache.values()));
         if (mNetworkInfo != null) {
             savedState.putParcelable(KEY_NETWORKINFO, mNetworkInfo);
         }
@@ -697,9 +715,6 @@
 
     boolean update(ScanResult result) {
         if (matches(result)) {
-            /* Update the LRU timestamp, if BSSID exists */
-            mScanResultCache.get(result.BSSID);
-
             /* Add or update the scan result for the BSSID */
             mScanResultCache.put(result.BSSID, result);
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index f8fb1e5..920df2e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -28,12 +28,9 @@
 import android.net.NetworkPolicyManager;
 import android.net.Uri;
 import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.KeyMgmt;
 import android.net.wifi.WifiManager;
-import android.os.FileUtils;
 import android.os.Handler;
 import android.os.ParcelFileDescriptor;
-import android.os.Process;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.BackupUtils;
@@ -41,34 +38,19 @@
 
 import com.android.internal.widget.LockPatternUtils;
 
-import libcore.io.IoUtils;
-
 import java.io.BufferedOutputStream;
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
-import java.io.CharArrayReader;
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.EOFException;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
-import java.io.FileReader;
-import java.io.FileWriter;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.Writer;
-import java.util.ArrayList;
-import java.util.BitSet;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.zip.CRC32;
 
 /**
@@ -86,10 +68,11 @@
     private static final String KEY_LOCK_SETTINGS = "lock_settings";
     private static final String KEY_SOFTAP_CONFIG = "softap_config";
     private static final String KEY_NETWORK_POLICIES = "network_policies";
+    private static final String KEY_WIFI_NEW_CONFIG = "wifi_new_config";
 
     // Versioning of the state file.  Increment this version
     // number any time the set of state items is altered.
-    private static final int STATE_VERSION = 6;
+    private static final int STATE_VERSION = 7;
 
     // Versioning of the Network Policies backup payload.
     private static final int NETWORK_POLICIES_BACKUP_VERSION = 1;
@@ -106,8 +89,9 @@
     private static final int STATE_LOCK_SETTINGS    = 6;
     private static final int STATE_SOFTAP_CONFIG    = 7;
     private static final int STATE_NETWORK_POLICIES = 8;
+    private static final int STATE_WIFI_NEW_CONFIG  = 9;
 
-    private static final int STATE_SIZE             = 9; // The current number of state items
+    private static final int STATE_SIZE             = 10; // The current number of state items
 
     // Number of entries in the checksum array at various version numbers
     private static final int STATE_SIZES[] = {
@@ -117,16 +101,18 @@
             6,              // version 3 added STATE_GLOBAL
             7,              // version 4 added STATE_LOCK_SETTINGS
             8,              // version 5 added STATE_SOFTAP_CONFIG
-            STATE_SIZE      // version 6 added STATE_NETWORK_POLICIES
+            9,              // version 6 added STATE_NETWORK_POLICIES
+            STATE_SIZE      // version 7 added STATE_WIFI_NEW_CONFIG
     };
 
     // Versioning of the 'full backup' format
     // Increment this version any time a new item is added
-    private static final int FULL_BACKUP_VERSION = 5;
+    private static final int FULL_BACKUP_VERSION = 6;
     private static final int FULL_BACKUP_ADDED_GLOBAL = 2;  // added the "global" entry
     private static final int FULL_BACKUP_ADDED_LOCK_SETTINGS = 3; // added the "lock_settings" entry
     private static final int FULL_BACKUP_ADDED_SOFTAP_CONF = 4; //added the "softap_config" entry
     private static final int FULL_BACKUP_ADDED_NETWORK_POLICIES = 5; //added "network_policies"
+    private static final int FULL_BACKUP_ADDED_WIFI_NEW = 6; // added "wifi_new_config" entry
 
     private static final int INTEGER_BYTE_COUNT = Integer.SIZE / Byte.SIZE;
 
@@ -139,10 +125,6 @@
             Settings.NameValueTable.VALUE
     };
 
-    private static final String FILE_WIFI_SUPPLICANT = "/data/misc/wifi/wpa_supplicant.conf";
-    private static final String FILE_WIFI_SUPPLICANT_TEMPLATE =
-            "/system/etc/wifi/wpa_supplicant.conf";
-
     // the key to store the WIFI data under, should be sorted as last, so restore happens last.
     // use very late unicode character to quasi-guarantee last sort position.
     private static final String KEY_WIFI_SUPPLICANT = "\uffedWIFI";
@@ -156,259 +138,17 @@
     // stored in the full-backup tarfile as well, so should not be changed.
     private static final String STAGE_FILE = "flattened-data";
 
-    // Delay in milliseconds between the restore operation and when we will bounce
-    // wifi in order to rewrite the supplicant config etc.
-    private static final long WIFI_BOUNCE_DELAY_MILLIS = 60 * 1000; // one minute
-
     private SettingsHelper mSettingsHelper;
-    private WifiManager mWfm;
-    private String mWifiConfigFile;
 
-    // Chain of asynchronous operations used when rewriting the wifi supplicant config file
-    WifiDisableRunnable mWifiDisable = null;
-    WifiRestoreRunnable mWifiRestore = null;
-    int mRetainedWifiState; // used only during config file rewrite
-
-    // Class for capturing a network definition from the wifi supplicant config file
-    static class Network {
-        String ssid = "";  // equals() and hashCode() need these to be non-null
-        String key_mgmt = "";
-        boolean certUsed = false;
-        boolean hasWepKey = false;
-        boolean isEap = false;
-        final ArrayList<String> rawLines = new ArrayList<String>();
-
-        public static Network readFromStream(BufferedReader in) {
-            final Network n = new Network();
-            String line;
-            try {
-                while (in.ready()) {
-                    line = in.readLine();
-                    if (line == null || line.startsWith("}")) {
-                        break;
-                    }
-                    n.rememberLine(line);
-                }
-            } catch (IOException e) {
-                return null;
-            }
-            return n;
-        }
-
-        void rememberLine(String line) {
-            // can't rely on particular whitespace patterns so strip leading/trailing
-            line = line.trim();
-            if (line.isEmpty()) return; // only whitespace; drop the line
-            rawLines.add(line);
-
-            // remember the ssid and key_mgmt lines for duplicate culling
-            if (line.startsWith("ssid=")) {
-                ssid = line;
-            } else if (line.startsWith("key_mgmt=")) {
-                key_mgmt = line;
-                if (line.contains("EAP")) {
-                    isEap = true;
-                }
-            } else if (line.startsWith("client_cert=")) {
-                certUsed = true;
-            } else if (line.startsWith("ca_cert=")) {
-                certUsed = true;
-            } else if (line.startsWith("ca_path=")) {
-                certUsed = true;
-            } else if (line.startsWith("wep_")) {
-                hasWepKey = true;
-            } else if (line.startsWith("eap=")) {
-                isEap = true;
-            }
-        }
-
-        public void write(Writer w) throws IOException {
-            w.write("\nnetwork={\n");
-            for (String line : rawLines) {
-                w.write("\t" + line + "\n");
-            }
-            w.write("}\n");
-        }
-
-        public void dump() {
-            Log.v(TAG, "network={");
-            for (String line : rawLines) {
-                Log.v(TAG, "   " + line);
-            }
-            Log.v(TAG, "}");
-        }
-
-        // Calculate the equivalent of WifiConfiguration's configKey()
-        public String configKey() {
-            if (ssid == null) {
-                // No SSID => malformed network definition
-                return null;
-            }
-
-            final String bareSsid = ssid.substring(ssid.indexOf('=') + 1);
-
-            final BitSet types = new BitSet();
-            if (key_mgmt == null) {
-                // no key_mgmt specified; this is defined as equivalent to "WPA-PSK WPA-EAP"
-                types.set(KeyMgmt.WPA_PSK);
-                types.set(KeyMgmt.WPA_EAP);
-            } else {
-                // Need to parse the key_mgmt line
-                final String bareKeyMgmt = key_mgmt.substring(key_mgmt.indexOf('=') + 1);
-                String[] typeStrings = bareKeyMgmt.split("\\s+");
-
-                // Parse out all the key management regimes permitted for this network.  The literal
-                // strings here are the standard values permitted in wpa_supplicant.conf.
-                for (int i = 0; i < typeStrings.length; i++) {
-                    final String ktype = typeStrings[i];
-                    if (ktype.equals("WPA-PSK")) {
-                        Log.v(TAG, "  + setting WPA_PSK bit");
-                        types.set(KeyMgmt.WPA_PSK);
-                    } else if (ktype.equals("WPA-EAP")) {
-                        Log.v(TAG, "  + setting WPA_EAP bit");
-                        types.set(KeyMgmt.WPA_EAP);
-                    } else if (ktype.equals("IEEE8021X")) {
-                        Log.v(TAG, "  + setting IEEE8021X bit");
-                        types.set(KeyMgmt.IEEE8021X);
-                    }
-                }
-            }
-
-            // Now build the canonical config key paralleling the WifiConfiguration semantics
-            final String key;
-            if (types.get(KeyMgmt.WPA_PSK)) {
-                key = bareSsid + KeyMgmt.strings[KeyMgmt.WPA_PSK];
-            } else if (types.get(KeyMgmt.WPA_EAP) || types.get(KeyMgmt.IEEE8021X)) {
-                key = bareSsid + KeyMgmt.strings[KeyMgmt.WPA_EAP];
-            } else if (hasWepKey) {
-                key = bareSsid + "WEP";  // hardcoded this way in WifiConfiguration
-            } else {
-                key = bareSsid + KeyMgmt.strings[KeyMgmt.NONE];
-            }
-            return key;
-        }
-
-        // Same approach as Pair.equals() and Pair.hashCode()
-        @Override
-        public boolean equals(Object o) {
-            if (o == this) return true;
-            if (!(o instanceof Network)) return false;
-            final Network other;
-            try {
-                other = (Network) o;
-            } catch (ClassCastException e) {
-                return false;
-            }
-            return ssid.equals(other.ssid) && key_mgmt.equals(other.key_mgmt);
-        }
-
-        @Override
-        public int hashCode() {
-            int result = 17;
-            result = 31 * result + ssid.hashCode();
-            result = 31 * result + key_mgmt.hashCode();
-            return result;
-        }
-    }
-
-    boolean networkInWhitelist(Network net, List<WifiConfiguration> whitelist) {
-        final String netConfigKey = net.configKey();
-        final int N = whitelist.size();
-        for (int i = 0; i < N; i++) {
-            if (Objects.equals(netConfigKey, whitelist.get(i).configKey(true))) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    // Ingest multiple wifi config file fragments, looking for network={} blocks
-    // and eliminating duplicates
-    class WifiNetworkSettings {
-        // One for fast lookup, one for maintaining ordering
-        final HashSet<Network> mKnownNetworks = new HashSet<Network>();
-        final ArrayList<Network> mNetworks = new ArrayList<Network>(8);
-
-        public void readNetworks(BufferedReader in, List<WifiConfiguration> whitelist,
-                boolean acceptEapNetworks) {
-            try {
-                String line;
-                while (in.ready()) {
-                    line = in.readLine();
-                    if (line != null) {
-                        // Parse out 'network=' decls so we can ignore duplicates
-                        if (line.startsWith("network")) {
-                            Network net = Network.readFromStream(in);
-                            if (whitelist != null) {
-                                if (!networkInWhitelist(net, whitelist)) {
-                                    if (DEBUG_BACKUP) {
-                                        Log.v(TAG, "Network not in whitelist, skipping: "
-                                                + net.ssid + " / " + net.key_mgmt);
-                                    }
-                                    continue;
-                                }
-                            }
-                            // Don't propagate EAP network definitions
-                            if (net.isEap && !acceptEapNetworks) {
-                                if (DEBUG_BACKUP) {
-                                    Log.v(TAG, "Skipping EAP network " + net.ssid + " / " + net.key_mgmt);
-                                }
-                                continue;
-                            }
-                            if (!mKnownNetworks.contains(net)) {
-                                if (DEBUG_BACKUP) {
-                                    Log.v(TAG, "Adding " + net.ssid + " / " + net.key_mgmt);
-                                }
-                                mKnownNetworks.add(net);
-                                mNetworks.add(net);
-                            } else {
-                                if (DEBUG_BACKUP) {
-                                    Log.v(TAG, "Dupe; skipped " + net.ssid + " / " + net.key_mgmt);
-                                }
-                            }
-                        }
-                    }
-                }
-            } catch (IOException e) {
-                // whatever happened, we're done now
-            }
-        }
-
-        public void write(Writer w) throws IOException {
-            for (Network net : mNetworks) {
-                if (net.certUsed) {
-                    // Networks that use certificates for authentication can't be restored
-                    // because the certificates they need don't get restored (because they
-                    // are stored in keystore, and can't be restored)
-                    continue;
-                }
-
-                if (net.isEap) {
-                    // Similarly, omit EAP network definitions to avoid propagating
-                    // controlled enterprise network definitions.
-                    continue;
-                }
-
-                net.write(w);
-            }
-        }
-
-        public void dump() {
-            for (Network net : mNetworks) {
-                net.dump();
-            }
-        }
-    }
+    private WifiManager mWifiManager;
 
     @Override
     public void onCreate() {
         if (DEBUG_BACKUP) Log.d(TAG, "onCreate() invoked");
 
         mSettingsHelper = new SettingsHelper(this);
+        mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
         super.onCreate();
-
-        WifiManager mWfm = (WifiManager) getSystemService(Context.WIFI_SERVICE);
-        if (mWfm != null) mWifiConfigFile = mWfm.getConfigFile();
     }
 
     @Override
@@ -420,10 +160,9 @@
         byte[] globalSettingsData = getGlobalSettings();
         byte[] lockSettingsData   = getLockSettings();
         byte[] locale = mSettingsHelper.getLocaleData();
-        byte[] wifiSupplicantData = getWifiSupplicant(FILE_WIFI_SUPPLICANT);
-        byte[] wifiConfigData = getFileData(mWifiConfigFile);
         byte[] softApConfigData = getSoftAPConfiguration();
         byte[] netPoliciesData = getNetworkPolicies();
+        byte[] wifiFullConfigData = getNewWifiConfigData();
 
         long[] stateChecksums = readOldChecksums(oldState);
 
@@ -435,12 +174,8 @@
                 writeIfChanged(stateChecksums[STATE_GLOBAL], KEY_GLOBAL, globalSettingsData, data);
         stateChecksums[STATE_LOCALE] =
                 writeIfChanged(stateChecksums[STATE_LOCALE], KEY_LOCALE, locale, data);
-        stateChecksums[STATE_WIFI_SUPPLICANT] =
-                writeIfChanged(stateChecksums[STATE_WIFI_SUPPLICANT], KEY_WIFI_SUPPLICANT,
-                        wifiSupplicantData, data);
-        stateChecksums[STATE_WIFI_CONFIG] =
-                writeIfChanged(stateChecksums[STATE_WIFI_CONFIG], KEY_WIFI_CONFIG, wifiConfigData,
-                        data);
+        stateChecksums[STATE_WIFI_SUPPLICANT] = 0;
+        stateChecksums[STATE_WIFI_CONFIG] = 0;
         stateChecksums[STATE_LOCK_SETTINGS] =
                 writeIfChanged(stateChecksums[STATE_LOCK_SETTINGS], KEY_LOCK_SETTINGS,
                         lockSettingsData, data);
@@ -450,112 +185,13 @@
         stateChecksums[STATE_NETWORK_POLICIES] =
                 writeIfChanged(stateChecksums[STATE_NETWORK_POLICIES], KEY_NETWORK_POLICIES,
                         netPoliciesData, data);
+        stateChecksums[STATE_WIFI_NEW_CONFIG] =
+                writeIfChanged(stateChecksums[STATE_WIFI_NEW_CONFIG], KEY_WIFI_NEW_CONFIG,
+                        wifiFullConfigData, data);
 
         writeNewChecksums(stateChecksums, newState);
     }
 
-    class WifiDisableRunnable implements Runnable {
-        final WifiRestoreRunnable mNextPhase;
-
-        public WifiDisableRunnable(WifiRestoreRunnable next) {
-            mNextPhase = next;
-        }
-
-        @Override
-        public void run() {
-            if (DEBUG_BACKUP) {
-                Log.v(TAG, "Disabling wifi during restore");
-            }
-            final ContentResolver cr = getContentResolver();
-            final int scanAlways = Settings.Global.getInt(cr,
-                    Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0);
-            final int retainedWifiState = enableWifi(false);
-            if (scanAlways != 0) {
-                Settings.Global.putInt(cr,
-                        Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0);
-            }
-
-            // Tell the final stage how to clean up after itself
-            mNextPhase.setPriorState(retainedWifiState, scanAlways);
-
-            // And run it after a modest pause to give broadcasts and content
-            // observers time an opportunity to run on this looper thread, so
-            // that the wifi stack actually goes all the way down.
-            new Handler(getMainLooper()).postDelayed(mNextPhase, 2500);
-        }
-    }
-
-    class WifiRestoreRunnable implements Runnable {
-        private byte[] restoredSupplicantData;
-        private byte[] restoredWifiConfigFile;
-        private int retainedWifiState;  // provided by disable stage
-        private int scanAlways; // provided by disable stage
-
-        void setPriorState(int retainedState, int always) {
-            retainedWifiState = retainedState;
-            scanAlways = always;
-        }
-
-        void incorporateWifiSupplicant(BackupDataInput data) {
-            restoredSupplicantData = new byte[data.getDataSize()];
-            if (restoredSupplicantData.length <= 0) return;
-            try {
-                data.readEntityData(restoredSupplicantData, 0, data.getDataSize());
-            } catch (IOException e) {
-                Log.w(TAG, "Unable to read supplicant data");
-                restoredSupplicantData = null;
-            }
-        }
-
-        void incorporateWifiConfigFile(BackupDataInput data) {
-            restoredWifiConfigFile = new byte[data.getDataSize()];
-            if (restoredWifiConfigFile.length <= 0) return;
-            try {
-                data.readEntityData(restoredWifiConfigFile, 0, data.getDataSize());
-            } catch (IOException e) {
-                Log.w(TAG, "Unable to read config file");
-                restoredWifiConfigFile = null;
-            }
-        }
-
-        @Override
-        public void run() {
-            if (restoredSupplicantData != null || restoredWifiConfigFile != null) {
-                if (DEBUG_BACKUP) {
-                    Log.v(TAG, "Applying restored wifi data");
-                }
-                if (restoredSupplicantData != null) {
-                    restoreWifiSupplicant(FILE_WIFI_SUPPLICANT,
-                            restoredSupplicantData, restoredSupplicantData.length);
-                    FileUtils.setPermissions(FILE_WIFI_SUPPLICANT,
-                            FileUtils.S_IRUSR | FileUtils.S_IWUSR
-                            | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
-                            Process.myUid(), Process.WIFI_UID);
-                }
-                if (restoredWifiConfigFile != null) {
-                    restoreFileData(mWifiConfigFile,
-                            restoredWifiConfigFile, restoredWifiConfigFile.length);
-                }
-                // restore the previous WIFI state.
-                if (scanAlways != 0) {
-                    Settings.Global.putInt(getContentResolver(),
-                            Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, scanAlways);
-                }
-                enableWifi(retainedWifiState == WifiManager.WIFI_STATE_ENABLED
-                        || retainedWifiState == WifiManager.WIFI_STATE_ENABLING);
-            }
-        }
-    }
-
-    // Instantiate the wifi-config restore runnable, scheduling it for execution
-    // a minute hence
-    void initWifiRestoreIfNecessary() {
-        if (mWifiRestore == null) {
-            mWifiRestore = new WifiRestoreRunnable();
-            mWifiDisable = new WifiDisableRunnable(mWifiRestore);
-        }
-    }
-
     @Override
     public void onRestore(BackupDataInput data, int appVersionCode,
             ParcelFileDescriptor newState) throws IOException {
@@ -563,6 +199,8 @@
         HashSet<String> movedToGlobal = new HashSet<String>();
         Settings.System.getMovedToGlobalSettings(movedToGlobal);
         Settings.Secure.getMovedToGlobalSettings(movedToGlobal);
+        byte[] restoredWifiSupplicantData = null;
+        byte[] restoredWifiIpConfigData = null;
 
         while (data.readNextHeader()) {
             final String key = data.getKey();
@@ -582,8 +220,8 @@
                     break;
 
                 case KEY_WIFI_SUPPLICANT :
-                    initWifiRestoreIfNecessary();
-                    mWifiRestore.incorporateWifiSupplicant(data);
+                    restoredWifiSupplicantData = new byte[size];
+                    data.readEntityData(restoredWifiSupplicantData, 0, size);
                     break;
 
                 case KEY_LOCALE :
@@ -593,8 +231,8 @@
                     break;
 
                 case KEY_WIFI_CONFIG :
-                    initWifiRestoreIfNecessary();
-                    mWifiRestore.incorporateWifiConfigFile(data);
+                    restoredWifiIpConfigData = new byte[size];
+                    data.readEntityData(restoredWifiIpConfigData, 0, size);
                     break;
 
                 case KEY_LOCK_SETTINGS :
@@ -613,23 +251,22 @@
                     restoreNetworkPolicies(netPoliciesData);
                     break;
 
+                case KEY_WIFI_NEW_CONFIG:
+                    byte[] restoredWifiNewConfigData = new byte[size];
+                    data.readEntityData(restoredWifiNewConfigData, 0, size);
+                    restoreNewWifiConfigData(restoredWifiNewConfigData);
+                    break;
+
                 default :
                     data.skipEntityData();
 
             }
         }
 
-        // If we have wifi data to restore, post a runnable to perform the
-        // bounce-and-update operation a little ways in the future.  The
-        // 'disable' runnable brings down the stack and remembers its state,
-        // and in turn schedules the 'restore' runnable to do the rewrite
-        // and cleanup operations.
-        if (mWifiRestore != null) {
-            long wifiBounceDelayMillis = Settings.Global.getLong(
-                    getContentResolver(),
-                    Settings.Global.WIFI_BOUNCE_DELAY_OVERRIDE_MS,
-                    WIFI_BOUNCE_DELAY_MILLIS);
-            new Handler(getMainLooper()).postDelayed(mWifiDisable, wifiBounceDelayMillis);
+        // Do this at the end so that we also pull in the ipconfig data.
+        if (restoredWifiSupplicantData != null) {
+            restoreSupplicantWifiConfigData(
+                    restoredWifiSupplicantData, restoredWifiIpConfigData);
         }
     }
 
@@ -640,10 +277,9 @@
         byte[] globalSettingsData = getGlobalSettings();
         byte[] lockSettingsData   = getLockSettings();
         byte[] locale = mSettingsHelper.getLocaleData();
-        byte[] wifiSupplicantData = getWifiSupplicant(FILE_WIFI_SUPPLICANT);
-        byte[] wifiConfigData = getFileData(mWifiConfigFile);
         byte[] softApConfigData = getSoftAPConfiguration();
         byte[] netPoliciesData = getNetworkPolicies();
+        byte[] wifiFullConfigData = getNewWifiConfigData();
 
         // Write the data to the staging file, then emit that as our tarfile
         // representation of the backed-up settings.
@@ -673,14 +309,6 @@
             if (DEBUG_BACKUP) Log.d(TAG, locale.length + " bytes of locale data");
             out.writeInt(locale.length);
             out.write(locale);
-            if (DEBUG_BACKUP) {
-                Log.d(TAG, wifiSupplicantData.length + " bytes of wifi supplicant data");
-            }
-            out.writeInt(wifiSupplicantData.length);
-            out.write(wifiSupplicantData);
-            if (DEBUG_BACKUP) Log.d(TAG, wifiConfigData.length + " bytes of wifi config data");
-            out.writeInt(wifiConfigData.length);
-            out.write(wifiConfigData);
             if (DEBUG_BACKUP) Log.d(TAG, lockSettingsData.length + " bytes of lock settings data");
             out.writeInt(lockSettingsData.length);
             out.write(lockSettingsData);
@@ -690,6 +318,11 @@
             if (DEBUG_BACKUP) Log.d(TAG, netPoliciesData.length + " bytes of net policies data");
             out.writeInt(netPoliciesData.length);
             out.write(netPoliciesData);
+            if (DEBUG_BACKUP) {
+                Log.d(TAG, wifiFullConfigData.length + " bytes of wifi config data");
+            }
+            out.writeInt(wifiFullConfigData.length);
+            out.write(wifiFullConfigData);
 
             out.flush();    // also flushes downstream
 
@@ -750,27 +383,21 @@
             in.readFully(buffer, 0, nBytes);
             mSettingsHelper.setLocaleData(buffer, nBytes);
 
-            // wifi supplicant
-            nBytes = in.readInt();
-            if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of wifi supplicant data");
-            if (nBytes > buffer.length) buffer = new byte[nBytes];
-            in.readFully(buffer, 0, nBytes);
-            int retainedWifiState = enableWifi(false);
-            restoreWifiSupplicant(FILE_WIFI_SUPPLICANT, buffer, nBytes);
-            FileUtils.setPermissions(FILE_WIFI_SUPPLICANT,
-                    FileUtils.S_IRUSR | FileUtils.S_IWUSR
-                    | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
-                    Process.myUid(), Process.WIFI_UID);
-            // retain the previous WIFI state.
-            enableWifi(retainedWifiState == WifiManager.WIFI_STATE_ENABLED
-                    || retainedWifiState == WifiManager.WIFI_STATE_ENABLING);
+            // Restore older backups performing the necessary migrations.
+            if (version < FULL_BACKUP_ADDED_WIFI_NEW) {
+                // wifi supplicant
+                int supplicant_size = in.readInt();
+                if (DEBUG_BACKUP) Log.d(TAG, supplicant_size + " bytes of wifi supplicant data");
+                byte[] supplicant_buffer = new byte[supplicant_size];
+                in.readFully(supplicant_buffer, 0, supplicant_size);
 
-            // wifi config
-            nBytes = in.readInt();
-            if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of wifi config data");
-            if (nBytes > buffer.length) buffer = new byte[nBytes];
-            in.readFully(buffer, 0, nBytes);
-            restoreFileData(mWifiConfigFile, buffer, nBytes);
+                // ip config
+                int ipconfig_size = in.readInt();
+                if (DEBUG_BACKUP) Log.d(TAG, ipconfig_size + " bytes of ip config data");
+                byte[] ipconfig_buffer = new byte[ipconfig_size];
+                in.readFully(ipconfig_buffer, 0, nBytes);
+                restoreSupplicantWifiConfigData(supplicant_buffer, ipconfig_buffer);
+            }
 
             if (version >= FULL_BACKUP_ADDED_LOCK_SETTINGS) {
                 nBytes = in.readInt();
@@ -801,6 +428,15 @@
                     restoreNetworkPolicies(buffer);
                 }
             }
+            // Restore full wifi config data
+            if (version >= FULL_BACKUP_ADDED_WIFI_NEW) {
+                nBytes = in.readInt();
+                if (DEBUG_BACKUP) Log.d(TAG, nBytes + " bytes of full wifi config data");
+                if (nBytes > buffer.length) buffer = new byte[nBytes];
+                in.readFully(buffer, 0, nBytes);
+                restoreNewWifiConfigData(buffer);
+            }
+
             if (DEBUG_BACKUP) Log.d(TAG, "Full restore complete.");
         } else {
             data.close();
@@ -1114,146 +750,16 @@
         return result;
     }
 
-    private byte[] getFileData(String filename) {
-        InputStream is = null;
-        try {
-            File file = new File(filename);
-            is = new FileInputStream(file);
-
-            //Will truncate read on a very long file,
-            //should not happen for a config file
-            byte[] bytes = new byte[(int) file.length()];
-
-            int offset = 0;
-            int numRead = 0;
-            while (offset < bytes.length
-                    && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0) {
-                offset += numRead;
-            }
-
-            //read failure
-            if (offset < bytes.length) {
-                Log.w(TAG, "Couldn't backup " + filename);
-                return EMPTY_DATA;
-            }
-            return bytes;
-        } catch (IOException ioe) {
-            Log.w(TAG, "Couldn't backup " + filename);
-            return EMPTY_DATA;
-        } finally {
-            if (is != null) {
-                try {
-                    is.close();
-                } catch (IOException e) {
-                }
-            }
+    private void restoreSupplicantWifiConfigData(byte[] supplicant_bytes, byte[] ipconfig_bytes) {
+        if (DEBUG_BACKUP) {
+            Log.v(TAG, "Applying restored supplicant wifi data");
         }
-    }
-
-    private void restoreFileData(String filename, byte[] bytes, int size) {
-        try {
-            File file = new File(filename);
-            if (file.exists()) file.delete();
-
-            OutputStream os = new BufferedOutputStream(new FileOutputStream(filename, true));
-            os.write(bytes, 0, size);
-            os.close();
-        } catch (IOException ioe) {
-            Log.w(TAG, "Couldn't restore " + filename);
-        }
-    }
-
-
-    private byte[] getWifiSupplicant(String filename) {
-        BufferedReader br = null;
-        try {
-            File file = new File(filename);
-            if (!file.exists()) {
-                return EMPTY_DATA;
-            }
-
-            WifiManager wifi = (WifiManager) getSystemService(WIFI_SERVICE);
-            List<WifiConfiguration> configs = wifi.getConfiguredNetworks();
-
-            WifiNetworkSettings fromFile = new WifiNetworkSettings();
-            br = new BufferedReader(new FileReader(file));
-            fromFile.readNetworks(br, configs, false);
-
-            // Write the parsed networks into a packed byte array
-            if (fromFile.mKnownNetworks.size() > 0) {
-                ByteArrayOutputStream bos = new ByteArrayOutputStream();
-                OutputStreamWriter out = new OutputStreamWriter(bos);
-                fromFile.write(out);
-                out.flush();
-                return bos.toByteArray();
-            } else {
-                return EMPTY_DATA;
-            }
-        } catch (IOException ioe) {
-            Log.w(TAG, "Couldn't backup " + filename);
-            return EMPTY_DATA;
-        } finally {
-            IoUtils.closeQuietly(br);
-        }
-    }
-
-    private void restoreWifiSupplicant(String filename, byte[] bytes, int size) {
-        try {
-            WifiNetworkSettings supplicantImage = new WifiNetworkSettings();
-
-            File supplicantFile = new File(FILE_WIFI_SUPPLICANT);
-            if (supplicantFile.exists()) {
-                // Retain the existing APs; we'll append the restored ones to them
-                BufferedReader in = new BufferedReader(new FileReader(FILE_WIFI_SUPPLICANT));
-                supplicantImage.readNetworks(in, null, true);
-                in.close();
-
-                supplicantFile.delete();
-            }
-
-            // Incorporate the restore AP information
-            if (size > 0) {
-                char[] restoredAsBytes = new char[size];
-                for (int i = 0; i < size; i++) restoredAsBytes[i] = (char) bytes[i];
-                BufferedReader in = new BufferedReader(new CharArrayReader(restoredAsBytes));
-                supplicantImage.readNetworks(in, null, false);
-
-                if (DEBUG_BACKUP) {
-                    Log.v(TAG, "Final AP list:");
-                    supplicantImage.dump();
-                }
-            }
-
-            // Install the correct default template
-            BufferedWriter bw = new BufferedWriter(new FileWriter(FILE_WIFI_SUPPLICANT));
-            copyWifiSupplicantTemplate(bw);
-
-            // Write the restored supplicant config and we're done
-            supplicantImage.write(bw);
-            bw.close();
-        } catch (IOException ioe) {
-            Log.w(TAG, "Couldn't restore " + filename);
-        }
-    }
-
-    private void copyWifiSupplicantTemplate(BufferedWriter bw) {
-        try {
-            BufferedReader br = new BufferedReader(new FileReader(FILE_WIFI_SUPPLICANT_TEMPLATE));
-            char[] temp = new char[1024];
-            int size;
-            while ((size = br.read(temp)) > 0) {
-                bw.write(temp, 0, size);
-            }
-            br.close();
-        } catch (IOException ioe) {
-            Log.w(TAG, "Couldn't copy wpa_supplicant file");
-        }
+        mWifiManager.restoreSupplicantBackupData(supplicant_bytes, ipconfig_bytes);
     }
 
     private byte[] getSoftAPConfiguration() {
-        WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
         try {
-            return wifiManager.getWifiApConfiguration().getBytesForBackup();
+            return mWifiManager.getWifiApConfiguration().getBytesForBackup();
         } catch (IOException ioe) {
             Log.e(TAG, "Failed to marshal SoftAPConfiguration" + ioe.getMessage());
             return new byte[0];
@@ -1261,12 +767,11 @@
     }
 
     private void restoreSoftApConfiguration(byte[] data) {
-        WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
         try {
             WifiConfiguration config = WifiConfiguration
                     .getWifiConfigFromBackup(new DataInputStream(new ByteArrayInputStream(data)));
             if (DEBUG) Log.d(TAG, "Successfully unMarshaled WifiConfiguration ");
-            wifiManager.setWifiApConfiguration(config);
+            mWifiManager.setWifiApConfiguration(config);
         } catch (IOException | BackupUtils.BadVersionException e) {
             Log.e(TAG, "Failed to unMarshal SoftAPConfiguration " + e.getMessage());
         }
@@ -1300,6 +805,17 @@
         return baos.toByteArray();
     }
 
+    private byte[] getNewWifiConfigData() {
+        return mWifiManager.retrieveBackupData();
+    }
+
+    private void restoreNewWifiConfigData(byte[] bytes) {
+        if (DEBUG_BACKUP) {
+            Log.v(TAG, "Applying restored wifi data");
+        }
+        mWifiManager.restoreBackupData(bytes);
+    }
+
     private void restoreNetworkPolicies(byte[] data) {
         NetworkPolicyManager networkPolicyManager =
                 (NetworkPolicyManager) getSystemService(NETWORK_POLICY_SERVICE);
@@ -1358,18 +874,4 @@
                 | ((in[pos + 3] & 0xFF) <<  0);
         return result;
     }
-
-    private int enableWifi(boolean enable) {
-        if (mWfm == null) {
-            mWfm = (WifiManager) getSystemService(Context.WIFI_SERVICE);
-        }
-        if (mWfm != null) {
-            int state = mWfm.getWifiState();
-            mWfm.setWifiEnabled(enable);
-            return state;
-        } else {
-            Log.e(TAG, "Failed to fetch WifiManager instance");
-        }
-        return WifiManager.WIFI_STATE_UNKNOWN;
-    }
 }
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index f236877..d3d7331 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -216,7 +216,7 @@
     private CountDownLatch mConnectedSignal = new CountDownLatch(1);
 
     private final RemoteCallbackList<INetworkManagementEventObserver> mObservers =
-            new RemoteCallbackList<INetworkManagementEventObserver>();
+            new RemoteCallbackList<>();
 
     private final NetworkStatsFactory mStatsFactory = new NetworkStatsFactory();
 
@@ -289,7 +289,7 @@
     private int mLastPowerStateFromWifi = DataConnectionRealTimeInfo.DC_POWER_STATE_LOW;
 
     private final RemoteCallbackList<INetworkActivityListener> mNetworkActivityListeners =
-            new RemoteCallbackList<INetworkActivityListener>();
+            new RemoteCallbackList<>();
     private boolean mNetworkActive;
 
     /**
@@ -1161,7 +1161,7 @@
 
     private ArrayList<String> readRouteList(String filename) {
         FileInputStream fstream = null;
-        ArrayList<String> list = new ArrayList<String>();
+        ArrayList<String> list = new ArrayList<>();
 
         try {
             fstream = new FileInputStream(filename);
@@ -1285,7 +1285,7 @@
         } catch (NativeDaemonConnectorException e) {
             throw e.rethrowAsParcelableException();
         }
-        List<RouteInfo> routes = new ArrayList<RouteInfo>();
+        List<RouteInfo> routes = new ArrayList<>();
         // The RouteInfo constructor truncates the LinkAddress to a network prefix, thus making it
         // suitable to use as a route destination.
         routes.add(new RouteInfo(getInterfaceConfig(iface).getLinkAddress(), null, iface));
@@ -1345,7 +1345,7 @@
     }
 
     private List<InterfaceAddress> excludeLinkLocal(List<InterfaceAddress> addresses) {
-        ArrayList<InterfaceAddress> filtered = new ArrayList<InterfaceAddress>(addresses.size());
+        ArrayList<InterfaceAddress> filtered = new ArrayList<>(addresses.size());
         for (InterfaceAddress ia : addresses) {
             if (!ia.getAddress().isLinkLocalAddress())
                 filtered.add(ia);
@@ -1458,122 +1458,6 @@
         }
     }
 
-    /**
-     * Private method used to call execute for a command given the provided arguments.
-     *
-     * This function checks the returned NativeDaemonEvent for the provided expected response code
-     * and message.  If either of these is not correct, an error is logged.
-     *
-     * @param String command The command to execute.
-     * @param Object[] args If needed, arguments for the command to execute.
-     * @param int expectedResponseCode The code expected to be returned in the corresponding event.
-     * @param String expectedResponseMessage The message expected in the returned event.
-     * @param String logMsg The message to log as an error (TAG will be applied).
-     */
-    private void executeOrLogWithMessage(String command, Object[] args,
-            int expectedResponseCode, String expectedResponseMessage, String logMsg)
-            throws NativeDaemonConnectorException {
-        NativeDaemonEvent event = mConnector.execute(command, args);
-        if (event.getCode() != expectedResponseCode
-                || !event.getMessage().equals(expectedResponseMessage)) {
-            Log.e(TAG, logMsg + ": event = " + event);
-        }
-    }
-
-    @Override
-    public void startAccessPoint(WifiConfiguration wifiConfig, String wlanIface) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        Object[] args;
-        String logMsg = "startAccessPoint Error setting up softap";
-        try {
-            if (wifiConfig == null) {
-                args = new Object[] {"set", wlanIface};
-            } else {
-                args = new Object[] {"set", wlanIface, wifiConfig.SSID,
-                        "broadcast", Integer.toString(wifiConfig.apChannel),
-                        getSecurityType(wifiConfig), new SensitiveArg(wifiConfig.preSharedKey)};
-            }
-            executeOrLogWithMessage(SOFT_AP_COMMAND, args, NetdResponseCode.SoftapStatusResult,
-                    SOFT_AP_COMMAND_SUCCESS, logMsg);
-
-            logMsg = "startAccessPoint Error starting softap";
-            args = new Object[] {"startap"};
-            executeOrLogWithMessage(SOFT_AP_COMMAND, args, NetdResponseCode.SoftapStatusResult,
-                    SOFT_AP_COMMAND_SUCCESS, logMsg);
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
-        }
-    }
-
-    private static String getSecurityType(WifiConfiguration wifiConfig) {
-        switch (wifiConfig.getAuthType()) {
-            case KeyMgmt.WPA_PSK:
-                return "wpa-psk";
-            case KeyMgmt.WPA2_PSK:
-                return "wpa2-psk";
-            default:
-                return "open";
-        }
-    }
-
-    /* @param mode can be "AP", "STA" or "P2P" */
-    @Override
-    public void wifiFirmwareReload(String wlanIface, String mode) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        Object[] args = {"fwreload", wlanIface, mode};
-        String logMsg = "wifiFirmwareReload Error reloading "
-                + wlanIface + " fw in " + mode + " mode";
-        try {
-            executeOrLogWithMessage(SOFT_AP_COMMAND, args, NetdResponseCode.SoftapStatusResult,
-                    SOFT_AP_COMMAND_SUCCESS, logMsg);
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
-        }
-
-        // Ensure that before we return from this command, any asynchronous
-        // notifications generated before the command completed have been
-        // processed by all NetworkManagementEventObservers.
-        mConnector.waitForCallbacks();
-    }
-
-    @Override
-    public void stopAccessPoint(String wlanIface) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        Object[] args = {"stopap"};
-        String logMsg = "stopAccessPoint Error stopping softap";
-
-        try {
-            executeOrLogWithMessage(SOFT_AP_COMMAND, args, NetdResponseCode.SoftapStatusResult,
-                    SOFT_AP_COMMAND_SUCCESS, logMsg);
-            wifiFirmwareReload(wlanIface, "STA");
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
-        }
-    }
-
-    @Override
-    public void setAccessPoint(WifiConfiguration wifiConfig, String wlanIface) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        Object[] args;
-        String logMsg = "startAccessPoint Error setting up softap";
-        try {
-            if (wifiConfig == null) {
-                args = new Object[] {"set", wlanIface};
-            } else {
-                // TODO: understand why this is set to "6" instead of
-                // Integer.toString(wifiConfig.apChannel) as in startAccessPoint
-                // TODO: should startAccessPoint call this instead of repeating code?
-                args = new Object[] {"set", wlanIface, wifiConfig.SSID,
-                        "broadcast", "6",
-                        getSecurityType(wifiConfig), new SensitiveArg(wifiConfig.preSharedKey)};
-            }
-            executeOrLogWithMessage(SOFT_AP_COMMAND, args, NetdResponseCode.SoftapStatusResult,
-                    SOFT_AP_COMMAND_SUCCESS, logMsg);
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
-        }
-    }
-
     @Override
     public void addIdleTimer(String iface, int timeout, final int type) {
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index 1012f9a..e9b6690 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -34,8 +34,6 @@
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.INetworkStatsService;
-import android.net.InterfaceConfiguration;
-import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.Network;
 import android.net.NetworkCapabilities;
@@ -58,18 +56,20 @@
 import android.telephony.CarrierConfigManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.TelephonyIntents;
-import com.android.internal.util.IState;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.MessageUtils;
 import com.android.internal.util.Protocol;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.server.IoThread;
+import com.android.server.connectivity.tethering.IControlsTethering;
+import com.android.server.connectivity.tethering.TetherInterfaceStateMachine;
 import com.android.server.net.BaseNetworkObserver;
 
 import java.io.FileDescriptor;
@@ -81,18 +81,16 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
-import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 
 
 /**
  * @hide
  *
- * Timeout
- *
- * TODO - look for parent classes and code sharing
+ * This class holds much of the business logic to allow Android devices
+ * to act as IP gateways via USB, BT, and WiFi interfaces.
  */
-public class Tethering extends BaseNetworkObserver {
+public class Tethering extends BaseNetworkObserver implements IControlsTethering {
 
     private final Context mContext;
     private final static String TAG = "Tethering";
@@ -100,7 +98,7 @@
     private final static boolean VDBG = false;
 
     private static final Class[] messageClasses = {
-            Tethering.class, TetherMasterSM.class, TetherInterfaceSM.class
+            Tethering.class, TetherMasterSM.class, TetherInterfaceStateMachine.class
     };
     private static final SparseArray<String> sMagicDecoderRing =
             MessageUtils.findMessageNames(messageClasses);
@@ -126,17 +124,25 @@
     private final INetworkStatsService mStatsService;
     private final Looper mLooper;
 
-    private HashMap<String, TetherInterfaceSM> mIfaces; // all tethered/tetherable ifaces
+    private static class TetherState {
+        public final TetherInterfaceStateMachine mStateMachine;
+        public int mLastState;
+        public int mLastError;
+        public TetherState(TetherInterfaceStateMachine sm) {
+            mStateMachine = sm;
+            // Assume all state machines start out available and with no errors.
+            mLastState = IControlsTethering.STATE_AVAILABLE;
+            mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+        }
+    }
+    private final ArrayMap<String, TetherState> mTetherStates;
 
-    private BroadcastReceiver mStateReceiver;
+    private final BroadcastReceiver mStateReceiver;
 
     // {@link ComponentName} of the Service used to run tether provisioning.
     private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString(Resources
             .getSystem().getString(com.android.internal.R.string.config_wifi_tether_enable));
 
-    private static final String USB_NEAR_IFACE_ADDR      = "192.168.42.129";
-    private static final int USB_PREFIX_LENGTH        = 24;
-
     // USB is  192.168.42.1 and 255.255.255.0
     // Wifi is 192.168.43.1 and 255.255.255.0
     // BT is limited to max default of 5 connections. 192.168.44.1 to 192.168.48.1
@@ -166,6 +172,9 @@
     private boolean mUsbTetherRequested; // true if USB tethering should be started
                                          // when RNDIS is enabled
 
+    // True iff WiFi tethering should be started when soft AP is ready.
+    private boolean mWifiTetherRequested;
+
     public Tethering(Context context, INetworkManagementService nmService,
             INetworkStatsService statsService) {
         mContext = context;
@@ -174,7 +183,7 @@
 
         mPublicSync = new Object();
 
-        mIfaces = new HashMap<String, TetherInterfaceSM>();
+        mTetherStates = new ArrayMap<>();
 
         // make our own thread so we don't anr the system
         mLooper = IoThread.get().getLooper();
@@ -187,6 +196,7 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(UsbManager.ACTION_USB_STATE);
         filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
+        filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
         filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
         mContext.registerReceiver(mStateReceiver, filter);
 
@@ -227,7 +237,7 @@
 
         int ifaceTypes[] = mContext.getResources().getIntArray(
                 com.android.internal.R.array.config_tether_upstream_types);
-        Collection<Integer> upstreamIfaceTypes = new ArrayList();
+        Collection<Integer> upstreamIfaceTypes = new ArrayList<>();
         for (int i : ifaceTypes) {
             upstreamIfaceTypes.add(new Integer(i));
         }
@@ -248,34 +258,28 @@
         // Never called directly: only called from interfaceLinkStateChanged.
         // See NetlinkHandler.cpp:71.
         if (VDBG) Log.d(TAG, "interfaceStatusChanged " + iface + ", " + up);
-        boolean found = false;
-        boolean usb = false;
         synchronized (mPublicSync) {
-            if (isWifi(iface)) {
-                found = true;
-            } else if (isUsb(iface)) {
-                found = true;
-                usb = true;
-            } else if (isBluetooth(iface)) {
-                found = true;
+            int interfaceType = ifaceNameToType(iface);
+            if (interfaceType == ConnectivityManager.TETHERING_INVALID) {
+                return;
             }
-            if (found == false) return;
 
-            TetherInterfaceSM sm = mIfaces.get(iface);
+            TetherState tetherState = mTetherStates.get(iface);
             if (up) {
-                if (sm == null) {
-                    sm = new TetherInterfaceSM(iface, mLooper, usb);
-                    mIfaces.put(iface, sm);
-                    sm.start();
+                if (tetherState == null) {
+                    trackNewTetherableInterface(iface, interfaceType);
                 }
             } else {
-                if (isUsb(iface)) {
-                    // ignore usb0 down after enabling RNDIS
-                    // we will handle disconnect in interfaceRemoved instead
+                if (interfaceType == ConnectivityManager.TETHERING_BLUETOOTH) {
+                    tetherState.mStateMachine.sendMessage(
+                            TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
+                    mTetherStates.remove(iface);
+                } else {
+                    // Ignore usb0 down after enabling RNDIS.
+                    // We will handle disconnect in interfaceRemoved.
+                    // Similarly, ignore interface down for WiFi.  We monitor WiFi AP status
+                    // through the WifiManager.WIFI_AP_STATE_CHANGED_ACTION intent.
                     if (VDBG) Log.d(TAG, "ignore interface down for " + iface);
-                } else if (sm != null) {
-                    sm.sendMessage(TetherInterfaceSM.CMD_INTERFACE_DOWN);
-                    mIfaces.remove(iface);
                 }
             }
         }
@@ -295,7 +299,7 @@
         }
     }
 
-    public boolean isWifi(String iface) {
+    private boolean isWifi(String iface) {
         synchronized (mPublicSync) {
             for (String regex : mTetherableWifiRegexs) {
                 if (iface.matches(regex)) return true;
@@ -304,7 +308,7 @@
         }
     }
 
-    public boolean isBluetooth(String iface) {
+    private boolean isBluetooth(String iface) {
         synchronized (mPublicSync) {
             for (String regex : mTetherableBluetoothRegexs) {
                 if (iface.matches(regex)) return true;
@@ -313,35 +317,33 @@
         }
     }
 
+    private int ifaceNameToType(String iface) {
+        if (isWifi(iface)) {
+            return ConnectivityManager.TETHERING_WIFI;
+        } else if (isUsb(iface)) {
+            return ConnectivityManager.TETHERING_USB;
+        } else if (isBluetooth(iface)) {
+            return ConnectivityManager.TETHERING_BLUETOOTH;
+        }
+        return ConnectivityManager.TETHERING_INVALID;
+    }
+
     @Override
     public void interfaceAdded(String iface) {
         if (VDBG) Log.d(TAG, "interfaceAdded " + iface);
-        boolean found = false;
-        boolean usb = false;
         synchronized (mPublicSync) {
-            if (isWifi(iface)) {
-                found = true;
-            }
-            if (isUsb(iface)) {
-                found = true;
-                usb = true;
-            }
-            if (isBluetooth(iface)) {
-                found = true;
-            }
-            if (found == false) {
+            int interfaceType = ifaceNameToType(iface);
+            if (interfaceType == ConnectivityManager.TETHERING_INVALID) {
                 if (VDBG) Log.d(TAG, iface + " is not a tetherable iface, ignoring");
                 return;
             }
 
-            TetherInterfaceSM sm = mIfaces.get(iface);
-            if (sm != null) {
+            TetherState tetherState = mTetherStates.get(iface);
+            if (tetherState == null) {
+                trackNewTetherableInterface(iface, interfaceType);
+            } else {
                 if (VDBG) Log.d(TAG, "active iface (" + iface + ") reported as added, ignoring");
-                return;
             }
-            sm = new TetherInterfaceSM(iface, mLooper, usb);
-            mIfaces.put(iface, sm);
-            sm.start();
         }
     }
 
@@ -349,15 +351,15 @@
     public void interfaceRemoved(String iface) {
         if (VDBG) Log.d(TAG, "interfaceRemoved " + iface);
         synchronized (mPublicSync) {
-            TetherInterfaceSM sm = mIfaces.get(iface);
-            if (sm == null) {
+            TetherState tetherState = mTetherStates.get(iface);
+            if (tetherState == null) {
                 if (VDBG) {
                     Log.e(TAG, "attempting to remove unknown iface (" + iface + "), ignoring");
                 }
                 return;
             }
-            sm.sendMessage(TetherInterfaceSM.CMD_INTERFACE_DOWN);
-            mIfaces.remove(iface);
+            tetherState.mStateMachine.sendMessage(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
+            mTetherStates.remove(iface);
         }
     }
 
@@ -413,24 +415,19 @@
      * for the specified interface.
      */
     private void enableTetheringInternal(int type, boolean enable, ResultReceiver receiver) {
-        boolean isProvisioningRequired = isTetherProvisioningRequired();
+        boolean isProvisioningRequired = enable && isTetherProvisioningRequired();
+        int result;
         switch (type) {
             case ConnectivityManager.TETHERING_WIFI:
-                final WifiManager wifiManager =
-                        (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
-                if (wifiManager.setWifiApEnabled(null, enable)) {
-                    sendTetherResult(receiver, ConnectivityManager.TETHER_ERROR_NO_ERROR);
-                    if (enable && isProvisioningRequired) {
-                        scheduleProvisioningRechecks(type);
-                    }
-                } else{
-                    sendTetherResult(receiver, ConnectivityManager.TETHER_ERROR_MASTER_ERROR);
+                result = setWifiTethering(enable);
+                if (isProvisioningRequired && result == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+                    scheduleProvisioningRechecks(type);
                 }
+                sendTetherResult(receiver, result);
                 break;
             case ConnectivityManager.TETHERING_USB:
-                int result = setUsbTethering(enable);
-                if (enable && isProvisioningRequired &&
-                        result == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+                result = setUsbTethering(enable);
+                if (isProvisioningRequired && result == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
                     scheduleProvisioningRechecks(type);
                 }
                 sendTetherResult(receiver, result);
@@ -450,6 +447,18 @@
         }
     }
 
+    private int setWifiTethering(final boolean enable) {
+        synchronized (mPublicSync) {
+            mWifiTetherRequested = enable;
+            final WifiManager wifiManager =
+                    (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+            if (wifiManager.setWifiApEnabled(null /* use existing wifi config */, enable)) {
+                return ConnectivityManager.TETHER_ERROR_NO_ERROR;
+            }
+            return ConnectivityManager.TETHER_ERROR_MASTER_ERROR;
+        }
+    }
+
     private void setBluetoothTethering(final boolean enable, final ResultReceiver receiver) {
         final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
         if (adapter == null || !adapter.isEnabled()) {
@@ -576,62 +585,62 @@
 
     public int tether(String iface) {
         if (DBG) Log.d(TAG, "Tethering " + iface);
-        TetherInterfaceSM sm = null;
         synchronized (mPublicSync) {
-            sm = mIfaces.get(iface);
+            TetherState tetherState = mTetherStates.get(iface);
+            if (tetherState == null) {
+                Log.e(TAG, "Tried to Tether an unknown iface :" + iface + ", ignoring");
+                return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
+            }
+            // Ignore the error status of the interface.  If the interface is available,
+            // the errors are referring to past tethering attempts anyway.
+            if (tetherState.mLastState != IControlsTethering.STATE_AVAILABLE) {
+                Log.e(TAG, "Tried to Tether an unavailable iface :" + iface + ", ignoring");
+                return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
+            }
+            tetherState.mStateMachine.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
+            return ConnectivityManager.TETHER_ERROR_NO_ERROR;
         }
-        if (sm == null) {
-            Log.e(TAG, "Tried to Tether an unknown iface :" + iface + ", ignoring");
-            return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
-        }
-        if (!sm.isAvailable() && !sm.isErrored()) {
-            Log.e(TAG, "Tried to Tether an unavailable iface :" + iface + ", ignoring");
-            return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
-        }
-        sm.sendMessage(TetherInterfaceSM.CMD_TETHER_REQUESTED);
-        return ConnectivityManager.TETHER_ERROR_NO_ERROR;
     }
 
     public int untether(String iface) {
         if (DBG) Log.d(TAG, "Untethering " + iface);
-        TetherInterfaceSM sm = null;
         synchronized (mPublicSync) {
-            sm = mIfaces.get(iface);
+            TetherState tetherState = mTetherStates.get(iface);
+            if (tetherState == null) {
+                Log.e(TAG, "Tried to Untether an unknown iface :" + iface + ", ignoring");
+                return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
+            }
+            if (tetherState.mLastState != IControlsTethering.STATE_TETHERED) {
+                Log.e(TAG, "Tried to untether an untethered iface :" + iface + ", ignoring");
+                return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
+            }
+            tetherState.mStateMachine.sendMessage(
+                    TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
+            return ConnectivityManager.TETHER_ERROR_NO_ERROR;
         }
-        if (sm == null) {
-            Log.e(TAG, "Tried to Untether an unknown iface :" + iface + ", ignoring");
-            return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
-        }
-        if (sm.isErrored()) {
-            Log.e(TAG, "Tried to Untethered an errored iface :" + iface + ", ignoring");
-            return ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
-        }
-        sm.sendMessage(TetherInterfaceSM.CMD_TETHER_UNREQUESTED);
-        return ConnectivityManager.TETHER_ERROR_NO_ERROR;
     }
 
     public void untetherAll() {
-        if (DBG) Log.d(TAG, "Untethering " + mIfaces);
-        for (String iface : mIfaces.keySet()) {
-            untether(iface);
+        synchronized (mPublicSync) {
+            if (DBG) Log.d(TAG, "Untethering " + mTetherStates.keySet());
+            for (int i = 0; i < mTetherStates.size(); i++) {
+                untether(mTetherStates.keyAt(i));
+            }
         }
     }
 
     public int getLastTetherError(String iface) {
-        TetherInterfaceSM sm = null;
         synchronized (mPublicSync) {
-            sm = mIfaces.get(iface);
-            if (sm == null) {
+            TetherState tetherState = mTetherStates.get(iface);
+            if (tetherState == null) {
                 Log.e(TAG, "Tried to getLastTetherError on an unknown iface :" + iface +
                         ", ignoring");
                 return ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
             }
-            return sm.getLastError();
+            return tetherState.mLastError;
         }
     }
 
-    // TODO - move all private methods used only by the state machine into the state machine
-    // to clarify what needs synchronized protection.
     private void sendTetherStateChangedBroadcast() {
         if (!getConnectivityManager().isTetheringSupported()) return;
 
@@ -644,24 +653,22 @@
         boolean bluetoothTethered = false;
 
         synchronized (mPublicSync) {
-            Set ifaces = mIfaces.keySet();
-            for (Object iface : ifaces) {
-                TetherInterfaceSM sm = mIfaces.get(iface);
-                if (sm != null) {
-                    if (sm.isErrored()) {
-                        erroredList.add((String)iface);
-                    } else if (sm.isAvailable()) {
-                        availableList.add((String)iface);
-                    } else if (sm.isTethered()) {
-                        if (isUsb((String)iface)) {
-                            usbTethered = true;
-                        } else if (isWifi((String)iface)) {
-                            wifiTethered = true;
-                      } else if (isBluetooth((String)iface)) {
-                            bluetoothTethered = true;
-                        }
-                        activeList.add((String)iface);
+            for (int i = 0; i < mTetherStates.size(); i++) {
+                TetherState tetherState = mTetherStates.valueAt(i);
+                String iface = mTetherStates.keyAt(i);
+                if (tetherState.mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+                    erroredList.add(iface);
+                } else if (tetherState.mLastState == IControlsTethering.STATE_AVAILABLE) {
+                    availableList.add(iface);
+                } else if (tetherState.mLastState == IControlsTethering.STATE_TETHERED) {
+                    if (isUsb(iface)) {
+                        usbTethered = true;
+                    } else if (isWifi(iface)) {
+                        wifiTethered = true;
+                    } else if (isBluetooth(iface)) {
+                        bluetoothTethered = true;
                     }
+                    activeList.add(iface);
                 }
             }
         }
@@ -770,7 +777,7 @@
                     mRndisEnabled = intent.getBooleanExtra(UsbManager.USB_FUNCTION_RNDIS, false);
                     // start tethering if we have a request pending
                     if (usbConnected && mRndisEnabled && mUsbTetherRequested) {
-                        tetherUsb(true);
+                        tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_USB);
                     }
                     mUsbTetherRequested = false;
                 }
@@ -782,69 +789,80 @@
                     if (VDBG) Log.d(TAG, "Tethering got CONNECTIVITY_ACTION");
                     mTetherMasterSM.sendMessage(TetherMasterSM.CMD_UPSTREAM_CHANGED);
                 }
+            } else if (action.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION)) {
+                synchronized (Tethering.this.mPublicSync) {
+                    int curState =  intent.getIntExtra(WifiManager.EXTRA_WIFI_AP_STATE,
+                            WifiManager.WIFI_AP_STATE_DISABLED);
+                    switch (curState) {
+                        case WifiManager.WIFI_AP_STATE_ENABLING:
+                            // We can see this state on the way to both enabled and failure states.
+                            break;
+                        case WifiManager.WIFI_AP_STATE_ENABLED:
+                            // When the AP comes up and we've been requested to tether it, do so.
+                            if (mWifiTetherRequested) {
+                                tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_WIFI);
+                            }
+                            break;
+                        case WifiManager.WIFI_AP_STATE_DISABLED:
+                        case WifiManager.WIFI_AP_STATE_DISABLING:
+                        case WifiManager.WIFI_AP_STATE_FAILED:
+                        default:
+                            if (DBG) {
+                                Log.d(TAG, "Canceling WiFi tethering request - AP_STATE=" +
+                                    curState);
+                            }
+                            // Tell appropriate interface state machines that they should tear
+                            // themselves down.
+                            for (int i = 0; i < mTetherStates.size(); i++) {
+                                TetherInterfaceStateMachine tism =
+                                        mTetherStates.valueAt(i).mStateMachine;
+                                if (tism.interfaceType() == ConnectivityManager.TETHERING_WIFI) {
+                                    tism.sendMessage(
+                                            TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
+                                    break;  // There should be at most one of these.
+                                }
+                            }
+                            // Regardless of whether we requested this transition, the AP has gone
+                            // down.  Don't try to tether again unless we're requested to do so.
+                            mWifiTetherRequested = false;
+                            break;
+                    }
+                }
             } else if (action.equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
                 updateConfiguration();
             }
         }
     }
 
-    private void tetherUsb(boolean enable) {
-        if (VDBG) Log.d(TAG, "tetherUsb " + enable);
+    private void tetherMatchingInterfaces(boolean enable, int interfaceType) {
+        if (VDBG) Log.d(TAG, "tetherMatchingInterfaces(" + enable + ", " + interfaceType + ")");
 
-        String[] ifaces = new String[0];
+        String[] ifaces = null;
         try {
             ifaces = mNMService.listInterfaces();
         } catch (Exception e) {
             Log.e(TAG, "Error listing Interfaces", e);
             return;
         }
-        for (String iface : ifaces) {
-            if (isUsb(iface)) {
-                int result = (enable ? tether(iface) : untether(iface));
-                if (result == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
-                    return;
+        String chosenIface = null;
+        if (ifaces != null) {
+            for (String iface : ifaces) {
+                if (ifaceNameToType(iface) == interfaceType) {
+                    chosenIface = iface;
+                    break;
                 }
             }
         }
-        Log.e(TAG, "unable start or stop USB tethering");
-    }
-
-    // configured when we start tethering and unconfig'd on error or conclusion
-    private boolean configureUsbIface(boolean enabled) {
-        if (VDBG) Log.d(TAG, "configureUsbIface(" + enabled + ")");
-
-        // toggle the USB interfaces
-        String[] ifaces = new String[0];
-        try {
-            ifaces = mNMService.listInterfaces();
-        } catch (Exception e) {
-            Log.e(TAG, "Error listing Interfaces", e);
-            return false;
+        if (chosenIface == null) {
+            Log.e(TAG, "could not find iface of type " + interfaceType);
+            return;
         }
-        for (String iface : ifaces) {
-            if (isUsb(iface)) {
-                InterfaceConfiguration ifcg = null;
-                try {
-                    ifcg = mNMService.getInterfaceConfig(iface);
-                    if (ifcg != null) {
-                        InetAddress addr = NetworkUtils.numericToInetAddress(USB_NEAR_IFACE_ADDR);
-                        ifcg.setLinkAddress(new LinkAddress(addr, USB_PREFIX_LENGTH));
-                        if (enabled) {
-                            ifcg.setInterfaceUp();
-                        } else {
-                            ifcg.setInterfaceDown();
-                        }
-                        ifcg.clearFlag("running");
-                        mNMService.setInterfaceConfig(iface, ifcg);
-                    }
-                } catch (Exception e) {
-                    Log.e(TAG, "Error configuring interface " + iface, e);
-                    return false;
-                }
-            }
-         }
 
-        return true;
+        int result = (enable ? tether(chosenIface) : untether(chosenIface));
+        if (result != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+            Log.e(TAG, "unable start or stop tethering on iface " + chosenIface);
+            return;
+        }
     }
 
     // TODO - return copies so people can't tamper
@@ -869,7 +887,7 @@
                 if (mRndisEnabled) {
                     final long ident = Binder.clearCallingIdentity();
                     try {
-                        tetherUsb(true);
+                        tetherMatchingInterfaces(true, ConnectivityManager.TETHERING_USB);
                     } finally {
                         Binder.restoreCallingIdentity(ident);
                     }
@@ -880,7 +898,7 @@
             } else {
                 final long ident = Binder.clearCallingIdentity();
                 try {
-                    tetherUsb(false);
+                    tetherMatchingInterfaces(false, ConnectivityManager.TETHERING_USB);
                 } finally {
                     Binder.restoreCallingIdentity(ident);
                 }
@@ -952,37 +970,27 @@
     public String[] getTetheredIfaces() {
         ArrayList<String> list = new ArrayList<String>();
         synchronized (mPublicSync) {
-            Set keys = mIfaces.keySet();
-            for (Object key : keys) {
-                TetherInterfaceSM sm = mIfaces.get(key);
-                if (sm.isTethered()) {
-                    list.add((String)key);
+            for (int i = 0; i < mTetherStates.size(); i++) {
+                TetherState tetherState = mTetherStates.valueAt(i);
+                if (tetherState.mLastState == IControlsTethering.STATE_TETHERED) {
+                    list.add(mTetherStates.keyAt(i));
                 }
             }
         }
-        String[] retVal = new String[list.size()];
-        for (int i=0; i < list.size(); i++) {
-            retVal[i] = list.get(i);
-        }
-        return retVal;
+        return list.toArray(new String[list.size()]);
     }
 
     public String[] getTetherableIfaces() {
         ArrayList<String> list = new ArrayList<String>();
         synchronized (mPublicSync) {
-            Set keys = mIfaces.keySet();
-            for (Object key : keys) {
-                TetherInterfaceSM sm = mIfaces.get(key);
-                if (sm.isAvailable()) {
-                    list.add((String)key);
+            for (int i = 0; i < mTetherStates.size(); i++) {
+                TetherState tetherState = mTetherStates.valueAt(i);
+                if (tetherState.mLastState == IControlsTethering.STATE_AVAILABLE) {
+                    list.add(mTetherStates.keyAt(i));
                 }
             }
         }
-        String[] retVal = new String[list.size()];
-        for (int i=0; i < list.size(); i++) {
-            retVal[i] = list.get(i);
-        }
-        return retVal;
+        return list.toArray(new String[list.size()]);
     }
 
     public String[] getTetheredDhcpRanges() {
@@ -992,19 +1000,14 @@
     public String[] getErroredIfaces() {
         ArrayList<String> list = new ArrayList<String>();
         synchronized (mPublicSync) {
-            Set keys = mIfaces.keySet();
-            for (Object key : keys) {
-                TetherInterfaceSM sm = mIfaces.get(key);
-                if (sm.isErrored()) {
-                    list.add((String)key);
+            for (int i = 0; i < mTetherStates.size(); i++) {
+                TetherState tetherState = mTetherStates.valueAt(i);
+                if (tetherState.mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+                    list.add(mTetherStates.keyAt(i));
                 }
             }
         }
-        String[] retVal = new String[list.size()];
-        for (int i= 0; i< list.size(); i++) {
-            retVal[i] = list.get(i);
-        }
-        return retVal;
+        return list.toArray(new String[list.size()]);
     }
 
     private void maybeLogMessage(State state, int what) {
@@ -1014,401 +1017,6 @@
         }
     }
 
-    class TetherInterfaceSM extends StateMachine {
-        private static final int BASE_IFACE              = Protocol.BASE_TETHERING + 100;
-        // notification from the master SM that it's not in tether mode
-        static final int CMD_TETHER_MODE_DEAD            = BASE_IFACE + 1;
-        // request from the user that it wants to tether
-        static final int CMD_TETHER_REQUESTED            = BASE_IFACE + 2;
-        // request from the user that it wants to untether
-        static final int CMD_TETHER_UNREQUESTED          = BASE_IFACE + 3;
-        // notification that this interface is down
-        static final int CMD_INTERFACE_DOWN              = BASE_IFACE + 4;
-        // notification that this interface is up
-        static final int CMD_INTERFACE_UP                = BASE_IFACE + 5;
-        // notification from the master SM that it had an error turning on cellular dun
-        static final int CMD_CELL_DUN_ERROR              = BASE_IFACE + 6;
-        // notification from the master SM that it had trouble enabling IP Forwarding
-        static final int CMD_IP_FORWARDING_ENABLE_ERROR  = BASE_IFACE + 7;
-        // notification from the master SM that it had trouble disabling IP Forwarding
-        static final int CMD_IP_FORWARDING_DISABLE_ERROR = BASE_IFACE + 8;
-        // notification from the master SM that it had trouble starting tethering
-        static final int CMD_START_TETHERING_ERROR       = BASE_IFACE + 9;
-        // notification from the master SM that it had trouble stopping tethering
-        static final int CMD_STOP_TETHERING_ERROR        = BASE_IFACE + 10;
-        // notification from the master SM that it had trouble setting the DNS forwarders
-        static final int CMD_SET_DNS_FORWARDERS_ERROR    = BASE_IFACE + 11;
-        // the upstream connection has changed
-        static final int CMD_TETHER_CONNECTION_CHANGED   = BASE_IFACE + 12;
-
-        private State mDefaultState;
-
-        private State mInitialState;
-        private State mStartingState;
-        private State mTetheredState;
-
-        private State mUnavailableState;
-
-        private boolean mAvailable;
-        private boolean mTethered;
-        int mLastError;
-
-        String mIfaceName;
-        String mMyUpstreamIfaceName;  // may change over time
-
-        boolean mUsb;
-
-        TetherInterfaceSM(String name, Looper looper, boolean usb) {
-            super(name, looper);
-            mIfaceName = name;
-            mUsb = usb;
-            setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR);
-
-            mInitialState = new InitialState();
-            addState(mInitialState);
-            mStartingState = new StartingState();
-            addState(mStartingState);
-            mTetheredState = new TetheredState();
-            addState(mTetheredState);
-            mUnavailableState = new UnavailableState();
-            addState(mUnavailableState);
-
-            setInitialState(mInitialState);
-        }
-
-        public String toString() {
-            String res = new String();
-            res += mIfaceName + " - ";
-            IState current = getCurrentState();
-            if (current == mInitialState) res += "InitialState";
-            if (current == mStartingState) res += "StartingState";
-            if (current == mTetheredState) res += "TetheredState";
-            if (current == mUnavailableState) res += "UnavailableState";
-            if (mAvailable) res += " - Available";
-            if (mTethered) res += " - Tethered";
-            res += " - lastError =" + mLastError;
-            return res;
-        }
-
-        public int getLastError() {
-            synchronized (Tethering.this.mPublicSync) {
-                return mLastError;
-            }
-        }
-
-        private void setLastError(int error) {
-            synchronized (Tethering.this.mPublicSync) {
-                mLastError = error;
-
-                if (isErrored()) {
-                    if (mUsb) {
-                        // note everything's been unwound by this point so nothing to do on
-                        // further error..
-                        Tethering.this.configureUsbIface(false);
-                    }
-                }
-            }
-        }
-
-        public boolean isAvailable() {
-            synchronized (Tethering.this.mPublicSync) {
-                return mAvailable;
-            }
-        }
-
-        private void setAvailable(boolean available) {
-            synchronized (Tethering.this.mPublicSync) {
-                mAvailable = available;
-            }
-        }
-
-        public boolean isTethered() {
-            synchronized (Tethering.this.mPublicSync) {
-                return mTethered;
-            }
-        }
-
-        private void setTethered(boolean tethered) {
-            synchronized (Tethering.this.mPublicSync) {
-                mTethered = tethered;
-            }
-        }
-
-        public boolean isErrored() {
-            synchronized (Tethering.this.mPublicSync) {
-                return (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR);
-            }
-        }
-
-        class InitialState extends State {
-            @Override
-            public void enter() {
-                setAvailable(true);
-                setTethered(false);
-                sendTetherStateChangedBroadcast();
-            }
-
-            @Override
-            public boolean processMessage(Message message) {
-                maybeLogMessage(this, message.what);
-                boolean retValue = true;
-                switch (message.what) {
-                    case CMD_TETHER_REQUESTED:
-                        setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR);
-                        mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_REQUESTED,
-                                TetherInterfaceSM.this);
-                        transitionTo(mStartingState);
-                        break;
-                    case CMD_INTERFACE_DOWN:
-                        transitionTo(mUnavailableState);
-                        break;
-                    default:
-                        retValue = false;
-                        break;
-                }
-                return retValue;
-            }
-        }
-
-        class StartingState extends State {
-            @Override
-            public void enter() {
-                setAvailable(false);
-                if (mUsb) {
-                    if (!Tethering.this.configureUsbIface(true)) {
-                        mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED,
-                                TetherInterfaceSM.this);
-                        setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
-
-                        transitionTo(mInitialState);
-                        return;
-                    }
-                }
-                sendTetherStateChangedBroadcast();
-
-                // Skipping StartingState
-                transitionTo(mTetheredState);
-            }
-            @Override
-            public boolean processMessage(Message message) {
-                maybeLogMessage(this, message.what);
-                boolean retValue = true;
-                switch (message.what) {
-                    // maybe a parent class?
-                    case CMD_TETHER_UNREQUESTED:
-                        mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED,
-                                TetherInterfaceSM.this);
-                        if (mUsb) {
-                            if (!Tethering.this.configureUsbIface(false)) {
-                                setLastErrorAndTransitionToInitialState(
-                                    ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
-                                break;
-                            }
-                        }
-                        transitionTo(mInitialState);
-                        break;
-                    case CMD_CELL_DUN_ERROR:
-                    case CMD_IP_FORWARDING_ENABLE_ERROR:
-                    case CMD_IP_FORWARDING_DISABLE_ERROR:
-                    case CMD_START_TETHERING_ERROR:
-                    case CMD_STOP_TETHERING_ERROR:
-                    case CMD_SET_DNS_FORWARDERS_ERROR:
-                        setLastErrorAndTransitionToInitialState(
-                                ConnectivityManager.TETHER_ERROR_MASTER_ERROR);
-                        break;
-                    case CMD_INTERFACE_DOWN:
-                        mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED,
-                                TetherInterfaceSM.this);
-                        transitionTo(mUnavailableState);
-                        break;
-                    default:
-                        retValue = false;
-                }
-                return retValue;
-            }
-        }
-
-        class TetheredState extends State {
-            @Override
-            public void enter() {
-                try {
-                    mNMService.tetherInterface(mIfaceName);
-                } catch (Exception e) {
-                    Log.e(TAG, "Error Tethering: " + e.toString());
-                    setLastError(ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR);
-
-                    try {
-                        mNMService.untetherInterface(mIfaceName);
-                    } catch (Exception ee) {
-                        Log.e(TAG, "Error untethering after failure!" + ee.toString());
-                    }
-                    transitionTo(mInitialState);
-                    return;
-                }
-                if (DBG) Log.d(TAG, "Tethered " + mIfaceName);
-                setAvailable(false);
-                setTethered(true);
-                sendTetherStateChangedBroadcast();
-            }
-
-            private void cleanupUpstream() {
-                if (mMyUpstreamIfaceName != null) {
-                    // note that we don't care about errors here.
-                    // sometimes interfaces are gone before we get
-                    // to remove their rules, which generates errors.
-                    // just do the best we can.
-                    try {
-                        // about to tear down NAT; gather remaining statistics
-                        mStatsService.forceUpdate();
-                    } catch (Exception e) {
-                        if (VDBG) Log.e(TAG, "Exception in forceUpdate: " + e.toString());
-                    }
-                    try {
-                        mNMService.stopInterfaceForwarding(mIfaceName, mMyUpstreamIfaceName);
-                    } catch (Exception e) {
-                        if (VDBG) Log.e(
-                                TAG, "Exception in removeInterfaceForward: " + e.toString());
-                    }
-                    try {
-                        mNMService.disableNat(mIfaceName, mMyUpstreamIfaceName);
-                    } catch (Exception e) {
-                        if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString());
-                    }
-                    mMyUpstreamIfaceName = null;
-                }
-                return;
-            }
-
-            @Override
-            public boolean processMessage(Message message) {
-                maybeLogMessage(this, message.what);
-                boolean retValue = true;
-                boolean error = false;
-                switch (message.what) {
-                    case CMD_TETHER_UNREQUESTED:
-                    case CMD_INTERFACE_DOWN:
-                        cleanupUpstream();
-                        try {
-                            mNMService.untetherInterface(mIfaceName);
-                        } catch (Exception e) {
-                            setLastErrorAndTransitionToInitialState(
-                                    ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR);
-                            break;
-                        }
-                        mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED,
-                                TetherInterfaceSM.this);
-                        if (message.what == CMD_TETHER_UNREQUESTED) {
-                            if (mUsb) {
-                                if (!Tethering.this.configureUsbIface(false)) {
-                                    setLastError(
-                                            ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
-                                }
-                            }
-                            transitionTo(mInitialState);
-                        } else if (message.what == CMD_INTERFACE_DOWN) {
-                            transitionTo(mUnavailableState);
-                        }
-                        if (DBG) Log.d(TAG, "Untethered " + mIfaceName);
-                        break;
-                    case CMD_TETHER_CONNECTION_CHANGED:
-                        String newUpstreamIfaceName = (String)(message.obj);
-                        if ((mMyUpstreamIfaceName == null && newUpstreamIfaceName == null) ||
-                                (mMyUpstreamIfaceName != null &&
-                                mMyUpstreamIfaceName.equals(newUpstreamIfaceName))) {
-                            if (VDBG) Log.d(TAG, "Connection changed noop - dropping");
-                            break;
-                        }
-                        cleanupUpstream();
-                        if (newUpstreamIfaceName != null) {
-                            try {
-                                mNMService.enableNat(mIfaceName, newUpstreamIfaceName);
-                                mNMService.startInterfaceForwarding(mIfaceName,
-                                        newUpstreamIfaceName);
-                            } catch (Exception e) {
-                                Log.e(TAG, "Exception enabling Nat: " + e.toString());
-                                try {
-                                    mNMService.disableNat(mIfaceName, newUpstreamIfaceName);
-                                } catch (Exception ee) {}
-                                try {
-                                    mNMService.untetherInterface(mIfaceName);
-                                } catch (Exception ee) {}
-
-                                setLastError(ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR);
-                                transitionTo(mInitialState);
-                                return true;
-                            }
-                        }
-                        mMyUpstreamIfaceName = newUpstreamIfaceName;
-                        break;
-                    case CMD_CELL_DUN_ERROR:
-                    case CMD_IP_FORWARDING_ENABLE_ERROR:
-                    case CMD_IP_FORWARDING_DISABLE_ERROR:
-                    case CMD_START_TETHERING_ERROR:
-                    case CMD_STOP_TETHERING_ERROR:
-                    case CMD_SET_DNS_FORWARDERS_ERROR:
-                        error = true;
-                        // fall through
-                    case CMD_TETHER_MODE_DEAD:
-                        cleanupUpstream();
-                        try {
-                            mNMService.untetherInterface(mIfaceName);
-                        } catch (Exception e) {
-                            setLastErrorAndTransitionToInitialState(
-                                    ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR);
-                            break;
-                        }
-                        if (error) {
-                            setLastErrorAndTransitionToInitialState(
-                                    ConnectivityManager.TETHER_ERROR_MASTER_ERROR);
-                            break;
-                        }
-                        if (DBG) Log.d(TAG, "Tether lost upstream connection " + mIfaceName);
-                        sendTetherStateChangedBroadcast();
-                        if (mUsb) {
-                            if (!Tethering.this.configureUsbIface(false)) {
-                                setLastError(ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR);
-                            }
-                        }
-                        transitionTo(mInitialState);
-                        break;
-                    default:
-                        retValue = false;
-                        break;
-                }
-                return retValue;
-            }
-        }
-
-        class UnavailableState extends State {
-            @Override
-            public void enter() {
-                setAvailable(false);
-                setLastError(ConnectivityManager.TETHER_ERROR_NO_ERROR);
-                setTethered(false);
-                sendTetherStateChangedBroadcast();
-            }
-            @Override
-            public boolean processMessage(Message message) {
-                boolean retValue = true;
-                switch (message.what) {
-                    case CMD_INTERFACE_UP:
-                        transitionTo(mInitialState);
-                        break;
-                    default:
-                        retValue = false;
-                        break;
-                }
-                return retValue;
-            }
-        }
-
-        void setLastErrorAndTransitionToInitialState(int error) {
-            setLastError(error);
-            transitionTo(mInitialState);
-        }
-
-    }
-
     /**
      * A NetworkCallback class that relays information of interest to the
      * tethering master state machine thread for subsequent processing.
@@ -1442,7 +1050,7 @@
      * could/should be moved here.
      */
     class UpstreamNetworkMonitor {
-        final HashMap<Network, NetworkState> mNetworkMap = new HashMap();
+        final HashMap<Network, NetworkState> mNetworkMap = new HashMap<>();
         NetworkCallback mDefaultNetworkCallback;
         NetworkCallback mDunTetheringCallback;
 
@@ -1520,12 +1128,6 @@
         static final int EVENT_UPSTREAM_LINKPROPERTIES_CHANGED  = BASE_MASTER + 5;
         static final int EVENT_UPSTREAM_LOST                    = BASE_MASTER + 6;
 
-        // This indicates what a timeout event relates to.  A state that
-        // sends itself a delayed timeout event and handles incoming timeout events
-        // should inc this when it is entered and whenever it sends a new timeout event.
-        // We do not flush the old ones.
-        private int mSequenceNumber;
-
         private State mInitialState;
         private State mTetherModeAliveState;
 
@@ -1535,7 +1137,19 @@
         private State mStopTetheringErrorState;
         private State mSetDnsForwardersErrorState;
 
-        private ArrayList<TetherInterfaceSM> mNotifyList;
+        // This list is a little subtle.  It contains all the interfaces that currently are
+        // requesting tethering, regardless of whether these interfaces are still members of
+        // mTetherStates.  This allows us to maintain the following predicates:
+        //
+        // 1) mTetherStates contains the set of all currently existing, tetherable, link state up
+        //    interfaces.
+        // 2) mNotifyList contains all state machines that may have outstanding tethering state
+        //    that needs to be torn down.
+        //
+        // Because we excise interfaces immediately from mTetherStates, we must maintain mNotifyList
+        // so that the garbage collector does not clean up the state machine before it has a chance
+        // to tear itself down.
+        private ArrayList<TetherInterfaceStateMachine> mNotifyList;
 
         private int mMobileApnReserved = ConnectivityManager.TYPE_NONE;
         private NetworkCallback mMobileUpstreamCallback;
@@ -1562,7 +1176,7 @@
             mSetDnsForwardersErrorState = new SetDnsForwardersErrorState();
             addState(mSetDnsForwardersErrorState);
 
-            mNotifyList = new ArrayList<TetherInterfaceSM>();
+            mNotifyList = new ArrayList<>();
             setInitialState(mInitialState);
         }
 
@@ -1777,8 +1391,8 @@
             protected void notifyTetheredOfNewUpstreamIface(String ifaceName) {
                 if (DBG) Log.d(TAG, "Notifying tethered with upstream=" + ifaceName);
                 mCurrentUpstreamIface = ifaceName;
-                for (TetherInterfaceSM sm : mNotifyList) {
-                    sm.sendMessage(TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED,
+                for (TetherInterfaceStateMachine sm : mNotifyList) {
+                    sm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED,
                             ifaceName);
                 }
             }
@@ -1845,20 +1459,16 @@
                                 config_mobile_hotspot_provision_app_no_ui).isEmpty() == false) {
                             ArrayList<Integer> tethered = new ArrayList<Integer>();
                             synchronized (mPublicSync) {
-                                Set ifaces = mIfaces.keySet();
-                                for (Object iface : ifaces) {
-                                    TetherInterfaceSM sm = mIfaces.get(iface);
-                                    if (sm != null && sm.isTethered()) {
-                                        if (isUsb((String)iface)) {
-                                            tethered.add(new Integer(
-                                                    ConnectivityManager.TETHERING_USB));
-                                        } else if (isWifi((String)iface)) {
-                                            tethered.add(new Integer(
-                                                    ConnectivityManager.TETHERING_WIFI));
-                                        } else if (isBluetooth((String)iface)) {
-                                            tethered.add(new Integer(
-                                                    ConnectivityManager.TETHERING_BLUETOOTH));
-                                        }
+                                for (int i = 0; i < mTetherStates.size(); i++) {
+                                    TetherState tetherState = mTetherStates.valueAt(i);
+                                    if (tetherState.mLastState !=
+                                            IControlsTethering.STATE_TETHERED) {
+                                        continue;  // Skip interfaces that aren't tethered.
+                                    }
+                                    String iface = mTetherStates.keyAt(i);
+                                    int interfaceType = ifaceNameToType(iface);
+                                    if (interfaceType != ConnectivityManager.TETHERING_INVALID) {
+                                        tethered.add(new Integer(interfaceType));
                                     }
                                 }
                             }
@@ -1885,26 +1495,22 @@
 
         class InitialState extends TetherMasterUtilState {
             @Override
-            public void enter() {
-            }
-            @Override
             public boolean processMessage(Message message) {
                 maybeLogMessage(this, message.what);
                 boolean retValue = true;
                 switch (message.what) {
                     case CMD_TETHER_MODE_REQUESTED:
-                        TetherInterfaceSM who = (TetherInterfaceSM)message.obj;
+                        TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj;
                         if (VDBG) Log.d(TAG, "Tether Mode requested by " + who);
-                        mNotifyList.add(who);
+                        if (mNotifyList.indexOf(who) < 0) {
+                            mNotifyList.add(who);
+                        }
                         transitionTo(mTetherModeAliveState);
                         break;
                     case CMD_TETHER_MODE_UNREQUESTED:
-                        who = (TetherInterfaceSM)message.obj;
+                        who = (TetherInterfaceStateMachine)message.obj;
                         if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who);
-                        int index = mNotifyList.indexOf(who);
-                        if (index != -1) {
-                            mNotifyList.remove(who);
-                        }
+                        mNotifyList.remove(who);
                         break;
                     default:
                         retValue = false;
@@ -1941,26 +1547,28 @@
                 boolean retValue = true;
                 switch (message.what) {
                     case CMD_TETHER_MODE_REQUESTED:
-                        TetherInterfaceSM who = (TetherInterfaceSM)message.obj;
+                        TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj;
                         if (VDBG) Log.d(TAG, "Tether Mode requested by " + who);
-                        mNotifyList.add(who);
-                        who.sendMessage(TetherInterfaceSM.CMD_TETHER_CONNECTION_CHANGED,
+                        if (mNotifyList.indexOf(who) < 0) {
+                            mNotifyList.add(who);
+                        }
+                        who.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED,
                                 mCurrentUpstreamIface);
                         break;
                     case CMD_TETHER_MODE_UNREQUESTED:
-                        who = (TetherInterfaceSM)message.obj;
+                        who = (TetherInterfaceStateMachine)message.obj;
                         if (VDBG) Log.d(TAG, "Tether Mode unrequested by " + who);
-                        int index = mNotifyList.indexOf(who);
-                        if (index != -1) {
+                        if (mNotifyList.remove(who)) {
                             if (DBG) Log.d(TAG, "TetherModeAlive removing notifyee " + who);
-                            mNotifyList.remove(index);
                             if (mNotifyList.isEmpty()) {
                                 turnOffMasterTetherSettings(); // transitions appropriately
                             } else {
                                 if (DBG) {
                                     Log.d(TAG, "TetherModeAlive still has " + mNotifyList.size() +
                                             " live requests:");
-                                    for (Object o : mNotifyList) Log.d(TAG, "  " + o);
+                                    for (TetherInterfaceStateMachine o : mNotifyList) {
+                                        Log.d(TAG, "  " + o);
+                                    }
                                 }
                             }
                         } else {
@@ -2010,7 +1618,7 @@
                 boolean retValue = true;
                 switch (message.what) {
                     case CMD_TETHER_MODE_REQUESTED:
-                        TetherInterfaceSM who = (TetherInterfaceSM)message.obj;
+                        TetherInterfaceStateMachine who = (TetherInterfaceStateMachine)message.obj;
                         who.sendMessage(mErrorNotification);
                         break;
                     default:
@@ -2020,8 +1628,7 @@
             }
             void notify(int msgType) {
                 mErrorNotification = msgType;
-                for (Object o : mNotifyList) {
-                    TetherInterfaceSM sm = (TetherInterfaceSM)o;
+                for (TetherInterfaceStateMachine sm : mNotifyList) {
                     sm.sendMessage(msgType);
                 }
             }
@@ -2031,7 +1638,7 @@
             @Override
             public void enter() {
                 Log.e(TAG, "Error in setIpForwardingEnabled");
-                notify(TetherInterfaceSM.CMD_IP_FORWARDING_ENABLE_ERROR);
+                notify(TetherInterfaceStateMachine.CMD_IP_FORWARDING_ENABLE_ERROR);
             }
         }
 
@@ -2039,7 +1646,7 @@
             @Override
             public void enter() {
                 Log.e(TAG, "Error in setIpForwardingDisabled");
-                notify(TetherInterfaceSM.CMD_IP_FORWARDING_DISABLE_ERROR);
+                notify(TetherInterfaceStateMachine.CMD_IP_FORWARDING_DISABLE_ERROR);
             }
         }
 
@@ -2047,7 +1654,7 @@
             @Override
             public void enter() {
                 Log.e(TAG, "Error in startTethering");
-                notify(TetherInterfaceSM.CMD_START_TETHERING_ERROR);
+                notify(TetherInterfaceStateMachine.CMD_START_TETHERING_ERROR);
                 try {
                     mNMService.setIpForwardingEnabled(false);
                 } catch (Exception e) {}
@@ -2058,7 +1665,7 @@
             @Override
             public void enter() {
                 Log.e(TAG, "Error in stopTethering");
-                notify(TetherInterfaceSM.CMD_STOP_TETHERING_ERROR);
+                notify(TetherInterfaceStateMachine.CMD_STOP_TETHERING_ERROR);
                 try {
                     mNMService.setIpForwardingEnabled(false);
                 } catch (Exception e) {}
@@ -2069,7 +1676,7 @@
             @Override
             public void enter() {
                 Log.e(TAG, "Error in setDnsForwarders");
-                notify(TetherInterfaceSM.CMD_SET_DNS_FORWARDERS_ERROR);
+                notify(TetherInterfaceStateMachine.CMD_SET_DNS_FORWARDERS_ERROR);
                 try {
                     mNMService.stopTethering();
                 } catch (Exception e) {}
@@ -2080,9 +1687,11 @@
         }
     }
 
+    @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        // Binder.java closes the resource for us.
+        @SuppressWarnings("resource")
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
-
         if (mContext.checkCallingOrSelfPermission(
                 android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) {
             pw.println("Permission Denial: can't dump ConnectivityService.Tether " +
@@ -2102,12 +1711,67 @@
 
             pw.println("Tether state:");
             pw.increaseIndent();
-            for (Object o : mIfaces.values()) {
-                pw.println(o);
+            for (int i = 0; i < mTetherStates.size(); i++) {
+                final String iface = mTetherStates.keyAt(i);
+                final TetherState tetherState = mTetherStates.valueAt(i);
+                pw.print(iface + " - ");
+
+                switch (tetherState.mLastState) {
+                    case IControlsTethering.STATE_UNAVAILABLE:
+                        pw.print("UnavailableState");
+                        break;
+                    case IControlsTethering.STATE_AVAILABLE:
+                        pw.print("AvailableState");
+                        break;
+                    case IControlsTethering.STATE_TETHERED:
+                        pw.print("TetheredState");
+                        break;
+                    default:
+                        pw.print("UnknownState");
+                        break;
+                }
+                pw.println(" - lastError = " + tetherState.mLastError);
             }
             pw.decreaseIndent();
         }
         pw.decreaseIndent();
-        return;
+    }
+
+    @Override
+    public void notifyInterfaceStateChange(String iface, TetherInterfaceStateMachine who,
+                                           int state, int error) {
+        synchronized (mPublicSync) {
+            TetherState tetherState = mTetherStates.get(iface);
+            if (tetherState != null && tetherState.mStateMachine.equals(who)) {
+                tetherState.mLastState = state;
+                tetherState.mLastError = error;
+            } else {
+                if (DBG) Log.d(TAG, "got notification from stale iface " + iface);
+            }
+        }
+
+        if (DBG) {
+            Log.d(TAG, "iface " + iface + " notified that it was in state " + state +
+                    " with error " + error);
+        }
+
+        switch (state) {
+            case IControlsTethering.STATE_UNAVAILABLE:
+            case IControlsTethering.STATE_AVAILABLE:
+                mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_UNREQUESTED, who);
+                break;
+            case IControlsTethering.STATE_TETHERED:
+                mTetherMasterSM.sendMessage(TetherMasterSM.CMD_TETHER_MODE_REQUESTED, who);
+                break;
+        }
+        sendTetherStateChangedBroadcast();
+    }
+
+    private void trackNewTetherableInterface(String iface, int interfaceType) {
+        TetherState tetherState;
+        tetherState = new TetherState(new TetherInterfaceStateMachine(iface, mLooper,
+                interfaceType, mNMService, mStatsService, this));
+        mTetherStates.put(iface, tetherState);
+        tetherState.mStateMachine.start();
     }
 }
diff --git a/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java b/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java
new file mode 100644
index 0000000..449b8a8
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/tethering/IControlsTethering.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.tethering;
+
+/**
+ * @hide
+ *
+ * Interface with methods necessary to notify that a given interface is ready for tethering.
+ */
+public interface IControlsTethering {
+    public final int STATE_UNAVAILABLE = 0;
+    public final int STATE_AVAILABLE = 1;
+    public final int STATE_TETHERED = 2;
+
+    /**
+     * Notify that |who| has changed its tethering state.  This may be called from any thread.
+     *
+     * @param iface a network interface (e.g. "wlan0")
+     * @param who corresponding instance of a TetherInterfaceStateMachine
+     * @param state one of IControlsTethering.STATE_*
+     * @param lastError one of ConnectivityManager.TETHER_ERROR_*
+     */
+    void notifyInterfaceStateChange(String iface, TetherInterfaceStateMachine who,
+                                    int state, int lastError);
+}
diff --git a/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
new file mode 100644
index 0000000..50bb022
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/tethering/TetherInterfaceStateMachine.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.tethering;
+
+import android.net.ConnectivityManager;
+import android.net.INetworkStatsService;
+import android.net.InterfaceConfiguration;
+import android.net.LinkAddress;
+import android.net.NetworkUtils;
+import android.os.INetworkManagementService;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.util.MessageUtils;
+import com.android.internal.util.Protocol;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import java.net.InetAddress;
+
+/**
+ * @hide
+ *
+ * Tracks the eligibility of a given network interface for tethering.
+ */
+public class TetherInterfaceStateMachine extends StateMachine {
+    private static final String USB_NEAR_IFACE_ADDR = "192.168.42.129";
+    private static final int USB_PREFIX_LENGTH = 24;
+    private static final String WIFI_HOST_IFACE_ADDR = "192.168.43.1";
+    private static final int WIFI_HOST_IFACE_PREFIX_LENGTH = 24;
+
+    private final static String TAG = "TetherInterfaceSM";
+    private final static boolean DBG = false;
+    private final static boolean VDBG = false;
+    private static final Class[] messageClasses = {
+            TetherInterfaceStateMachine.class
+    };
+    private static final SparseArray<String> sMagicDecoderRing =
+            MessageUtils.findMessageNames(messageClasses);
+
+    private static final int BASE_IFACE              = Protocol.BASE_TETHERING + 100;
+    // request from the user that it wants to tether
+    public static final int CMD_TETHER_REQUESTED            = BASE_IFACE + 2;
+    // request from the user that it wants to untether
+    public static final int CMD_TETHER_UNREQUESTED          = BASE_IFACE + 3;
+    // notification that this interface is down
+    public static final int CMD_INTERFACE_DOWN              = BASE_IFACE + 4;
+    // notification from the master SM that it had trouble enabling IP Forwarding
+    public static final int CMD_IP_FORWARDING_ENABLE_ERROR  = BASE_IFACE + 7;
+    // notification from the master SM that it had trouble disabling IP Forwarding
+    public static final int CMD_IP_FORWARDING_DISABLE_ERROR = BASE_IFACE + 8;
+    // notification from the master SM that it had trouble starting tethering
+    public static final int CMD_START_TETHERING_ERROR       = BASE_IFACE + 9;
+    // notification from the master SM that it had trouble stopping tethering
+    public static final int CMD_STOP_TETHERING_ERROR        = BASE_IFACE + 10;
+    // notification from the master SM that it had trouble setting the DNS forwarders
+    public static final int CMD_SET_DNS_FORWARDERS_ERROR    = BASE_IFACE + 11;
+    // the upstream connection has changed
+    public static final int CMD_TETHER_CONNECTION_CHANGED   = BASE_IFACE + 12;
+
+    private final State mInitialState;
+    private final State mTetheredState;
+    private final State mUnavailableState;
+
+    private final INetworkManagementService mNMService;
+    private final INetworkStatsService mStatsService;
+    private final IControlsTethering mTetherController;
+
+    private final String mIfaceName;
+    private final int mInterfaceType;
+
+    private int mLastError;
+    private String mMyUpstreamIfaceName;  // may change over time
+
+    public TetherInterfaceStateMachine(String ifaceName, Looper looper, int interfaceType,
+                    INetworkManagementService nMService, INetworkStatsService statsService,
+                    IControlsTethering tetherController) {
+        super(ifaceName, looper);
+        mNMService = nMService;
+        mStatsService = statsService;
+        mTetherController = tetherController;
+        mIfaceName = ifaceName;
+        mInterfaceType = interfaceType;
+        mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+
+        mInitialState = new InitialState();
+        addState(mInitialState);
+        mTetheredState = new TetheredState();
+        addState(mTetheredState);
+        mUnavailableState = new UnavailableState();
+        addState(mUnavailableState);
+
+        setInitialState(mInitialState);
+    }
+
+    public int interfaceType() {
+        return mInterfaceType;
+    }
+
+    // configured when we start tethering and unconfig'd on error or conclusion
+    private boolean configureIfaceIp(boolean enabled) {
+        if (VDBG) Log.d(TAG, "configureIfaceIp(" + enabled + ")");
+
+        String ipAsString = null;
+        int prefixLen = 0;
+        if (mInterfaceType == ConnectivityManager.TETHERING_USB) {
+            ipAsString = USB_NEAR_IFACE_ADDR;
+            prefixLen = USB_PREFIX_LENGTH;
+        } else if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) {
+            ipAsString = WIFI_HOST_IFACE_ADDR;
+            prefixLen = WIFI_HOST_IFACE_PREFIX_LENGTH;
+        } else {
+            // Nothing to do, BT does this elsewhere.
+            return true;
+        }
+
+        InterfaceConfiguration ifcg = null;
+        try {
+            ifcg = mNMService.getInterfaceConfig(mIfaceName);
+            if (ifcg != null) {
+                InetAddress addr = NetworkUtils.numericToInetAddress(ipAsString);
+                ifcg.setLinkAddress(new LinkAddress(addr, prefixLen));
+                if (enabled) {
+                    ifcg.setInterfaceUp();
+                } else {
+                    ifcg.setInterfaceDown();
+                }
+                ifcg.clearFlag("running");
+                mNMService.setInterfaceConfig(mIfaceName, ifcg);
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Error configuring interface " + mIfaceName, e);
+            return false;
+        }
+
+        return true;
+    }
+
+    private void maybeLogMessage(State state, int what) {
+        if (DBG) {
+            Log.d(TAG, state.getName() + " got " +
+                    sMagicDecoderRing.get(what, Integer.toString(what)));
+        }
+    }
+
+    class InitialState extends State {
+        @Override
+        public void enter() {
+            mTetherController.notifyInterfaceStateChange(
+                    mIfaceName, TetherInterfaceStateMachine.this,
+                    IControlsTethering.STATE_AVAILABLE, mLastError);
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            maybeLogMessage(this, message.what);
+            boolean retValue = true;
+            switch (message.what) {
+                case CMD_TETHER_REQUESTED:
+                    mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+                    transitionTo(mTetheredState);
+                    break;
+                case CMD_INTERFACE_DOWN:
+                    transitionTo(mUnavailableState);
+                    break;
+                default:
+                    retValue = false;
+                    break;
+            }
+            return retValue;
+        }
+    }
+
+    class TetheredState extends State {
+        @Override
+        public void enter() {
+            if (!configureIfaceIp(true)) {
+                mLastError = ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR;
+                transitionTo(mInitialState);
+                return;
+            }
+
+            try {
+                mNMService.tetherInterface(mIfaceName);
+            } catch (Exception e) {
+                Log.e(TAG, "Error Tethering: " + e.toString());
+                mLastError = ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+                transitionTo(mInitialState);
+                return;
+            }
+            if (DBG) Log.d(TAG, "Tethered " + mIfaceName);
+            mTetherController.notifyInterfaceStateChange(
+                    mIfaceName, TetherInterfaceStateMachine.this,
+                    IControlsTethering.STATE_TETHERED, mLastError);
+        }
+
+        @Override
+        public void exit() {
+            // Note that at this point, we're leaving the tethered state.  We can fail any
+            // of these operations, but it doesn't really change that we have to try them
+            // all in sequence.
+            cleanupUpstream();
+
+            try {
+                mNMService.untetherInterface(mIfaceName);
+            } catch (Exception ee) {
+                mLastError = ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
+                Log.e(TAG, "Failed to untether interface: " + ee.toString());
+            }
+
+            configureIfaceIp(false);
+        }
+
+        private void cleanupUpstream() {
+            if (mMyUpstreamIfaceName != null) {
+                // note that we don't care about errors here.
+                // sometimes interfaces are gone before we get
+                // to remove their rules, which generates errors.
+                // just do the best we can.
+                try {
+                    // about to tear down NAT; gather remaining statistics
+                    mStatsService.forceUpdate();
+                } catch (Exception e) {
+                    if (VDBG) Log.e(TAG, "Exception in forceUpdate: " + e.toString());
+                }
+                try {
+                    mNMService.stopInterfaceForwarding(mIfaceName, mMyUpstreamIfaceName);
+                } catch (Exception e) {
+                    if (VDBG) Log.e(
+                            TAG, "Exception in removeInterfaceForward: " + e.toString());
+                }
+                try {
+                    mNMService.disableNat(mIfaceName, mMyUpstreamIfaceName);
+                } catch (Exception e) {
+                    if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString());
+                }
+                mMyUpstreamIfaceName = null;
+            }
+            return;
+        }
+
+        @Override
+        public boolean processMessage(Message message) {
+            maybeLogMessage(this, message.what);
+            boolean retValue = true;
+            switch (message.what) {
+                case CMD_TETHER_UNREQUESTED:
+                    transitionTo(mInitialState);
+                    if (DBG) Log.d(TAG, "Untethered (unrequested)" + mIfaceName);
+                    break;
+                case CMD_INTERFACE_DOWN:
+                    transitionTo(mUnavailableState);
+                    if (DBG) Log.d(TAG, "Untethered (ifdown)" + mIfaceName);
+                    break;
+                case CMD_TETHER_CONNECTION_CHANGED:
+                    String newUpstreamIfaceName = (String)(message.obj);
+                    if ((mMyUpstreamIfaceName == null && newUpstreamIfaceName == null) ||
+                            (mMyUpstreamIfaceName != null &&
+                            mMyUpstreamIfaceName.equals(newUpstreamIfaceName))) {
+                        if (VDBG) Log.d(TAG, "Connection changed noop - dropping");
+                        break;
+                    }
+                    cleanupUpstream();
+                    if (newUpstreamIfaceName != null) {
+                        try {
+                            mNMService.enableNat(mIfaceName, newUpstreamIfaceName);
+                            mNMService.startInterfaceForwarding(mIfaceName,
+                                    newUpstreamIfaceName);
+                        } catch (Exception e) {
+                            Log.e(TAG, "Exception enabling Nat: " + e.toString());
+                            mLastError = ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
+                            transitionTo(mInitialState);
+                            return true;
+                        }
+                    }
+                    mMyUpstreamIfaceName = newUpstreamIfaceName;
+                    break;
+                case CMD_IP_FORWARDING_ENABLE_ERROR:
+                case CMD_IP_FORWARDING_DISABLE_ERROR:
+                case CMD_START_TETHERING_ERROR:
+                case CMD_STOP_TETHERING_ERROR:
+                case CMD_SET_DNS_FORWARDERS_ERROR:
+                    mLastError = ConnectivityManager.TETHER_ERROR_MASTER_ERROR;
+                    transitionTo(mInitialState);
+                    break;
+                default:
+                    retValue = false;
+                    break;
+            }
+            return retValue;
+        }
+    }
+
+    /**
+     * This state is terminal for the per interface state machine.  At this
+     * point, the master state machine should have removed this interface
+     * specific state machine from its list of possible recipients of
+     * tethering requests.  The state machine itself will hang around until
+     * the garbage collector finds it.
+     */
+    class UnavailableState extends State {
+        @Override
+        public void enter() {
+            mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+            mTetherController.notifyInterfaceStateChange(
+                    mIfaceName, TetherInterfaceStateMachine.this,
+                    IControlsTethering.STATE_UNAVAILABLE, mLastError);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/net/IpConfigStore.java b/services/core/java/com/android/server/net/IpConfigStore.java
index 2807ec8..4d56468 100644
--- a/services/core/java/com/android/server/net/IpConfigStore.java
+++ b/services/core/java/com/android/server/net/IpConfigStore.java
@@ -27,6 +27,7 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.net.DelayedDiskWrite;
 
 import java.io.BufferedInputStream;
@@ -34,7 +35,9 @@
 import java.io.DataOutputStream;
 import java.io.EOFException;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.io.InputStream;
 import java.net.InetAddress;
 import java.net.Inet4Address;
 
@@ -67,7 +70,8 @@
         this(new DelayedDiskWrite());
     }
 
-    private boolean writeConfig(DataOutputStream out, int configKey,
+    @VisibleForTesting
+    public static boolean writeConfig(DataOutputStream out, int configKey,
                                 IpConfiguration config) throws IOException {
         boolean written = false;
 
@@ -171,12 +175,25 @@
         });
     }
 
-    public SparseArray<IpConfiguration> readIpAndProxyConfigurations(String filePath) {
-        SparseArray<IpConfiguration> networks = new SparseArray<IpConfiguration>();
+    public static SparseArray<IpConfiguration> readIpAndProxyConfigurations(String filePath) {
+        BufferedInputStream bufferedInputStream;
+        try {
+            bufferedInputStream = new BufferedInputStream(new FileInputStream(filePath));
+        } catch (FileNotFoundException e) {
+            // Return an empty array here because callers expect an empty array when the file is
+            // not present.
+            loge("Error opening configuration file: " + e);
+            return new SparseArray<>();
+        }
+        return readIpAndProxyConfigurations(bufferedInputStream);
+    }
 
+    public static SparseArray<IpConfiguration> readIpAndProxyConfigurations(
+            InputStream inputStream) {
+        SparseArray<IpConfiguration> networks = new SparseArray<IpConfiguration>();
         DataInputStream in = null;
         try {
-            in = new DataInputStream(new BufferedInputStream(new FileInputStream(filePath)));
+            in = new DataInputStream(inputStream);
 
             int version = in.readInt();
             if (version != 2 && version != 1) {
@@ -327,11 +344,11 @@
         return networks;
     }
 
-    protected void loge(String s) {
+    protected static void loge(String s) {
         Log.e(TAG, s);
     }
 
-    protected void log(String s) {
+    protected static void log(String s) {
         Log.d(TAG, s);
     }
 }
diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk
index 0437e1d..d1bd505 100644
--- a/services/tests/servicestests/Android.mk
+++ b/services/tests/servicestests/Android.mk
@@ -12,6 +12,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
+    frameworks-base-testutils \
     services.core \
     services.devicepolicy \
     services.net \
diff --git a/services/tests/servicestests/src/android/net/IpUtilsTest.java b/services/tests/servicestests/src/android/net/util/IpUtilsTest.java
similarity index 100%
rename from services/tests/servicestests/src/android/net/IpUtilsTest.java
rename to services/tests/servicestests/src/android/net/util/IpUtilsTest.java
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
new file mode 100644
index 0000000..a30b362
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/connectivity/tethering/TetherInterfaceStateMachineTest.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.connectivity.tethering;
+
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import static android.net.ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
+import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
+import static android.net.ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+import static android.net.ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
+import static com.android.server.connectivity.tethering.IControlsTethering.STATE_AVAILABLE;
+import static com.android.server.connectivity.tethering.IControlsTethering.STATE_TETHERED;
+import static com.android.server.connectivity.tethering.IControlsTethering.STATE_UNAVAILABLE;
+
+import android.net.ConnectivityManager;
+import android.net.INetworkStatsService;
+import android.net.InterfaceConfiguration;
+import android.os.INetworkManagementService;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class TetherInterfaceStateMachineTest {
+    private static final String IFACE_NAME = "testnet1";
+    private static final String UPSTREAM_IFACE = "upstream0";
+    private static final String UPSTREAM_IFACE2 = "upstream1";
+
+    @Mock private INetworkManagementService mNMService;
+    @Mock private INetworkStatsService mStatsService;
+    @Mock private IControlsTethering mTetherHelper;
+    @Mock private InterfaceConfiguration mInterfaceConfiguration;
+
+    private final TestLooper mLooper = new TestLooper();
+    private TetherInterfaceStateMachine mTestedSm;
+
+    private void initStateMachine(int interfaceType) throws Exception {
+        mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(), interfaceType,
+                mNMService, mStatsService, mTetherHelper);
+        mTestedSm.start();
+        // Starting the state machine always puts us in a consistent state and notifies
+        // the test of the world that we've changed from an unknown to available state.
+        mLooper.dispatchAll();
+        reset(mNMService, mStatsService, mTetherHelper);
+        when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
+    }
+
+    private void initTetheredStateMachine(int interfaceType, String upstreamIface) throws Exception {
+        initStateMachine(interfaceType);
+        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
+        if (upstreamIface != null) {
+            dispatchTetherConnectionChanged(upstreamIface);
+        }
+        reset(mNMService, mStatsService, mTetherHelper);
+        when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
+    }
+
+    @Before public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void startsOutAvailable() {
+        mTestedSm = new TetherInterfaceStateMachine(IFACE_NAME, mLooper.getLooper(),
+                ConnectivityManager.TETHERING_BLUETOOTH, mNMService, mStatsService, mTetherHelper);
+        mTestedSm.start();
+        mLooper.dispatchAll();
+        verify(mTetherHelper).notifyInterfaceStateChange(
+                IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
+        verifyNoMoreInteractions(mTetherHelper, mNMService, mStatsService);
+    }
+
+    @Test
+    public void shouldDoNothingUntilRequested() throws Exception {
+        initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH);
+        final int [] NOOP_COMMANDS = {
+            TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED,
+            TetherInterfaceStateMachine.CMD_IP_FORWARDING_ENABLE_ERROR,
+            TetherInterfaceStateMachine.CMD_IP_FORWARDING_DISABLE_ERROR,
+            TetherInterfaceStateMachine.CMD_START_TETHERING_ERROR,
+            TetherInterfaceStateMachine.CMD_STOP_TETHERING_ERROR,
+            TetherInterfaceStateMachine.CMD_SET_DNS_FORWARDERS_ERROR,
+            TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED
+        };
+        for (int command : NOOP_COMMANDS) {
+            // None of these commands should trigger us to request action from
+            // the rest of the system.
+            dispatchCommand(command);
+            verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+        }
+    }
+
+    @Test
+    public void handlesImmediateInterfaceDown() throws Exception {
+        initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH);
+
+        dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
+        verify(mTetherHelper).notifyInterfaceStateChange(
+                IFACE_NAME, mTestedSm, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
+        verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+    }
+
+    @Test
+    public void canBeTethered() throws Exception {
+        initStateMachine(ConnectivityManager.TETHERING_BLUETOOTH);
+
+        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
+        InOrder inOrder = inOrder(mTetherHelper, mNMService);
+        inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
+        inOrder.verify(mTetherHelper).notifyInterfaceStateChange(
+                IFACE_NAME, mTestedSm, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
+        verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+    }
+
+    @Test
+    public void canUnrequestTethering() throws Exception {
+        initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, null);
+
+        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
+        InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper);
+        inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
+        inOrder.verify(mTetherHelper).notifyInterfaceStateChange(
+                IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
+        verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+    }
+
+    @Test
+    public void canBeTetheredAsUsb() throws Exception {
+        initStateMachine(ConnectivityManager.TETHERING_USB);
+
+        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
+        InOrder inOrder = inOrder(mTetherHelper, mNMService);
+        inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME);
+        inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
+        inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
+        inOrder.verify(mTetherHelper).notifyInterfaceStateChange(
+                IFACE_NAME, mTestedSm, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
+        verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+    }
+
+    @Test
+    public void handlesFirstUpstreamChange() throws Exception {
+        initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, null);
+
+        // Telling the state machine about its upstream interface triggers a little more configuration.
+        dispatchTetherConnectionChanged(UPSTREAM_IFACE);
+        InOrder inOrder = inOrder(mNMService);
+        inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
+        verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+    }
+
+    @Test
+    public void handlesChangingUpstream() throws Exception {
+        initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, UPSTREAM_IFACE);
+
+        dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
+        InOrder inOrder = inOrder(mNMService, mStatsService);
+        inOrder.verify(mStatsService).forceUpdate();
+        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
+        inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
+        verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+    }
+
+    @Test
+    public void canUnrequestTetheringWithUpstream() throws Exception {
+        initTetheredStateMachine(ConnectivityManager.TETHERING_BLUETOOTH, UPSTREAM_IFACE);
+
+        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_UNREQUESTED);
+        InOrder inOrder = inOrder(mNMService, mStatsService, mTetherHelper);
+        inOrder.verify(mStatsService).forceUpdate();
+        inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
+        inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
+        inOrder.verify(mTetherHelper).notifyInterfaceStateChange(
+                IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
+        verifyNoMoreInteractions(mNMService, mStatsService, mTetherHelper);
+    }
+
+    @Test
+    public void interfaceDownLeadsToUnavailable() throws Exception {
+        for (boolean shouldThrow : new boolean[]{true, false}) {
+            initTetheredStateMachine(ConnectivityManager.TETHERING_USB, null);
+
+            if (shouldThrow) {
+                doThrow(RemoteException.class).when(mNMService).untetherInterface(IFACE_NAME);
+            }
+            dispatchCommand(TetherInterfaceStateMachine.CMD_INTERFACE_DOWN);
+            InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mTetherHelper);
+            usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
+            usbTeardownOrder.verify(mNMService).setInterfaceConfig(
+                    IFACE_NAME, mInterfaceConfiguration);
+            usbTeardownOrder.verify(mTetherHelper).notifyInterfaceStateChange(
+                    IFACE_NAME, mTestedSm, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
+        }
+    }
+
+    @Test
+    public void usbShouldBeTornDownOnTetherError() throws Exception {
+        initStateMachine(ConnectivityManager.TETHERING_USB);
+
+        doThrow(RemoteException.class).when(mNMService).tetherInterface(IFACE_NAME);
+        dispatchCommand(TetherInterfaceStateMachine.CMD_TETHER_REQUESTED);
+        InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mTetherHelper);
+        usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
+        usbTeardownOrder.verify(mNMService).setInterfaceConfig(
+                IFACE_NAME, mInterfaceConfiguration);
+        usbTeardownOrder.verify(mTetherHelper).notifyInterfaceStateChange(
+                IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_TETHER_IFACE_ERROR);
+    }
+
+    @Test
+    public void shouldTearDownUsbOnUpstreamError() throws Exception {
+        initTetheredStateMachine(ConnectivityManager.TETHERING_USB, null);
+
+        doThrow(RemoteException.class).when(mNMService).enableNat(anyString(), anyString());
+        dispatchTetherConnectionChanged(UPSTREAM_IFACE);
+        InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mTetherHelper);
+        usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
+        usbTeardownOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
+        usbTeardownOrder.verify(mTetherHelper).notifyInterfaceStateChange(
+                IFACE_NAME, mTestedSm, STATE_AVAILABLE, TETHER_ERROR_ENABLE_NAT_ERROR);
+    }
+
+    /**
+     * Send a command to the state machine under test, and run the event loop to idle.
+     *
+     * @param command One of the TetherInterfaceStateMachine.CMD_* constants.
+     */
+    private void dispatchCommand(int command) {
+        mTestedSm.sendMessage(command);
+        mLooper.dispatchAll();
+    }
+
+    /**
+     * Special override to tell the state machine that the upstream interface has changed.
+     *
+     * @see #dispatchCommand(int)
+     * @param upstreamIface String name of upstream interface (or null)
+     */
+    private void dispatchTetherConnectionChanged(String upstreamIface) {
+        mTestedSm.sendMessage(TetherInterfaceStateMachine.CMD_TETHER_CONNECTION_CHANGED,
+                upstreamIface);
+        mLooper.dispatchAll();
+    }
+}
\ No newline at end of file
diff --git a/tests/utils/testutils/Android.mk b/tests/utils/testutils/Android.mk
new file mode 100644
index 0000000..4bfd234
--- /dev/null
+++ b/tests/utils/testutils/Android.mk
@@ -0,0 +1,30 @@
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := frameworks-base-testutils
+LOCAL_MODULE_TAG := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under,java)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    mockito-target-minus-junit4
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/utils/testutils/java/android/app/test/MockAnswerUtil.java b/tests/utils/testutils/java/android/app/test/MockAnswerUtil.java
new file mode 100644
index 0000000..746c77d
--- /dev/null
+++ b/tests/utils/testutils/java/android/app/test/MockAnswerUtil.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.test;
+
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+/**
+ * Utilities for creating Answers for mock objects
+ */
+public class MockAnswerUtil {
+
+    /**
+     * Answer that calls the method in the Answer called "answer" that matches the type signature of
+     * the method being answered. An error will be thrown at runtime if the signature does not match
+     * exactly.
+     */
+    public static class AnswerWithArguments implements Answer<Object> {
+        @Override
+        public final Object answer(InvocationOnMock invocation) throws Throwable {
+            Method method = invocation.getMethod();
+            try {
+                Method implementation = getClass().getMethod("answer", method.getParameterTypes());
+                if (!implementation.getReturnType().equals(method.getReturnType())) {
+                    throw new RuntimeException("Found answer method does not have expected return "
+                            + "type. Expected: " + method.getReturnType() + ", got "
+                            + implementation.getReturnType());
+                }
+                Object[] args = invocation.getArguments();
+                try {
+                    return implementation.invoke(this, args);
+                } catch (IllegalAccessException e) {
+                    throw new RuntimeException("Error invoking answer method", e);
+                } catch (InvocationTargetException e) {
+                    throw e.getCause();
+                }
+            } catch (NoSuchMethodException e) {
+                throw new RuntimeException("Could not find answer method with the expected args "
+                        + Arrays.toString(method.getParameterTypes()), e);
+            }
+        }
+    }
+
+}
diff --git a/tests/utils/testutils/java/android/app/test/TestAlarmManager.java b/tests/utils/testutils/java/android/app/test/TestAlarmManager.java
new file mode 100644
index 0000000..e90ea1e
--- /dev/null
+++ b/tests/utils/testutils/java/android/app/test/TestAlarmManager.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.test;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+
+import android.app.AlarmManager;
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
+import android.os.Handler;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Creates an AlarmManager whose alarm dispatch can be controlled
+ * Currently only supports alarm listeners
+ *
+ * Alarm listeners will be dispatched to the handler provided or will
+ * be dispatched immediately if they would have been sent to the main
+ * looper (handler was null).
+ */
+public class TestAlarmManager {
+    private final AlarmManager mAlarmManager;
+    private final List<PendingAlarm> mPendingAlarms;
+
+    public TestAlarmManager() throws Exception {
+        mPendingAlarms = new ArrayList<>();
+
+        mAlarmManager = mock(AlarmManager.class);
+        doAnswer(new SetListenerAnswer()).when(mAlarmManager).set(anyInt(), anyLong(), anyString(),
+                any(AlarmManager.OnAlarmListener.class), any(Handler.class));
+        doAnswer(new SetListenerAnswer()).when(mAlarmManager).setExact(anyInt(), anyLong(),
+                anyString(), any(AlarmManager.OnAlarmListener.class), any(Handler.class));
+        doAnswer(new CancelListenerAnswer())
+                .when(mAlarmManager).cancel(any(AlarmManager.OnAlarmListener.class));
+    }
+
+    public AlarmManager getAlarmManager() {
+        return mAlarmManager;
+    }
+
+    /**
+     * Dispatch a pending alarm with the given tag
+     * @return if any alarm was dispatched
+     */
+    public boolean dispatch(String tag) {
+        for (int i = 0; i < mPendingAlarms.size(); ++i) {
+            PendingAlarm alarm = mPendingAlarms.get(i);
+            if (Objects.equals(tag, alarm.getTag())) {
+                mPendingAlarms.remove(i);
+                alarm.dispatch();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @return if an alarm with the given tag is pending
+     */
+    public boolean isPending(String tag) {
+        for (int i = 0; i < mPendingAlarms.size(); ++i) {
+            PendingAlarm alarm = mPendingAlarms.get(i);
+            if (Objects.equals(tag, alarm.getTag())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * @return trigger time of an pending alarm with the given tag
+     *         -1 if no pending alarm with the given tag
+     */
+    public long getTriggerTimeMillis(String tag) {
+        for (int i = 0; i < mPendingAlarms.size(); ++i) {
+            PendingAlarm alarm = mPendingAlarms.get(i);
+            if (Objects.equals(tag, alarm.getTag())) {
+                return alarm.getTriggerTimeMillis();
+            }
+        }
+        return -1;
+    }
+
+    private static class PendingAlarm {
+        private final int mType;
+        private final long mTriggerAtMillis;
+        private final String mTag;
+        private final Runnable mCallback;
+
+        public PendingAlarm(int type, long triggerAtMillis, String tag, Runnable callback) {
+            mType = type;
+            mTriggerAtMillis = triggerAtMillis;
+            mTag = tag;
+            mCallback = callback;
+        }
+
+        public void dispatch() {
+            if (mCallback != null) {
+                mCallback.run();
+            }
+        }
+
+        public Runnable getCallback() {
+            return mCallback;
+        }
+
+        public String getTag() {
+            return mTag;
+        }
+
+        public long getTriggerTimeMillis() {
+            return mTriggerAtMillis;
+        }
+    }
+
+    private class SetListenerAnswer extends AnswerWithArguments {
+        public void answer(int type, long triggerAtMillis, String tag,
+                AlarmManager.OnAlarmListener listener, Handler handler) {
+            mPendingAlarms.add(new PendingAlarm(type, triggerAtMillis, tag,
+                            new AlarmListenerRunnable(listener, handler)));
+        }
+    }
+
+    private class CancelListenerAnswer extends AnswerWithArguments {
+        public void answer(AlarmManager.OnAlarmListener listener) {
+            Iterator<PendingAlarm> alarmItr = mPendingAlarms.iterator();
+            while (alarmItr.hasNext()) {
+                PendingAlarm alarm = alarmItr.next();
+                if (alarm.getCallback() instanceof AlarmListenerRunnable) {
+                    AlarmListenerRunnable alarmCallback =
+                            (AlarmListenerRunnable) alarm.getCallback();
+                    if (alarmCallback.getListener() == listener) {
+                        alarmItr.remove();
+                    }
+                }
+            }
+        }
+    }
+
+    private static class AlarmListenerRunnable implements Runnable {
+        private final AlarmManager.OnAlarmListener mListener;
+        private final Handler mHandler;
+        public AlarmListenerRunnable(AlarmManager.OnAlarmListener listener, Handler handler) {
+            mListener = listener;
+            mHandler = handler;
+        }
+
+        public AlarmManager.OnAlarmListener getListener() {
+            return mListener;
+        }
+
+        @Override
+        public void run() {
+            if (mHandler != null) {
+                mHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            mListener.onAlarm();
+                        }
+                    });
+            } else { // normally gets dispatched in main looper
+                mListener.onAlarm();
+            }
+        }
+    }
+}
diff --git a/tests/utils/testutils/java/android/os/test/TestLooper.java b/tests/utils/testutils/java/android/os/test/TestLooper.java
new file mode 100644
index 0000000..e8ceb4a
--- /dev/null
+++ b/tests/utils/testutils/java/android/os/test/TestLooper.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.test;
+
+import static org.junit.Assert.assertTrue;
+
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+import android.os.SystemClock;
+import android.util.Log;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Creates a looper whose message queue can be manipulated
+ * This allows testing code that uses a looper to dispatch messages in a deterministic manner
+ * Creating a TestLooper will also install it as the looper for the current thread
+ */
+public class TestLooper {
+    protected final Looper mLooper;
+
+    private static final Constructor<Looper> LOOPER_CONSTRUCTOR;
+    private static final Field THREAD_LOCAL_LOOPER_FIELD;
+    private static final Field MESSAGE_QUEUE_MESSAGES_FIELD;
+    private static final Field MESSAGE_NEXT_FIELD;
+    private static final Field MESSAGE_WHEN_FIELD;
+    private static final Method MESSAGE_MARK_IN_USE_METHOD;
+    private static final String TAG = "TestLooper";
+
+    private AutoDispatchThread mAutoDispatchThread;
+
+    static {
+        try {
+            LOOPER_CONSTRUCTOR = Looper.class.getDeclaredConstructor(Boolean.TYPE);
+            LOOPER_CONSTRUCTOR.setAccessible(true);
+            THREAD_LOCAL_LOOPER_FIELD = Looper.class.getDeclaredField("sThreadLocal");
+            THREAD_LOCAL_LOOPER_FIELD.setAccessible(true);
+            MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
+            MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
+            MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
+            MESSAGE_NEXT_FIELD.setAccessible(true);
+            MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when");
+            MESSAGE_WHEN_FIELD.setAccessible(true);
+            MESSAGE_MARK_IN_USE_METHOD = Message.class.getDeclaredMethod("markInUse");
+            MESSAGE_MARK_IN_USE_METHOD.setAccessible(true);
+        } catch (NoSuchFieldException | NoSuchMethodException e) {
+            throw new RuntimeException("Failed to initialize TestLooper", e);
+        }
+    }
+
+
+    public TestLooper() {
+        try {
+            mLooper = LOOPER_CONSTRUCTOR.newInstance(false);
+
+            ThreadLocal<Looper> threadLocalLooper = (ThreadLocal<Looper>) THREAD_LOCAL_LOOPER_FIELD
+                    .get(null);
+            threadLocalLooper.set(mLooper);
+        } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
+            throw new RuntimeException("Reflection error constructing or accessing looper", e);
+        }
+    }
+
+    public Looper getLooper() {
+        return mLooper;
+    }
+
+    private Message getMessageLinkedList() {
+        try {
+            MessageQueue queue = mLooper.getQueue();
+            return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException("Access failed in TestLooper: get - MessageQueue.mMessages",
+                    e);
+        }
+    }
+
+    public void moveTimeForward(long milliSeconds) {
+        try {
+            Message msg = getMessageLinkedList();
+            while (msg != null) {
+                long updatedWhen = msg.getWhen() - milliSeconds;
+                if (updatedWhen < 0) {
+                    updatedWhen = 0;
+                }
+                MESSAGE_WHEN_FIELD.set(msg, updatedWhen);
+                msg = (Message) MESSAGE_NEXT_FIELD.get(msg);
+            }
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException("Access failed in TestLooper: set - Message.when", e);
+        }
+    }
+
+    private Message messageQueueNext() {
+        try {
+            long now = SystemClock.uptimeMillis();
+
+            Message prevMsg = null;
+            Message msg = getMessageLinkedList();
+            if (msg != null && msg.getTarget() == null) {
+                // Stalled by a barrier. Find the next asynchronous message in
+                // the queue.
+                do {
+                    prevMsg = msg;
+                    msg = (Message) MESSAGE_NEXT_FIELD.get(msg);
+                } while (msg != null && !msg.isAsynchronous());
+            }
+            if (msg != null) {
+                if (now >= msg.getWhen()) {
+                    // Got a message.
+                    if (prevMsg != null) {
+                        MESSAGE_NEXT_FIELD.set(prevMsg, MESSAGE_NEXT_FIELD.get(msg));
+                    } else {
+                        MESSAGE_QUEUE_MESSAGES_FIELD.set(mLooper.getQueue(),
+                                MESSAGE_NEXT_FIELD.get(msg));
+                    }
+                    MESSAGE_NEXT_FIELD.set(msg, null);
+                    MESSAGE_MARK_IN_USE_METHOD.invoke(msg);
+                    return msg;
+                }
+            }
+        } catch (IllegalAccessException | InvocationTargetException e) {
+            throw new RuntimeException("Access failed in TestLooper", e);
+        }
+
+        return null;
+    }
+
+    /**
+     * @return true if there are pending messages in the message queue
+     */
+    public synchronized boolean isIdle() {
+        Message messageList = getMessageLinkedList();
+
+        return messageList != null && SystemClock.uptimeMillis() >= messageList.getWhen();
+    }
+
+    /**
+     * @return the next message in the Looper's message queue or null if there is none
+     */
+    public synchronized Message nextMessage() {
+        if (isIdle()) {
+            return messageQueueNext();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Dispatch the next message in the queue
+     * Asserts that there is a message in the queue
+     */
+    public synchronized void dispatchNext() {
+        assertTrue(isIdle());
+        Message msg = messageQueueNext();
+        if (msg == null) {
+            return;
+        }
+        msg.getTarget().dispatchMessage(msg);
+    }
+
+    /**
+     * Dispatch all messages currently in the queue
+     * Will not fail if there are no messages pending
+     * @return the number of messages dispatched
+     */
+    public synchronized int dispatchAll() {
+        int count = 0;
+        while (isIdle()) {
+            dispatchNext();
+            ++count;
+        }
+        return count;
+    }
+
+    /**
+     * Thread used to dispatch messages when the main thread is blocked waiting for a response.
+     */
+    private class AutoDispatchThread extends Thread {
+        private static final int MAX_LOOPS = 100;
+        private static final int LOOP_SLEEP_TIME_MS = 10;
+
+        private RuntimeException mAutoDispatchException = null;
+
+        /**
+         * Run method for the auto dispatch thread.
+         * The thread loops a maximum of MAX_LOOPS times with a 10ms sleep between loops.
+         * The thread continues looping and attempting to dispatch all messages until at
+         * least one message has been dispatched.
+         */
+        @Override
+        public void run() {
+            int dispatchCount = 0;
+            for (int i = 0; i < MAX_LOOPS; i++) {
+                try {
+                    dispatchCount = dispatchAll();
+                } catch (RuntimeException e) {
+                    mAutoDispatchException = e;
+                }
+                Log.d(TAG, "dispatched " + dispatchCount + " messages");
+                if (dispatchCount > 0) {
+                    return;
+                }
+                try {
+                    Thread.sleep(LOOP_SLEEP_TIME_MS);
+                } catch (InterruptedException e) {
+                    mAutoDispatchException = new IllegalStateException(
+                            "stopAutoDispatch called before any messages were dispatched.");
+                    return;
+                }
+            }
+            Log.e(TAG, "AutoDispatchThread did not dispatch any messages.");
+            mAutoDispatchException = new IllegalStateException(
+                    "TestLooper did not dispatch any messages before exiting.");
+        }
+
+        /**
+         * Method allowing the TestLooper to pass any exceptions thrown by the thread to be passed
+         * to the main thread.
+         *
+         * @return RuntimeException Exception created by stopping without dispatching a message
+         */
+        public RuntimeException getException() {
+            return mAutoDispatchException;
+        }
+    }
+
+    /**
+     * Create and start a new AutoDispatchThread if one is not already running.
+     */
+    public void startAutoDispatch() {
+        if (mAutoDispatchThread != null) {
+            throw new IllegalStateException(
+                    "startAutoDispatch called with the AutoDispatchThread already running.");
+        }
+        mAutoDispatchThread = new AutoDispatchThread();
+        mAutoDispatchThread.start();
+    }
+
+    /**
+     * If an AutoDispatchThread is currently running, stop and clean up.
+     */
+    public void stopAutoDispatch() {
+        if (mAutoDispatchThread != null) {
+            if (mAutoDispatchThread.isAlive()) {
+                mAutoDispatchThread.interrupt();
+            }
+            try {
+                mAutoDispatchThread.join();
+            } catch (InterruptedException e) {
+                // Catch exception from join.
+            }
+
+            RuntimeException e = mAutoDispatchThread.getException();
+            mAutoDispatchThread = null;
+            if (e != null) {
+                throw e;
+            }
+        } else {
+            // stopAutoDispatch was called when startAutoDispatch has not created a new thread.
+            throw new IllegalStateException(
+                    "stopAutoDispatch called without startAutoDispatch.");
+        }
+    }
+}
diff --git a/tests/utils/testutils/java/android/os/test/TestLooperTest.java b/tests/utils/testutils/java/android/os/test/TestLooperTest.java
new file mode 100644
index 0000000..40d83b5
--- /dev/null
+++ b/tests/utils/testutils/java/android/os/test/TestLooperTest.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os.test;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ErrorCollector;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Test TestLooperAbstractTime which provides control over "time". Note that
+ * real-time is being used as well. Therefore small time increments are NOT
+ * reliable. All tests are in "K" units (i.e. *1000).
+ */
+
+@SmallTest
+public class TestLooperTest {
+    private TestLooper mTestLooper;
+    private Handler mHandler;
+    private Handler mHandlerSpy;
+
+    @Rule
+    public ErrorCollector collector = new ErrorCollector();
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mTestLooper = new TestLooper();
+        mHandler = new Handler(mTestLooper.getLooper());
+        mHandlerSpy = spy(mHandler);
+    }
+
+    /**
+     * Basic test with no time stamps: dispatch 4 messages, check that all 4
+     * delivered (in correct order).
+     */
+    @Test
+    public void testNoTimeMovement() {
+        final int messageA = 1;
+        final int messageB = 2;
+        final int messageC = 3;
+
+        InOrder inOrder = inOrder(mHandlerSpy);
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageC));
+        mTestLooper.dispatchAll();
+
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("2: messageA", messageA, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("3: messageB", messageB, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("4: messageC", messageC, equalTo(messageCaptor.getValue().what));
+
+        inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
+    }
+
+    /**
+     * Test message sequence: A, B, C@5K, A@10K. Don't move time.
+     * <p>
+     * Expected: only get A, B
+     */
+    @Test
+    public void testDelayedDispatchNoTimeMove() {
+        final int messageA = 1;
+        final int messageB = 2;
+        final int messageC = 3;
+
+        InOrder inOrder = inOrder(mHandlerSpy);
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
+        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
+        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 10000);
+        mTestLooper.dispatchAll();
+
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
+
+        inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
+    }
+
+    /**
+     * Test message sequence: A, B, C@5K, A@10K, Advance time by 5K.
+     * <p>
+     * Expected: only get A, B, C
+     */
+    @Test
+    public void testDelayedDispatchAdvanceTimeOnce() {
+        final int messageA = 1;
+        final int messageB = 2;
+        final int messageC = 3;
+
+        InOrder inOrder = inOrder(mHandlerSpy);
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
+        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
+        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 10000);
+        mTestLooper.moveTimeForward(5000);
+        mTestLooper.dispatchAll();
+
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what));
+
+        inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
+    }
+
+    /**
+     * Test message sequence: A, B, C@5K, Advance time by 4K, A@1K, B@2K Advance
+     * time by 1K.
+     * <p>
+     * Expected: get A, B, C, A
+     */
+    @Test
+    public void testDelayedDispatchAdvanceTimeTwice() {
+        final int messageA = 1;
+        final int messageB = 2;
+        final int messageC = 3;
+
+        InOrder inOrder = inOrder(mHandlerSpy);
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
+        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
+        mTestLooper.moveTimeForward(4000);
+        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 1000);
+        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageB), 2000);
+        mTestLooper.moveTimeForward(1000);
+        mTestLooper.dispatchAll();
+
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("4: messageA", messageA, equalTo(messageCaptor.getValue().what));
+
+        inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
+    }
+
+    /**
+     * Test message sequence: A, B, C@5K, Advance time by 4K, A@5K, B@2K Advance
+     * time by 3K.
+     * <p>
+     * Expected: get A, B, C, B
+     */
+    @Test
+    public void testDelayedDispatchReverseOrder() {
+        final int messageA = 1;
+        final int messageB = 2;
+        final int messageC = 3;
+
+        InOrder inOrder = inOrder(mHandlerSpy);
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
+        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
+        mTestLooper.moveTimeForward(4000);
+        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 5000);
+        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageB), 2000);
+        mTestLooper.moveTimeForward(3000);
+        mTestLooper.dispatchAll();
+
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("4: messageB", messageB, equalTo(messageCaptor.getValue().what));
+
+        inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
+    }
+
+    /**
+     * Test message sequence: A, B, C@5K, Advance time by 4K, dispatch all,
+     * A@5K, B@2K Advance time by 3K, dispatch all.
+     * <p>
+     * Expected: get A, B after first dispatch; then C, B after second dispatch
+     */
+    @Test
+    public void testDelayedDispatchAllMultipleTimes() {
+        final int messageA = 1;
+        final int messageB = 2;
+        final int messageC = 3;
+
+        InOrder inOrder = inOrder(mHandlerSpy);
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageB));
+        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageC), 5000);
+        mTestLooper.moveTimeForward(4000);
+        mTestLooper.dispatchAll();
+
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("1: messageA", messageA, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("2: messageB", messageB, equalTo(messageCaptor.getValue().what));
+
+        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageA), 5000);
+        mHandlerSpy.sendMessageDelayed(mHandler.obtainMessage(messageB), 2000);
+        mTestLooper.moveTimeForward(3000);
+        mTestLooper.dispatchAll();
+
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("3: messageC", messageC, equalTo(messageCaptor.getValue().what));
+        inOrder.verify(mHandlerSpy).handleMessage(messageCaptor.capture());
+        collector.checkThat("4: messageB", messageB, equalTo(messageCaptor.getValue().what));
+
+        inOrder.verify(mHandlerSpy, never()).handleMessage(any(Message.class));
+    }
+
+    /**
+     * Test AutoDispatch for a single message.
+     * This test would ideally use the Channel sendMessageSynchronously.  At this time, the setup to
+     * get a working test channel is cumbersome.  Until this is fixed, we substitute with a
+     * sendMessage followed by a blocking call.  The main test thread blocks until the test handler
+     * receives the test message (messageA) and sets a boolean true.  Once the boolean is true, the
+     * main thread will exit the busy wait loop, stop autoDispatch and check the assert.
+     *
+     * Enable AutoDispatch, add message, block on message being handled and stop AutoDispatch.
+     * <p>
+     * Expected: handleMessage is called for messageA and stopAutoDispatch is called.
+     */
+    @Test
+    public void testAutoDispatchWithSingleMessage() {
+        final int mLoopSleepTimeMs = 5;
+
+        final int messageA = 1;
+
+        TestLooper mockLooper = new TestLooper();
+        class TestHandler extends Handler {
+            public volatile boolean handledMessage = false;
+            TestHandler(Looper looper) {
+                super(looper);
+            }
+
+            @Override
+            public void handleMessage(Message msg) {
+                if (msg.what == messageA) {
+                    handledMessage = true;
+                }
+            }
+        }
+
+        TestHandler testHandler = new TestHandler(mockLooper.getLooper());
+        mockLooper.startAutoDispatch();
+        testHandler.sendMessage(testHandler.obtainMessage(messageA));
+        while (!testHandler.handledMessage) {
+            // Block until message is handled
+            try {
+                Thread.sleep(mLoopSleepTimeMs);
+            } catch (InterruptedException e) {
+                // Interrupted while sleeping.
+            }
+        }
+        mockLooper.stopAutoDispatch();
+        assertTrue("TestHandler should have received messageA", testHandler.handledMessage);
+    }
+
+    /**
+     * Test starting AutoDispatch while already running throws IllegalStateException
+     * Enable AutoDispatch two times in a row.
+     * <p>
+     * Expected: catch IllegalStateException on second call.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testRepeatedStartAutoDispatchThrowsException() {
+        mTestLooper.startAutoDispatch();
+        mTestLooper.startAutoDispatch();
+    }
+
+    /**
+     * Test stopping AutoDispatch without previously starting throws IllegalStateException
+     * Stop AutoDispatch
+     * <p>
+     * Expected: catch IllegalStateException on second call.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testStopAutoDispatchWithoutStartThrowsException() {
+        mTestLooper.stopAutoDispatch();
+    }
+
+    /**
+     * Test AutoDispatch exits and does not dispatch a later message.
+     * Start and stop AutoDispatch then add a message.
+     * <p>
+     * Expected: After AutoDispatch is stopped, dispatchAll will return 1.
+     */
+    @Test
+    public void testAutoDispatchStopsCleanlyWithoutDispatchingAMessage() {
+        final int messageA = 1;
+
+        InOrder inOrder = inOrder(mHandlerSpy);
+        ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
+
+        mTestLooper.startAutoDispatch();
+        try {
+            mTestLooper.stopAutoDispatch();
+        } catch (IllegalStateException e) {
+            //  Stopping without a dispatch will throw an exception.
+        }
+
+        mHandlerSpy.sendMessage(mHandler.obtainMessage(messageA));
+        assertEquals("One message should be dispatched", 1, mTestLooper.dispatchAll());
+    }
+
+    /**
+     * Test AutoDispatch throws an exception when no messages are dispatched.
+     * Start and stop AutoDispatch
+     * <p>
+     * Expected: Exception is thrown with the stopAutoDispatch call.
+     */
+    @Test(expected = IllegalStateException.class)
+    public void testAutoDispatchThrowsExceptionWhenNoMessagesDispatched() {
+        mTestLooper.startAutoDispatch();
+        mTestLooper.stopAutoDispatch();
+    }
+}
diff --git a/tests/utils/testutils/java/com/android/internal/util/test/BidirectionalAsyncChannel.java b/tests/utils/testutils/java/com/android/internal/util/test/BidirectionalAsyncChannel.java
new file mode 100644
index 0000000..25cd5b9
--- /dev/null
+++ b/tests/utils/testutils/java/com/android/internal/util/test/BidirectionalAsyncChannel.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.util.test;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.util.Log;
+
+import com.android.internal.util.AsyncChannel;
+
+
+/**
+ * Provides an AsyncChannel interface that implements the connection initiating half of a
+ * bidirectional channel as described in {@link com.android.internal.util.AsyncChannel}.
+ */
+public class BidirectionalAsyncChannel {
+    private static final String TAG = "BidirectionalAsyncChannel";
+
+    private AsyncChannel mChannel;
+    public enum ChannelState { DISCONNECTED, HALF_CONNECTED, CONNECTED, FAILURE };
+    private ChannelState mState = ChannelState.DISCONNECTED;
+
+    public void assertConnected() {
+        assertEquals("AsyncChannel was not fully connected", ChannelState.CONNECTED, mState);
+    }
+
+    public void connect(final Looper looper, final Messenger messenger,
+            final Handler incomingMessageHandler) {
+        assertEquals("AsyncChannel must be disconnected to connect",
+                ChannelState.DISCONNECTED, mState);
+        mChannel = new AsyncChannel();
+        Handler rawMessageHandler = new Handler(looper) {
+                @Override
+                public void handleMessage(Message msg) {
+                    switch (msg.what) {
+                    case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
+                        if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
+                            Log.d(TAG, "Successfully half connected " + this);
+                            mChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
+                            mState = ChannelState.HALF_CONNECTED;
+                        } else {
+                            Log.d(TAG, "Failed to connect channel " + this);
+                            mState = ChannelState.FAILURE;
+                            mChannel = null;
+                        }
+                        break;
+                    case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
+                        mState = ChannelState.CONNECTED;
+                        Log.d(TAG, "Channel fully connected" + this);
+                        break;
+                    case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+                        mState = ChannelState.DISCONNECTED;
+                        mChannel = null;
+                        Log.d(TAG, "Channel disconnected" + this);
+                        break;
+                    default:
+                        incomingMessageHandler.handleMessage(msg);
+                        break;
+                    }
+                }
+            };
+        mChannel.connect(null, rawMessageHandler, messenger);
+    }
+
+    public void disconnect() {
+        assertEquals("AsyncChannel must be connected to disconnect",
+                ChannelState.CONNECTED, mState);
+        mChannel.sendMessage(AsyncChannel.CMD_CHANNEL_DISCONNECT);
+        mState = ChannelState.DISCONNECTED;
+        mChannel = null;
+    }
+
+    public void sendMessage(Message msg) {
+        assertEquals("AsyncChannel must be connected to send messages",
+                ChannelState.CONNECTED, mState);
+        mChannel.sendMessage(msg);
+    }
+}
diff --git a/tests/utils/testutils/java/com/android/internal/util/test/BidirectionalAsyncChannelServer.java b/tests/utils/testutils/java/com/android/internal/util/test/BidirectionalAsyncChannelServer.java
new file mode 100644
index 0000000..49c8332
--- /dev/null
+++ b/tests/utils/testutils/java/com/android/internal/util/test/BidirectionalAsyncChannelServer.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util.test;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.util.Log;
+
+import com.android.internal.util.AsyncChannel;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Provides an interface for the server side implementation of a bidirectional channel as described
+ * in {@link com.android.internal.util.AsyncChannel}.
+ */
+public class BidirectionalAsyncChannelServer {
+
+    private static final String TAG = "BidirectionalAsyncChannelServer";
+
+    // Keeps track of incoming clients, which are identifiable by their messengers.
+    private final Map<Messenger, AsyncChannel> mClients = new HashMap<>();
+
+    private Messenger mMessenger;
+
+    public BidirectionalAsyncChannelServer(final Context context, final Looper looper,
+            final Handler messageHandler) {
+        Handler handler = new Handler(looper) {
+            @Override
+            public void handleMessage(Message msg) {
+                AsyncChannel channel = mClients.get(msg.replyTo);
+                switch (msg.what) {
+                    case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION:
+                        if (channel != null) {
+                            Log.d(TAG, "duplicate client connection: " + msg.sendingUid);
+                            channel.replyToMessage(msg,
+                                    AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
+                                    AsyncChannel.STATUS_FULL_CONNECTION_REFUSED_ALREADY_CONNECTED);
+                        } else {
+                            channel = new AsyncChannel();
+                            mClients.put(msg.replyTo, channel);
+                            channel.connected(context, this, msg.replyTo);
+                            channel.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
+                                    AsyncChannel.STATUS_SUCCESSFUL);
+                        }
+                        break;
+                    case AsyncChannel.CMD_CHANNEL_DISCONNECT:
+                        channel.disconnect();
+                        break;
+
+                    case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
+                        mClients.remove(msg.replyTo);
+                        break;
+
+                    default:
+                        messageHandler.handleMessage(msg);
+                        break;
+                }
+            }
+        };
+        mMessenger = new Messenger(handler);
+    }
+
+    public Messenger getMessenger() {
+        return mMessenger;
+    }
+
+}
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 9268a2b..b62ffac 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -89,7 +89,7 @@
 
     WifiInfo getConnectionInfo();
 
-    boolean setWifiEnabled(boolean enable);
+    boolean setWifiEnabled(String packageName, boolean enable);
 
     int getWifiEnabledState();
 
@@ -169,5 +169,11 @@
     void factoryReset();
 
     Network getCurrentNetwork();
+
+    byte[] retrieveBackupData();
+
+    void restoreBackupData(in byte[] data);
+
+    void restoreSupplicantBackupData(in byte[] supplicantData, in byte[] ipConfigData);
 }
 
diff --git a/wifi/java/android/net/wifi/RttManager.aidl b/wifi/java/android/net/wifi/RttManager.aidl
index 5c6d447..9479cf0 100644
--- a/wifi/java/android/net/wifi/RttManager.aidl
+++ b/wifi/java/android/net/wifi/RttManager.aidl
@@ -15,4 +15,7 @@
  */
 
 package android.net.wifi;
-parcelable RttManager.RttCapabilities;
\ No newline at end of file
+
+parcelable RttManager.RttCapabilities;
+parcelable RttManager.ParcelableRttResults;
+parcelable RttManager.ParcelableRttParams;
diff --git a/wifi/java/android/net/wifi/RttManager.java b/wifi/java/android/net/wifi/RttManager.java
index 590ff1b..c6f1f68 100644
--- a/wifi/java/android/net/wifi/RttManager.java
+++ b/wifi/java/android/net/wifi/RttManager.java
@@ -472,6 +472,34 @@
             preamble = PREAMBLE_HT;
             bandwidth = RTT_BW_20_SUPPORT;
         }
+
+        /**
+         * {@hide}
+         */
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("deviceType=" + deviceType);
+            sb.append(", requestType=" + requestType);
+            sb.append(", secure=" + secure);
+            sb.append(", bssid=" + bssid);
+            sb.append(", frequency=" + frequency);
+            sb.append(", channelWidth=" + channelWidth);
+            sb.append(", centerFreq0=" + centerFreq0);
+            sb.append(", centerFreq1=" + centerFreq1);
+            sb.append(", num_samples=" + num_samples);
+            sb.append(", num_retries=" + num_retries);
+            sb.append(", numberBurst=" + numberBurst);
+            sb.append(", interval=" + interval);
+            sb.append(", numSamplesPerBurst=" + numSamplesPerBurst);
+            sb.append(", numRetriesPerMeasurementFrame=" + numRetriesPerMeasurementFrame);
+            sb.append(", numRetriesPerFTMR=" + numRetriesPerFTMR);
+            sb.append(", LCIRequest=" + LCIRequest);
+            sb.append(", LCRRequest=" + LCRRequest);
+            sb.append(", burstTimeout=" + burstTimeout);
+            sb.append(", preamble=" + preamble);
+            sb.append(", bandwidth=" + bandwidth);
+            return sb.toString();
+        }
     }
 
     /** pseudo-private class used to parcel arguments */
@@ -727,6 +755,51 @@
             mResults = results;
         }
 
+        /**
+         * {@hide}
+         */
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            for (int i = 0; i < mResults.length; ++i) {
+                sb.append("[" + i + "]: ");
+                sb.append("bssid=" + mResults[i].bssid);
+                sb.append(", burstNumber=" + mResults[i].burstNumber);
+                sb.append(", measurementFrameNumber=" + mResults[i].measurementFrameNumber);
+                sb.append(", successMeasurementFrameNumber="
+                        + mResults[i].successMeasurementFrameNumber);
+                sb.append(", frameNumberPerBurstPeer=" + mResults[i].frameNumberPerBurstPeer);
+                sb.append(", status=" + mResults[i].status);
+                sb.append(", requestType=" + mResults[i].requestType);
+                sb.append(", measurementType=" + mResults[i].measurementType);
+                sb.append(", retryAfterDuration=" + mResults[i].retryAfterDuration);
+                sb.append(", ts=" + mResults[i].ts);
+                sb.append(", rssi=" + mResults[i].rssi);
+                sb.append(", rssi_spread=" + mResults[i].rssi_spread);
+                sb.append(", rssiSpread=" + mResults[i].rssiSpread);
+                sb.append(", tx_rate=" + mResults[i].tx_rate);
+                sb.append(", txRate=" + mResults[i].txRate);
+                sb.append(", rxRate=" + mResults[i].rxRate);
+                sb.append(", rtt_ns=" + mResults[i].rtt_ns);
+                sb.append(", rtt=" + mResults[i].rtt);
+                sb.append(", rtt_sd_ns=" + mResults[i].rtt_sd_ns);
+                sb.append(", rttStandardDeviation=" + mResults[i].rttStandardDeviation);
+                sb.append(", rtt_spread_ns=" + mResults[i].rtt_spread_ns);
+                sb.append(", rttSpread=" + mResults[i].rttSpread);
+                sb.append(", distance_cm=" + mResults[i].distance_cm);
+                sb.append(", distance=" + mResults[i].distance);
+                sb.append(", distance_sd_cm=" + mResults[i].distance_sd_cm);
+                sb.append(", distanceStandardDeviation=" + mResults[i].distanceStandardDeviation);
+                sb.append(", distance_spread_cm=" + mResults[i].distance_spread_cm);
+                sb.append(", distanceSpread=" + mResults[i].distanceSpread);
+                sb.append(", burstDuration=" + mResults[i].burstDuration);
+                sb.append(", negotiatedBurstNum=" + mResults[i].negotiatedBurstNum);
+                sb.append(", LCI=" + mResults[i].LCI);
+                sb.append(", LCR=" + mResults[i].LCR);
+                sb.append(", secure=" + mResults[i].secure);
+            }
+            return sb.toString();
+        }
+
         /** Implement the Parcelable interface {@hide} */
         @Override
         public int describeContents() {
@@ -1295,4 +1368,3 @@
     }
 
 }
-
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 9d0c20c..7c5276c 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -806,7 +806,7 @@
          * Quality network selection status String (for debug purpose). Use Quality network
          * selection status value as index to extec the corresponding debug string
          */
-        private static final String[] QUALITY_NETWORK_SELECTION_STATUS = {
+        public static final String[] QUALITY_NETWORK_SELECTION_STATUS = {
                 "NETWORK_SELECTION_ENABLED",
                 "NETWORK_SELECTION_TEMPORARY_DISABLED",
                 "NETWORK_SELECTION_PERMANENTLY_DISABLED"};
@@ -817,6 +817,7 @@
          */
         public static final int NETWORK_SELECTION_ENABLE = 0;
         /**
+         * @deprecated it is not used any more.
          * This network is disabled because higher layer (>2) network is bad
          */
         public static final int DISABLED_BAD_LINK = 1;
@@ -837,40 +838,51 @@
          */
         public static final int DISABLED_DNS_FAILURE = 5;
         /**
+         * This network is disabled because we started WPS
+         */
+        public static final int DISABLED_WPS_START = 6;
+        /**
          * This network is disabled because EAP-TLS failure
          */
-        public static final int DISABLED_TLS_VERSION_MISMATCH = 6;
+        public static final int DISABLED_TLS_VERSION_MISMATCH = 7;
         /**
-         * This network is disabled due to WifiManager disable it explicitly
+         * This network is disabled due to absence of user credentials
          */
-        public static final int DISABLED_AUTHENTICATION_NO_CREDENTIALS = 7;
+        public static final int DISABLED_AUTHENTICATION_NO_CREDENTIALS = 8;
         /**
          * This network is disabled because no Internet connected and user do not want
          */
-        public static final int DISABLED_NO_INTERNET = 8;
+        public static final int DISABLED_NO_INTERNET = 9;
         /**
          * This network is disabled due to WifiManager disable it explicitly
          */
-        public static final int DISABLED_BY_WIFI_MANAGER = 9;
+        public static final int DISABLED_BY_WIFI_MANAGER = 10;
+        /**
+         * This network is disabled due to user switching
+         */
+        public static final int DISABLED_DUE_TO_USER_SWITCH = 11;
         /**
          * This Maximum disable reason value
          */
-        public static final int NETWORK_SELECTION_DISABLED_MAX = 10;
+        public static final int NETWORK_SELECTION_DISABLED_MAX = 12;
 
         /**
          * Quality network selection disable reason String (for debug purpose)
          */
-        private static final String[] QUALITY_NETWORK_SELECTION_DISABLE_REASON = {
+        public static final String[] QUALITY_NETWORK_SELECTION_DISABLE_REASON = {
                 "NETWORK_SELECTION_ENABLE",
-                "NETWORK_SELECTION_DISABLED_BAD_LINK",
+                "NETWORK_SELECTION_DISABLED_BAD_LINK", // deprecated
                 "NETWORK_SELECTION_DISABLED_ASSOCIATION_REJECTION ",
                 "NETWORK_SELECTION_DISABLED_AUTHENTICATION_FAILURE",
                 "NETWORK_SELECTION_DISABLED_DHCP_FAILURE",
                 "NETWORK_SELECTION_DISABLED_DNS_FAILURE",
+                "NETWORK_SELECTION_DISABLED_WPS_START",
                 "NETWORK_SELECTION_DISABLED_TLS_VERSION",
                 "NETWORK_SELECTION_DISABLED_AUTHENTICATION_NO_CREDENTIALS",
                 "NETWORK_SELECTION_DISABLED_NO_INTERNET",
-                "NETWORK_SELECTION_DISABLED_BY_WIFI_MANAGER"};
+                "NETWORK_SELECTION_DISABLED_BY_WIFI_MANAGER",
+                "NETWORK_SELECTION_DISABLED_BY_USER_SWITCH"
+        };
 
         /**
          * Invalid time stamp for network selection disable
@@ -1054,7 +1066,7 @@
             return mHasEverConnected;
         }
 
-        private NetworkSelectionStatus() {
+        public NetworkSelectionStatus() {
             // previously stored configs will not have this parameter, so we default to false.
             mHasEverConnected = false;
         };
@@ -1254,6 +1266,8 @@
             }
             mTemporarilyDisabledTimestamp = source.mTemporarilyDisabledTimestamp;
             mNetworkSelectionBSSID = source.mNetworkSelectionBSSID;
+            setCandidate(source.getCandidate());
+            setCandidateScore(source.getCandidateScore());
             setConnectChoice(source.getConnectChoice());
             setConnectChoiceTimestamp(source.getConnectChoiceTimestamp());
             setHasEverConnected(source.getHasEverConnected());
@@ -1302,7 +1316,7 @@
      * @hide
      * network selection related member
      */
-    private final NetworkSelectionStatus mNetworkSelectionStatus = new NetworkSelectionStatus();
+    private NetworkSelectionStatus mNetworkSelectionStatus = new NetworkSelectionStatus();
 
     /**
      * @hide
@@ -1311,6 +1325,15 @@
     public NetworkSelectionStatus getNetworkSelectionStatus() {
         return mNetworkSelectionStatus;
     }
+
+    /**
+     * Set the network selection status
+     * @hide
+     */
+    public void setNetworkSelectionStatus(NetworkSelectionStatus status) {
+        mNetworkSelectionStatus = status;
+    }
+
     /**
      * @hide
      * Linked Configurations: represent the set of Wificonfigurations that are equivalent
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index b614a86..08ad35e 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -375,7 +375,15 @@
             return false;
         }
 
+        // wpa_supplicant can update the anonymous identity for these kinds of networks after
+        // framework reads them, so make sure the framework doesn't try to overwrite them.
+        boolean shouldNotWriteAnonIdentity = mEapMethod == WifiEnterpriseConfig.Eap.SIM
+                || mEapMethod == WifiEnterpriseConfig.Eap.AKA
+                || mEapMethod == WifiEnterpriseConfig.Eap.AKA_PRIME;
         for (String key : mFields.keySet()) {
+            if (shouldNotWriteAnonIdentity && ANON_IDENTITY_KEY.equals(key)) {
+                continue;
+            }
             if (!saver.saveValue(key, mFields.get(key))) {
                 return false;
             }
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 1cd32b6..2b73fac 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -19,6 +19,7 @@
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemApi;
+import android.bluetooth.BluetoothAdapter;
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.DhcpInfo;
@@ -561,6 +562,34 @@
     public static final String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK";
 
     /**
+     * Activity Action: Show UI to get user approval to enable WiFi.
+     * <p>Input: {@link android.content.Intent#EXTRA_PACKAGE_NAME} string extra with
+     *           the name of the app requesting the action.
+     * <p>Output: Nothing.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_REQUEST_ENABLE = "android.net.wifi.action.REQUEST_ENABLE";
+
+    /**
+     * Activity Action: Show UI to get user approval to disable WiFi.
+     * <p>Input: {@link android.content.Intent#EXTRA_PACKAGE_NAME} string extra with
+     *           the name of the app requesting the action.
+     * <p>Output: Nothing.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_REQUEST_DISABLE = "android.net.wifi.action.REQUEST_DISABLE";
+
+    /**
+     * Internally used Wi-Fi lock mode representing the case were no locks are held.
+     * @hide
+     */
+    public static final int WIFI_MODE_NO_LOCKS_HELD = 0;
+
+    /**
      * In this Wi-Fi lock mode, Wi-Fi will be kept active,
      * and will behave normally, i.e., it will attempt to automatically
      * establish a connection to a remembered access point that is
@@ -1114,7 +1143,7 @@
 
     /**
      * @return true if this adapter supports Neighbour Awareness Network APIs
-     * @hide PROPOSED_NAN_API
+     * @hide
      */
     public boolean isNanSupported() {
         return isFeatureSupported(WIFI_FEATURE_NAN);
@@ -1437,7 +1466,7 @@
      */
     public boolean setWifiEnabled(boolean enabled) {
         try {
-            return mService.setWifiEnabled(enabled);
+            return mService.setWifiEnabled(mContext.getOpPackageName(), enabled);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -2731,4 +2760,41 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Retrieve the data to be backed to save the current state.
+     * @hide
+     */
+    public byte[] retrieveBackupData() {
+        try {
+            return mService.retrieveBackupData();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Restore state from the backed up data.
+     * @hide
+     */
+    public void restoreBackupData(byte[] data) {
+        try {
+            mService.restoreBackupData(data);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Restore state from the older version of back up data.
+     * The old backup data was essentially a backup of wpa_supplicant.conf & ipconfig.txt file.
+     * @hide
+     */
+    public void restoreSupplicantBackupData(byte[] supplicantData, byte[] ipConfigData) {
+        try {
+            mService.restoreSupplicantBackupData(supplicantData, ipConfigData);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 2636c3f..716f1d3 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -656,6 +656,40 @@
         void onPnoNetworkFound(ScanResult[] results);
     }
 
+    /**
+     * Register a listener that will receive results from all single scans
+     * Either the onSuccess/onFailure will be called once when the listener is registered. After
+     * (assuming onSuccess was called) all subsequent single scan results will be delivered to the
+     * listener. It is possible that onFullResult will not be called for all results of the first
+     * scan if the listener was registered during the scan.
+     *
+     * @param listener specifies the object to report events to. This object is also treated as a
+     *                 key for this request, and must also be specified to cancel the request.
+     *                 Multiple requests should also not share this object.
+     * {@hide}
+     */
+    public void registerScanListener(ScanListener listener) {
+        Preconditions.checkNotNull(listener, "listener cannot be null");
+        int key = addListener(listener);
+        if (key == INVALID_KEY) return;
+        validateChannel();
+        mAsyncChannel.sendMessage(CMD_REGISTER_SCAN_LISTENER, 0, key);
+    }
+
+    /**
+     * Deregister a listener for ongoing single scans
+     * @param listener specifies which scan to cancel; must be same object as passed in {@link
+     *  #registerScanListener}
+     * {@hide}
+     */
+    public void deregisterScanListener(ScanListener listener) {
+        Preconditions.checkNotNull(listener, "listener cannot be null");
+        int key = removeListener(listener);
+        if (key == INVALID_KEY) return;
+        validateChannel();
+        mAsyncChannel.sendMessage(CMD_DEREGISTER_SCAN_LISTENER, 0, key);
+    }
+
     /** start wifi scan in background
      * @param settings specifies various parameters for the scan; for more information look at
      * {@link ScanSettings}
@@ -1129,6 +1163,10 @@
     public static final int CMD_STOP_PNO_SCAN               = BASE + 25;
     /** @hide */
     public static final int CMD_PNO_NETWORK_FOUND           = BASE + 26;
+    /** @hide */
+    public static final int CMD_REGISTER_SCAN_LISTENER      = BASE + 27;
+    /** @hide */
+    public static final int CMD_DEREGISTER_SCAN_LISTENER    = BASE + 28;
 
     private Context mContext;
     private IWifiScanner mService;
diff --git a/wifi/java/android/net/wifi/nan/ConfigRequest.java b/wifi/java/android/net/wifi/nan/ConfigRequest.java
index 23e3754..44544de 100644
--- a/wifi/java/android/net/wifi/nan/ConfigRequest.java
+++ b/wifi/java/android/net/wifi/nan/ConfigRequest.java
@@ -16,15 +16,17 @@
 
 package android.net.wifi.nan;
 
+import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 /**
  * Defines a request object to configure a Wi-Fi NAN network. Built using
  * {@link ConfigRequest.Builder}. Configuration is requested using
- * {@link WifiNanManager#requestConfig(ConfigRequest)}. Note that the actual
- * achieved configuration may be different from the requested configuration -
- * since multiple applications may request different configurations.
+ * {@link WifiNanManager#connect(android.os.Looper, ConfigRequest, WifiNanEventCallback)}.
+ * Note that the actual achieved configuration may be different from the
+ * requested configuration - since different applications may request different
+ * configurations.
  *
  * @hide PROPOSED_NAN_API
  */
@@ -73,19 +75,28 @@
      */
     public final int mClusterHigh;
 
+    /**
+     * Indicates whether we want to get callbacks when our identity is changed.
+     *
+     * @hide
+     */
+    public final boolean mEnableIdentityChangeCallback;
+
     private ConfigRequest(boolean support5gBand, int masterPreference, int clusterLow,
-            int clusterHigh) {
+            int clusterHigh, boolean enableIdentityChangeCallback) {
         mSupport5gBand = support5gBand;
         mMasterPreference = masterPreference;
         mClusterLow = clusterLow;
         mClusterHigh = clusterHigh;
+        mEnableIdentityChangeCallback = enableIdentityChangeCallback;
     }
 
     @Override
     public String toString() {
         return "ConfigRequest [mSupport5gBand=" + mSupport5gBand + ", mMasterPreference="
                 + mMasterPreference + ", mClusterLow=" + mClusterLow + ", mClusterHigh="
-                + mClusterHigh + "]";
+                + mClusterHigh + ", mEnableIdentityChangeCallback=" + mEnableIdentityChangeCallback
+                + "]";
     }
 
     @Override
@@ -99,6 +110,7 @@
         dest.writeInt(mMasterPreference);
         dest.writeInt(mClusterLow);
         dest.writeInt(mClusterHigh);
+        dest.writeInt(mEnableIdentityChangeCallback ? 1 : 0);
     }
 
     public static final Creator<ConfigRequest> CREATOR = new Creator<ConfigRequest>() {
@@ -113,7 +125,9 @@
             int masterPreference = in.readInt();
             int clusterLow = in.readInt();
             int clusterHigh = in.readInt();
-            return new ConfigRequest(support5gBand, masterPreference, clusterLow, clusterHigh);
+            boolean enableIdentityChangeCallback = in.readInt() != 0;
+            return new ConfigRequest(support5gBand, masterPreference, clusterLow, clusterHigh,
+                    enableIdentityChangeCallback);
         }
     };
 
@@ -130,9 +144,47 @@
         ConfigRequest lhs = (ConfigRequest) o;
 
         return mSupport5gBand == lhs.mSupport5gBand && mMasterPreference == lhs.mMasterPreference
+                && mClusterLow == lhs.mClusterLow && mClusterHigh == lhs.mClusterHigh
+                && mEnableIdentityChangeCallback == lhs.mEnableIdentityChangeCallback;
+    }
+
+    /**
+     * Checks for equality of two configuration - but only considering their
+     * on-the-air NAN configuration impact.
+     *
+     * @param o Object to compare to.
+     * @return true if configuration objects have the same on-the-air
+     *         configuration, false otherwise.
+     *
+     * @hide
+     */
+    public boolean equalsOnTheAir(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof ConfigRequest)) {
+            return false;
+        }
+
+        ConfigRequest lhs = (ConfigRequest) o;
+
+        return mSupport5gBand == lhs.mSupport5gBand && mMasterPreference == lhs.mMasterPreference
                 && mClusterLow == lhs.mClusterLow && mClusterHigh == lhs.mClusterHigh;
     }
 
+    /**
+     * Checks whether the configuration's settings which impact on-air behavior are non-default.
+     *
+     * @return true if any of the on-air-impacting settings are non-default.
+     *
+     * @hide
+     */
+    public boolean isNonDefaultOnTheAir() {
+        return mSupport5gBand || mMasterPreference != 0 || mClusterLow != CLUSTER_ID_MIN
+                || mClusterHigh != CLUSTER_ID_MAX;
+    }
+
     @Override
     public int hashCode() {
         int result = 17;
@@ -141,35 +193,63 @@
         result = 31 * result + mMasterPreference;
         result = 31 * result + mClusterLow;
         result = 31 * result + mClusterHigh;
+        result = 31 * result + (mEnableIdentityChangeCallback ? 1 : 0);
 
         return result;
     }
 
     /**
+     * Verifies that the contents of the ConfigRequest are valid. Otherwise
+     * throws an IllegalArgumentException.
+     *
+     * @hide
+     */
+    public void validate() throws IllegalArgumentException {
+        if (mMasterPreference < 0) {
+            throw new IllegalArgumentException(
+                    "Master Preference specification must be non-negative");
+        }
+        if (mMasterPreference == 1 || mMasterPreference == 255 || mMasterPreference > 255) {
+            throw new IllegalArgumentException("Master Preference specification must not "
+                    + "exceed 255 or use 1 or 255 (reserved values)");
+        }
+        if (mClusterLow < CLUSTER_ID_MIN) {
+            throw new IllegalArgumentException("Cluster specification must be non-negative");
+        }
+        if (mClusterLow > CLUSTER_ID_MAX) {
+            throw new IllegalArgumentException("Cluster specification must not exceed 0xFFFF");
+        }
+        if (mClusterHigh < CLUSTER_ID_MIN) {
+            throw new IllegalArgumentException("Cluster specification must be non-negative");
+        }
+        if (mClusterHigh > CLUSTER_ID_MAX) {
+            throw new IllegalArgumentException("Cluster specification must not exceed 0xFFFF");
+        }
+        if (mClusterLow > mClusterHigh) {
+            throw new IllegalArgumentException(
+                    "Invalid argument combination - must have Cluster Low <= Cluster High");
+        }
+    }
+
+    /**
      * Builder used to build {@link ConfigRequest} objects.
      */
     public static final class Builder {
-        private boolean mSupport5gBand;
-        private int mMasterPreference;
-        private int mClusterLow;
-        private int mClusterHigh;
+        private boolean mSupport5gBand = false;
+        private int mMasterPreference = 0;
+        private int mClusterLow = CLUSTER_ID_MIN;
+        private int mClusterHigh = CLUSTER_ID_MAX;
+        private boolean mEnableIdentityChangeCallback = false;
 
         /**
-         * Default constructor for the Builder.
-         */
-        public Builder() {
-            mSupport5gBand = false;
-            mMasterPreference = 0;
-            mClusterLow = 0;
-            mClusterHigh = CLUSTER_ID_MAX;
-        }
-
-        /**
-         * Specify whether 5G band support is required in this request.
+         * Specify whether 5G band support is required in this request. Disabled by default.
          *
          * @param support5gBand Support for 5G band is required.
+         *
          * @return The builder to facilitate chaining
          *         {@code builder.setXXX(..).setXXX(..)}.
+         *
+         * @hide PROPOSED_NAN_SYSTEM_API
          */
         public Builder setSupport5gBand(boolean support5gBand) {
             mSupport5gBand = support5gBand;
@@ -177,12 +257,15 @@
         }
 
         /**
-         * Specify the Master Preference requested. The permitted range is 0 to
+         * Specify the Master Preference requested. The permitted range is 0 (the default) to
          * 255 with 1 and 255 excluded (reserved).
          *
          * @param masterPreference The requested master preference
+         *
          * @return The builder to facilitate chaining
          *         {@code builder.setXXX(..).setXXX(..)}.
+         *
+         * @hide PROPOSED_NAN_SYSTEM_API
          */
         public Builder setMasterPreference(int masterPreference) {
             if (masterPreference < 0) {
@@ -202,13 +285,16 @@
          * The Cluster ID is generated randomly for new NAN networks. Specify
          * the lower range of the cluster ID. The upper range is specified using
          * the {@link ConfigRequest.Builder#setClusterHigh(int)}. The permitted
-         * range is 0 to the value specified by
-         * {@link ConfigRequest.Builder#setClusterHigh(int)}. Equality is
+         * range is 0 (the default) to the value specified by
+         * {@link ConfigRequest.Builder#setClusterHigh(int)}. Equality of Low and High is
          * permitted which restricts the Cluster ID to the specified value.
          *
          * @param clusterLow The lower range of the generated cluster ID.
+         *
          * @return The builder to facilitate chaining
          *         {@code builder.setClusterLow(..).setClusterHigh(..)}.
+         *
+         * @hide PROPOSED_NAN_SYSTEM_API
          */
         public Builder setClusterLow(int clusterLow) {
             if (clusterLow < CLUSTER_ID_MIN) {
@@ -227,12 +313,15 @@
          * the lower upper of the cluster ID. The lower range is specified using
          * the {@link ConfigRequest.Builder#setClusterLow(int)}. The permitted
          * range is the value specified by
-         * {@link ConfigRequest.Builder#setClusterLow(int)} to 0xFFFF. Equality
-         * is permitted which restricts the Cluster ID to the specified value.
+         * {@link ConfigRequest.Builder#setClusterLow(int)} to 0xFFFF (the default). Equality of
+         * Low and High is permitted which restricts the Cluster ID to the specified value.
          *
          * @param clusterHigh The upper range of the generated cluster ID.
+         *
          * @return The builder to facilitate chaining
          *         {@code builder.setClusterLow(..).setClusterHigh(..)}.
+         *
+         * @hide PROPOSED_NAN_SYSTEM_API
          */
         public Builder setClusterHigh(int clusterHigh) {
             if (clusterHigh < CLUSTER_ID_MIN) {
@@ -247,6 +336,27 @@
         }
 
         /**
+         * Indicate whether or not we want to enable the
+         * {@link WifiNanEventCallback#onIdentityChanged(byte[])} callback. A
+         * device identity is its Discovery MAC address which is randomized at regular intervals.
+         * An application may need to know the MAC address, e.g. when using OOB (out-of-band)
+         * discovery together with NAN connections.
+         * <p>
+         *     The callbacks are disabled by default since it may result in additional wake-ups
+         *     of the host -
+         *     increasing power.
+         *
+         * @param enableIdentityChangeCallback Enable the callback informing
+         *            listener when identity is changed.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setEnableIdentityChangeCallback(boolean enableIdentityChangeCallback) {
+            mEnableIdentityChangeCallback = enableIdentityChangeCallback;
+            return this;
+        }
+
+        /**
          * Build {@link ConfigRequest} given the current requests made on the
          * builder.
          */
@@ -256,7 +366,8 @@
                         "Invalid argument combination - must have Cluster Low <= Cluster High");
             }
 
-            return new ConfigRequest(mSupport5gBand, mMasterPreference, mClusterLow, mClusterHigh);
+            return new ConfigRequest(mSupport5gBand, mMasterPreference, mClusterLow, mClusterHigh,
+                    mEnableIdentityChangeCallback);
         }
     }
 }
diff --git a/wifi/java/android/net/wifi/nan/IWifiNanEventListener.aidl b/wifi/java/android/net/wifi/nan/IWifiNanEventCallback.aidl
similarity index 66%
rename from wifi/java/android/net/wifi/nan/IWifiNanEventListener.aidl
rename to wifi/java/android/net/wifi/nan/IWifiNanEventCallback.aidl
index fa666af..a4e590b 100644
--- a/wifi/java/android/net/wifi/nan/IWifiNanEventListener.aidl
+++ b/wifi/java/android/net/wifi/nan/IWifiNanEventCallback.aidl
@@ -17,16 +17,20 @@
 package android.net.wifi.nan;
 
 import android.net.wifi.nan.ConfigRequest;
+import android.net.wifi.RttManager;
 
 /**
  * Callback interface that WifiNanManager implements
  *
  * {@hide}
  */
-oneway interface IWifiNanEventListener
+oneway interface IWifiNanEventCallback
 {
-    void onConfigCompleted(in ConfigRequest completedConfig);
-    void onConfigFailed(in ConfigRequest failedConfig, int reason);
-    void onNanDown(int reason);
-    void onIdentityChanged();
+    void onConnectSuccess();
+    void onConnectFail(int reason);
+    void onIdentityChanged(in byte[] mac);
+
+    void onRangingSuccess(int rangingId, in RttManager.ParcelableRttResults results);
+    void onRangingFailure(int rangingId, int reason, in String description);
+    void onRangingAborted(int rangingId);
 }
diff --git a/wifi/java/android/net/wifi/nan/IWifiNanManager.aidl b/wifi/java/android/net/wifi/nan/IWifiNanManager.aidl
index f382d97..17ec1bc 100644
--- a/wifi/java/android/net/wifi/nan/IWifiNanManager.aidl
+++ b/wifi/java/android/net/wifi/nan/IWifiNanManager.aidl
@@ -19,12 +19,11 @@
 import android.app.PendingIntent;
 
 import android.net.wifi.nan.ConfigRequest;
-import android.net.wifi.nan.IWifiNanEventListener;
-import android.net.wifi.nan.IWifiNanSessionListener;
-import android.net.wifi.nan.PublishData;
-import android.net.wifi.nan.PublishSettings;
-import android.net.wifi.nan.SubscribeData;
-import android.net.wifi.nan.SubscribeSettings;
+import android.net.wifi.nan.IWifiNanEventCallback;
+import android.net.wifi.nan.IWifiNanSessionCallback;
+import android.net.wifi.nan.PublishConfig;
+import android.net.wifi.nan.SubscribeConfig;
+import android.net.wifi.RttManager;
 
 /**
  * Interface that WifiNanService implements
@@ -33,18 +32,25 @@
  */
 interface IWifiNanManager
 {
+    // NAN API
+    void enableUsage();
+    void disableUsage();
+    boolean isUsageEnabled();
+
     // client API
-    void connect(in IBinder binder, in IWifiNanEventListener listener, int events);
-    void disconnect(in IBinder binder);
-    void requestConfig(in ConfigRequest configRequest);
+    int connect(in IBinder binder, in String callingPackage, in IWifiNanEventCallback callback,
+            in ConfigRequest configRequest);
+    void disconnect(int clientId, in IBinder binder);
+
+    void publish(int clientId, in PublishConfig publishConfig, in IWifiNanSessionCallback callback);
+    void subscribe(int clientId, in SubscribeConfig subscribeConfig,
+            in IWifiNanSessionCallback callback);
 
     // session API
-    int createSession(in IWifiNanSessionListener listener, int events);
-    void publish(int sessionId, in PublishData publishData, in PublishSettings publishSettings);
-    void subscribe(int sessionId, in SubscribeData subscribeData,
-            in SubscribeSettings subscribeSettings);
-    void sendMessage(int sessionId, int peerId, in byte[] message, int messageLength,
-            int messageId);
-    void stopSession(int sessionId);
-    void destroySession(int sessionId);
+    void updatePublish(int clientId, int sessionId, in PublishConfig publishConfig);
+    void updateSubscribe(int clientId, int sessionId, in SubscribeConfig subscribeConfig);
+    void sendMessage(int clientId, int sessionId, int peerId, in byte[] message, int messageId,
+        int retryCount);
+    void terminateSession(int clientId, int sessionId);
+    int startRanging(int clientId, int sessionId, in RttManager.ParcelableRttParams parms);
 }
diff --git a/wifi/java/android/net/wifi/nan/IWifiNanSessionListener.aidl b/wifi/java/android/net/wifi/nan/IWifiNanSessionCallback.aidl
similarity index 65%
rename from wifi/java/android/net/wifi/nan/IWifiNanSessionListener.aidl
rename to wifi/java/android/net/wifi/nan/IWifiNanSessionCallback.aidl
index d60d8ca..ff2c409 100644
--- a/wifi/java/android/net/wifi/nan/IWifiNanSessionListener.aidl
+++ b/wifi/java/android/net/wifi/nan/IWifiNanSessionCallback.aidl
@@ -21,18 +21,16 @@
  *
  * {@hide}
  */
-oneway interface IWifiNanSessionListener
+oneway interface IWifiNanSessionCallback
 {
-    void onPublishFail(int reason);
-    void onPublishTerminated(int reason);
+    void onSessionStarted(int sessionId);
+    void onSessionConfigSuccess();
+    void onSessionConfigFail(int reason);
+    void onSessionTerminated(int reason);
 
-    void onSubscribeFail(int reason);
-    void onSubscribeTerminated(int reason);
-
-    void onMatch(int peerId, in byte[] serviceSpecificInfo,
-            int serviceSpecificInfoLength, in byte[] matchFilter, int matchFilterLength);
+    void onMatch(int peerId, in byte[] serviceSpecificInfo, in byte[] matchFilter);
 
     void onMessageSendSuccess(int messageId);
     void onMessageSendFail(int messageId, int reason);
-    void onMessageReceived(int peerId, in byte[] message, int messageLength);
+    void onMessageReceived(int peerId, in byte[] message);
 }
diff --git a/wifi/java/android/net/wifi/nan/LvBufferUtils.java b/wifi/java/android/net/wifi/nan/LvBufferUtils.java
new file mode 100644
index 0000000..eb56070
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/LvBufferUtils.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nan;
+
+import android.annotation.Nullable;
+
+import libcore.io.Memory;
+
+import java.nio.ByteOrder;
+import java.util.Iterator;
+
+/**
+ * Utility class to construct and parse byte arrays using the LV format -
+ * Length/Value format. The utilities accept a configuration of the size of
+ * the Length field.
+ *
+ * @hide PROPOSED_NAN_API
+ */
+public class LvBufferUtils {
+    private LvBufferUtils() {
+        // no reason to ever create this class
+    }
+
+    /**
+     * Utility class to construct byte arrays using the LV format - Length/Value.
+     * <p>
+     * A constructor is created specifying the size of the Length (L) field.
+     * <p>
+     * The byte array is either provided (using
+     * {@link LvBufferUtils.LvConstructor#wrap(byte[])}) or allocated (using
+     * {@link LvBufferUtils.LvConstructor#allocate(int)}).
+     * <p>
+     * Values are added to the structure using the {@code LvConstructor.put*()}
+     * methods.
+     * <p>
+     * The final byte array is obtained using {@link LvBufferUtils.LvConstructor#getArray()}.
+     */
+    public static class LvConstructor {
+        private TlvBufferUtils.TlvConstructor mTlvImpl;
+
+        /**
+         * Define a LV constructor with the specified size of the Length (L) field.
+         *
+         * @param lengthSize Number of bytes used for the Length (L) field.
+         *            Values of 1 or 2 bytes are allowed.
+         */
+        public LvConstructor(int lengthSize) {
+            mTlvImpl = new TlvBufferUtils.TlvConstructor(0, lengthSize);
+        }
+
+        /**
+         * Set the byte array to be used to construct the LV.
+         *
+         * @param array Byte array to be formatted.
+         * @return The constructor to facilitate chaining
+         *         {@code ctr.putXXX(..).putXXX(..)}.
+         */
+        public LvBufferUtils.LvConstructor wrap(@Nullable byte[] array) {
+            mTlvImpl.wrap(array);
+            return this;
+        }
+
+        /**
+         * Allocates a new byte array to be used ot construct a LV.
+         *
+         * @param capacity The size of the byte array to be allocated.
+         * @return The constructor to facilitate chaining
+         *         {@code ctr.putXXX(..).putXXX(..)}.
+         */
+        public LvBufferUtils.LvConstructor allocate(int capacity) {
+            mTlvImpl.allocate(capacity);
+            return this;
+        }
+
+        /**
+         * Copies a byte into the LV array.
+         *
+         * @param b The byte to be inserted into the structure.
+         * @return The constructor to facilitate chaining
+         *         {@code ctr.putXXX(..).putXXX(..)}.
+         */
+        public LvBufferUtils.LvConstructor putByte(byte b) {
+            mTlvImpl.putByte(0, b);
+            return this;
+        }
+
+        /**
+         * Copies a byte array into the LV.
+         *
+         * @param array The array to be copied into the LV structure.
+         * @param offset Start copying from the array at the specified offset.
+         * @param length Copy the specified number (length) of bytes from the
+         *            array.
+         * @return The constructor to facilitate chaining
+         *         {@code ctr.putXXX(..).putXXX(..)}.
+         */
+        public LvBufferUtils.LvConstructor putByteArray(@Nullable byte[] array, int offset,
+                int length) {
+            mTlvImpl.putByteArray(0, array, offset, length);
+            return this;
+        }
+
+        /**
+         * Copies a byte array into the LV.
+         *
+         * @param array The array to be copied (in full) into the LV structure.
+         * @return The constructor to facilitate chaining
+         *         {@code ctr.putXXX(..).putXXX(..)}.
+         */
+        public LvBufferUtils.LvConstructor putByteArray(int type, @Nullable byte[] array) {
+            return putByteArray(array, 0, (array == null) ? 0 : array.length);
+        }
+
+        /**
+         * Places a zero length element (i.e. Length field = 0) into the LV.
+         *
+         * @return The constructor to facilitate chaining
+         *         {@code ctr.putXXX(..).putXXX(..)}.
+         */
+        public LvBufferUtils.LvConstructor putZeroLengthElement() {
+            mTlvImpl.putZeroLengthElement(0);
+            return this;
+        }
+
+        /**
+         * Copies short into the LV.
+         *
+         * @param data The short to be inserted into the structure.
+         * @return The constructor to facilitate chaining
+         *         {@code ctr.putXXX(..).putXXX(..)}.
+         */
+        public LvBufferUtils.LvConstructor putShort(short data) {
+            mTlvImpl.putShort(0, data);
+            return this;
+        }
+
+        /**
+         * Copies integer into the LV.
+         *
+         * @param data The integer to be inserted into the structure.
+         * @return The constructor to facilitate chaining
+         *         {@code ctr.putXXX(..).putXXX(..)}.
+         */
+        public LvBufferUtils.LvConstructor putInt(int data) {
+            mTlvImpl.putInt(0, data);
+            return this;
+        }
+
+        /**
+         * Copies a String's byte representation into the LV.
+         *
+         * @param data The string whose bytes are to be inserted into the
+         *            structure.
+         * @return The constructor to facilitate chaining
+         *         {@code ctr.putXXX(..).putXXX(..)}.
+         */
+        public LvBufferUtils.LvConstructor putString(@Nullable String data) {
+            mTlvImpl.putString(0, data);
+            return this;
+        }
+
+        /**
+         * Returns the constructed LV formatted byte-array. This array is a copy of the wrapped
+         * or allocated array - truncated to just the significant bytes - i.e. those written into
+         * the LV.
+         *
+         * @return The byte array containing the LV formatted structure.
+         */
+        public byte[] getArray() {
+            return mTlvImpl.getArray();
+        }
+    }
+
+    /**
+     * Utility class used when iterating over an LV formatted byte-array. Use
+     * {@link LvBufferUtils.LvIterable} to iterate over array. A {@link LvBufferUtils.LvElement}
+     * represents each entry in a LV formatted byte-array.
+     */
+    public static class LvElement {
+        /**
+         * The Length (L) field of the current LV element.
+         */
+        public int length;
+
+        /**
+         * The Value (V) field - a raw byte array representing the current LV
+         * element where the entry starts at {@link LvBufferUtils.LvElement#offset}.
+         */
+        public byte[] refArray;
+
+        /**
+         * The offset to be used into {@link LvBufferUtils.LvElement#refArray} to access the
+         * raw data representing the current LV element.
+         */
+        public int offset;
+
+        private LvElement(int length, @Nullable byte[] refArray, int offset) {
+            this.length = length;
+            this.refArray = refArray;
+            this.offset = offset;
+        }
+
+        /**
+         * Utility function to return a byte representation of a LV element of
+         * length 1. Note: an attempt to call this function on a LV item whose
+         * {@link LvBufferUtils.LvElement#length} is != 1 will result in an exception.
+         *
+         * @return byte representation of current LV element.
+         */
+        public byte getByte() {
+            if (length != 1) {
+                throw new IllegalArgumentException(
+                        "Accesing a byte from a LV element of length " + length);
+            }
+            return refArray[offset];
+        }
+
+        /**
+         * Utility function to return a short representation of a LV element of
+         * length 2. Note: an attempt to call this function on a LV item whose
+         * {@link LvBufferUtils.LvElement#length} is != 2 will result in an exception.
+         *
+         * @return short representation of current LV element.
+         */
+        public short getShort() {
+            if (length != 2) {
+                throw new IllegalArgumentException(
+                        "Accesing a short from a LV element of length " + length);
+            }
+            return Memory.peekShort(refArray, offset, ByteOrder.BIG_ENDIAN);
+        }
+
+        /**
+         * Utility function to return an integer representation of a LV element
+         * of length 4. Note: an attempt to call this function on a LV item
+         * whose {@link LvBufferUtils.LvElement#length} is != 4 will result in an exception.
+         *
+         * @return integer representation of current LV element.
+         */
+        public int getInt() {
+            if (length != 4) {
+                throw new IllegalArgumentException(
+                        "Accesing an int from a LV element of length " + length);
+            }
+            return Memory.peekInt(refArray, offset, ByteOrder.BIG_ENDIAN);
+        }
+
+        /**
+         * Utility function to return a String representation of a LV element.
+         *
+         * @return String representation of the current LV element.
+         */
+        public String getString() {
+            return new String(refArray, offset, length);
+        }
+    }
+
+    /**
+     * Utility class to iterate over a LV formatted byte-array.
+     */
+    public static class LvIterable implements Iterable<LvBufferUtils.LvElement> {
+        private final TlvBufferUtils.TlvIterable mTlvIterable;
+
+        /**
+         * Constructs an LvIterable object - specifying the format of the LV
+         * (the size of the Length field), and the byte array whose data is to be parsed.
+         *
+         * @param lengthSize Number of bytes sued for the Length (L) field.
+         *            Values values are 1 or 2 bytes.
+         * @param array The LV formatted byte-array to parse.
+         */
+        public LvIterable(int lengthSize, @Nullable byte[] array) {
+            mTlvIterable = new TlvBufferUtils.TlvIterable(0, lengthSize, array);
+        }
+
+        /**
+         * Prints out a parsed representation of the LV-formatted byte array.
+         * Whenever possible bytes, shorts, and integer are printed out (for
+         * fields whose length is 1, 2, or 4 respectively).
+         */
+        @Override
+        public String toString() {
+            return mTlvIterable.toString();
+        }
+
+        /**
+         * Returns an iterator to step through a LV formatted byte-array. The
+         * individual elements returned by the iterator are {@link LvBufferUtils.LvElement}.
+         */
+        @Override
+        public Iterator<LvBufferUtils.LvElement> iterator() {
+            return new Iterator<LvBufferUtils.LvElement>() {
+                private Iterator<TlvBufferUtils.TlvElement> mTlvIterator = mTlvIterable.iterator();
+
+                @Override
+                public boolean hasNext() {
+                    return mTlvIterator.hasNext();
+                }
+
+                @Override
+                public LvBufferUtils.LvElement next() {
+                    TlvBufferUtils.TlvElement tlvE = mTlvIterator.next();
+
+                    return new LvElement(tlvE.length, tlvE.refArray, tlvE.offset);
+                }
+
+                @Override
+                public void remove() {
+                    throw new UnsupportedOperationException();
+                }
+            };
+        }
+    }
+
+    /**
+     * Validates that a LV array is constructed correctly. I.e. that its specified Length
+     * fields correctly fill the specified length (and do not overshoot).
+     *
+     * @param array The LV array to verify.
+     * @param lengthSize The size (in bytes) of the length field. Valid values are 1 or 2.
+     * @return A boolean indicating whether the array is valid (true) or invalid (false).
+     */
+    public static boolean isValid(@Nullable byte[] array, int lengthSize) {
+        return TlvBufferUtils.isValid(array, 0, lengthSize);
+    }
+}
diff --git a/wifi/java/android/net/wifi/nan/SubscribeSettings.aidl b/wifi/java/android/net/wifi/nan/PublishConfig.aidl
similarity index 95%
rename from wifi/java/android/net/wifi/nan/SubscribeSettings.aidl
rename to wifi/java/android/net/wifi/nan/PublishConfig.aidl
index 44849bc..5f66d16 100644
--- a/wifi/java/android/net/wifi/nan/SubscribeSettings.aidl
+++ b/wifi/java/android/net/wifi/nan/PublishConfig.aidl
@@ -16,4 +16,4 @@
 
 package android.net.wifi.nan;
 
-parcelable SubscribeSettings;
+parcelable PublishConfig;
diff --git a/wifi/java/android/net/wifi/nan/PublishConfig.java b/wifi/java/android/net/wifi/nan/PublishConfig.java
new file mode 100644
index 0000000..71f99d9
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/PublishConfig.java
@@ -0,0 +1,391 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nan;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import libcore.util.HexEncoding;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+/**
+ * Defines the configuration of a NAN publish session. Built using
+ * {@link PublishConfig.Builder}. A publish session is created using
+ * {@link WifiNanManager#publish(PublishConfig, WifiNanSessionCallback)} or updated using
+ * {@link WifiNanPublishSession#updatePublish(PublishConfig)}.
+ *
+ * @hide PROPOSED_NAN_API
+ */
+public class PublishConfig implements Parcelable {
+    /** @hide */
+    @IntDef({
+            PUBLISH_TYPE_UNSOLICITED, PUBLISH_TYPE_SOLICITED })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PublishTypes {
+    }
+
+    /**
+     * Defines an unsolicited publish session - a publish session where the publisher is
+     * advertising itself by broadcasting on-the-air. An unsolicited publish session is paired
+     * with an passive subscribe session {@link SubscribeConfig#SUBSCRIBE_TYPE_PASSIVE}.
+     * Configuration is done using {@link PublishConfig.Builder#setPublishType(int)}.
+     */
+    public static final int PUBLISH_TYPE_UNSOLICITED = 0;
+
+    /**
+     * Defines a solicited publish session - a publish session which is silent, waiting for a
+     * matching active subscribe session - and responding to it in unicast. A
+     * solicited publish session is paired with an active subscribe session
+     * {@link SubscribeConfig#SUBSCRIBE_TYPE_ACTIVE}. Configuration is done using
+     * {@link PublishConfig.Builder#setPublishType(int)}.
+     */
+    public static final int PUBLISH_TYPE_SOLICITED = 1;
+
+    /** @hide */
+    public final byte[] mServiceName;
+
+    /** @hide */
+    public final byte[] mServiceSpecificInfo;
+
+    /** @hide */
+    public final byte[] mMatchFilter;
+
+    /** @hide */
+    public final int mPublishType;
+
+    /** @hide */
+    public final int mPublishCount;
+
+    /** @hide */
+    public final int mTtlSec;
+
+    /** @hide */
+    public final boolean mEnableTerminateNotification;
+
+    private PublishConfig(byte[] serviceName, byte[] serviceSpecificInfo, byte[] matchFilter,
+            int publishType, int publichCount, int ttlSec, boolean enableTerminateNotification) {
+        mServiceName = serviceName;
+        mServiceSpecificInfo = serviceSpecificInfo;
+        mMatchFilter = matchFilter;
+        mPublishType = publishType;
+        mPublishCount = publichCount;
+        mTtlSec = ttlSec;
+        mEnableTerminateNotification = enableTerminateNotification;
+    }
+
+    @Override
+    public String toString() {
+        return "PublishConfig [mServiceName='" + mServiceName + ", mServiceSpecificInfo='" + (
+                (mServiceSpecificInfo == null) ? "null" : HexEncoding.encode(mServiceSpecificInfo))
+                + ", mTxFilter=" + (new LvBufferUtils.LvIterable(1, mMatchFilter)).toString()
+                + ", mPublishType=" + mPublishType + ", mPublishCount=" + mPublishCount
+                + ", mTtlSec=" + mTtlSec + ", mEnableTerminateNotification="
+                + mEnableTerminateNotification + "]";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeByteArray(mServiceName);
+        dest.writeByteArray(mServiceSpecificInfo);
+        dest.writeByteArray(mMatchFilter);
+        dest.writeInt(mPublishType);
+        dest.writeInt(mPublishCount);
+        dest.writeInt(mTtlSec);
+        dest.writeInt(mEnableTerminateNotification ? 1 : 0);
+    }
+
+    public static final Creator<PublishConfig> CREATOR = new Creator<PublishConfig>() {
+        @Override
+        public PublishConfig[] newArray(int size) {
+            return new PublishConfig[size];
+        }
+
+        @Override
+        public PublishConfig createFromParcel(Parcel in) {
+            byte[] serviceName = in.createByteArray();
+            byte[] ssi = in.createByteArray();
+            byte[] matchFilter = in.createByteArray();
+            int publishType = in.readInt();
+            int publishCount = in.readInt();
+            int ttlSec = in.readInt();
+            boolean enableTerminateNotification = in.readInt() != 0;
+
+            return new PublishConfig(serviceName, ssi, matchFilter, publishType, publishCount,
+                    ttlSec, enableTerminateNotification);
+        }
+    };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof PublishConfig)) {
+            return false;
+        }
+
+        PublishConfig lhs = (PublishConfig) o;
+
+        return Arrays.equals(mServiceName, lhs.mServiceName) && Arrays.equals(mServiceSpecificInfo,
+                lhs.mServiceSpecificInfo) && Arrays.equals(mMatchFilter, lhs.mMatchFilter)
+                && mPublishType == lhs.mPublishType && mPublishCount == lhs.mPublishCount
+                && mTtlSec == lhs.mTtlSec
+                && mEnableTerminateNotification == lhs.mEnableTerminateNotification;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+
+        result = 31 * result + Arrays.hashCode(mServiceName);
+        result = 31 * result + Arrays.hashCode(mServiceSpecificInfo);
+        result = 31 * result + Arrays.hashCode(mMatchFilter);
+        result = 31 * result + mPublishType;
+        result = 31 * result + mPublishCount;
+        result = 31 * result + mTtlSec;
+        result = 31 * result + (mEnableTerminateNotification ? 1 : 0);
+
+        return result;
+    }
+
+    /**
+     * Verifies that the contents of the PublishConfig are valid. Otherwise
+     * throws an IllegalArgumentException.
+     *
+     * @hide
+     */
+    public void validate() throws IllegalArgumentException {
+        WifiNanUtils.validateServiceName(mServiceName);
+
+        if (!LvBufferUtils.isValid(mMatchFilter, 1)) {
+            throw new IllegalArgumentException(
+                    "Invalid txFilter configuration - LV fields do not match up to length");
+        }
+        if (mPublishType < PUBLISH_TYPE_UNSOLICITED || mPublishType > PUBLISH_TYPE_SOLICITED) {
+            throw new IllegalArgumentException("Invalid publishType - " + mPublishType);
+        }
+        if (mPublishCount < 0) {
+            throw new IllegalArgumentException("Invalid publishCount - must be non-negative");
+        }
+        if (mTtlSec < 0) {
+            throw new IllegalArgumentException("Invalid ttlSec - must be non-negative");
+        }
+    }
+
+    /**
+     * Builder used to build {@link PublishConfig} objects.
+     */
+    public static final class Builder {
+        private byte[] mServiceName;
+        private byte[] mServiceSpecificInfo;
+        private byte[] mMatchFilter;
+        private int mPublishType = PUBLISH_TYPE_UNSOLICITED;
+        private int mPublishCount = 0;
+        private int mTtlSec = 0;
+        private boolean mEnableTerminateNotification = true;
+
+        /**
+         * Specify the service name of the publish session. The actual on-air
+         * value is a 6 byte hashed representation of this string.
+         * <p>
+         * The Service Name is a UTF-8 encoded string from 1 to 255 bytes in length.
+         * The only acceptable single-byte UTF-8 symbols for a Service Name are alphanumeric
+         * values (A-Z, a-z, 0-9), the hyphen ('-'), and the period ('.'). All valid multi-byte
+         * UTF-8 characters are acceptable in a Service Name.
+         * <p>
+         * Must be called - an empty ServiceName is not valid.
+         *
+         * @param serviceName The service name for the publish session.
+         *
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setServiceName(@NonNull String serviceName) {
+            if (serviceName == null) {
+                throw new IllegalArgumentException("Invalid service name - must be non-null");
+            }
+            mServiceName = serviceName.getBytes(StandardCharsets.UTF_8);
+            return this;
+        }
+
+        /**
+         * Specify service specific information for the publish session. This is
+         * a free-form byte array available to the application to send
+         * additional information as part of the discovery operation - it
+         * will not be used to determine whether a publish/subscribe match
+         * occurs.
+         * <p>
+         *     Optional. Empty by default.
+         *
+         * @param serviceSpecificInfo A byte-array for the service-specific
+         *            information field.
+         *
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setServiceSpecificInfo(@Nullable byte[] serviceSpecificInfo) {
+            mServiceSpecificInfo = serviceSpecificInfo;
+            return this;
+        }
+
+        /**
+         * Specify service specific information for the publish session - a simple wrapper
+         * of {@link PublishConfig.Builder#setServiceSpecificInfo(byte[])}
+         * obtaining the data from a String.
+         * <p>
+         *     Optional. Empty by default.
+         *
+         * @param serviceSpecificInfoStr The service specific information string
+         *            to be included (as a byte array) in the publish
+         *            information.
+         *
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setServiceSpecificInfo(@NonNull String serviceSpecificInfoStr) {
+            mServiceSpecificInfo = serviceSpecificInfoStr.getBytes();
+            return this;
+        }
+
+        /**
+         * The match filter for a publish session. Used to determine whether a service
+         * discovery occurred - in addition to relying on the service name.
+         * <p>
+         * Format is an LV byte array: a single byte Length field followed by L bytes (the value of
+         * the Length field) of a value blob.
+         * <p>
+         *     Optional. Empty by default.
+         *
+         * @param matchFilter The byte-array containing the LV formatted match filter.
+         *
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setMatchFilter(@Nullable byte[] matchFilter) {
+            mMatchFilter = matchFilter;
+            return this;
+        }
+
+        /**
+         * Specify the type of the publish session: solicited (aka active - publish
+         * packets are transmitted over-the-air), or unsolicited (aka passive -
+         * no publish packets are transmitted, a match is made against an active
+         * subscribe session whose packets are transmitted over-the-air).
+         *
+         * @param publishType Publish session type:
+         *            {@link PublishConfig#PUBLISH_TYPE_SOLICITED} or
+         *            {@link PublishConfig#PUBLISH_TYPE_UNSOLICITED} (the default).
+         *
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setPublishType(@PublishTypes int publishType) {
+            if (publishType < PUBLISH_TYPE_UNSOLICITED || publishType > PUBLISH_TYPE_SOLICITED) {
+                throw new IllegalArgumentException("Invalid publishType - " + publishType);
+            }
+            mPublishType = publishType;
+            return this;
+        }
+
+        /**
+         * Sets the number of times an unsolicited (configured using
+         * {@link PublishConfig.Builder#setPublishType(int)}) publish session
+         * will be broadcast. When the count is reached an event will be
+         * generated for {@link WifiNanSessionCallback#onSessionTerminated(int)}
+         * with {@link WifiNanSessionCallback#TERMINATE_REASON_DONE} [unless
+         * {@link #setEnableTerminateNotification(boolean)} disables the callback].
+         * <p>
+         *     Optional. 0 by default - indicating the session doesn't terminate on its own.
+         *     Session will be terminated when {@link WifiNanSession#terminate()} is called.
+         *
+         * @param publishCount Number of publish packets to broadcast.
+         *
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setPublishCount(int publishCount) {
+            if (publishCount < 0) {
+                throw new IllegalArgumentException("Invalid publishCount - must be non-negative");
+            }
+            mPublishCount = publishCount;
+            return this;
+        }
+
+        /**
+         * Sets the time interval (in seconds) an unsolicited (
+         * {@link PublishConfig.Builder#setPublishType(int)}) publish session
+         * will be alive - broadcasting a packet. When the TTL is reached
+         * an event will be generated for
+         * {@link WifiNanSessionCallback#onSessionTerminated(int)} with
+         * {@link WifiNanSessionCallback#TERMINATE_REASON_DONE}  [unless
+         * {@link #setEnableTerminateNotification(boolean)} disables the callback].
+         * <p>
+         *     Optional. 0 by default - indicating the session doesn't terminate on its own.
+         *     Session will be terminated when {@link WifiNanSession#terminate()} is called.
+         *
+         * @param ttlSec Lifetime of a publish session in seconds.
+         *
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setTtlSec(int ttlSec) {
+            if (ttlSec < 0) {
+                throw new IllegalArgumentException("Invalid ttlSec - must be non-negative");
+            }
+            mTtlSec = ttlSec;
+            return this;
+        }
+
+        /**
+         * Configure whether a publish terminate notification
+         * {@link WifiNanSessionCallback#onSessionTerminated(int)} is reported
+         * back to the callback.
+         *
+         * @param enable If true the terminate callback will be called when the
+         *            publish is terminated. Otherwise it will not be called.
+         *
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setEnableTerminateNotification(boolean enable) {
+            mEnableTerminateNotification = enable;
+            return this;
+        }
+
+        /**
+         * Build {@link PublishConfig} given the current requests made on the
+         * builder.
+         */
+        public PublishConfig build() {
+            return new PublishConfig(mServiceName, mServiceSpecificInfo, mMatchFilter, mPublishType,
+                    mPublishCount, mTtlSec, mEnableTerminateNotification);
+        }
+    }
+}
diff --git a/wifi/java/android/net/wifi/nan/PublishData.aidl b/wifi/java/android/net/wifi/nan/PublishData.aidl
deleted file mode 100644
index 15e4ddf..0000000
--- a/wifi/java/android/net/wifi/nan/PublishData.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.wifi.nan;
-
-parcelable PublishData;
diff --git a/wifi/java/android/net/wifi/nan/PublishData.java b/wifi/java/android/net/wifi/nan/PublishData.java
deleted file mode 100644
index 80119eb..0000000
--- a/wifi/java/android/net/wifi/nan/PublishData.java
+++ /dev/null
@@ -1,343 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.wifi.nan;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.Arrays;
-
-/**
- * Defines the data for a NAN publish session. Built using
- * {@link PublishData.Builder}. Publish is done using
- * {@link WifiNanManager#publish(PublishData, PublishSettings, WifiNanSessionListener, int)}
- * or {@link WifiNanPublishSession#publish(PublishData, PublishSettings)}.
- * @hide PROPOSED_NAN_API
- */
-public class PublishData implements Parcelable {
-    /**
-     * @hide
-     */
-    public final String mServiceName;
-
-    /**
-     * @hide
-     */
-    public final int mServiceSpecificInfoLength;
-
-    /**
-     * @hide
-     */
-    public final byte[] mServiceSpecificInfo;
-
-    /**
-     * @hide
-     */
-    public final int mTxFilterLength;
-
-    /**
-     * @hide
-     */
-    public final byte[] mTxFilter;
-
-    /**
-     * @hide
-     */
-    public final int mRxFilterLength;
-
-    /**
-     * @hide
-     */
-    public final byte[] mRxFilter;
-
-    private PublishData(String serviceName, byte[] serviceSpecificInfo,
-            int serviceSpecificInfoLength, byte[] txFilter, int txFilterLength, byte[] rxFilter,
-            int rxFilterLength) {
-        mServiceName = serviceName;
-        mServiceSpecificInfoLength = serviceSpecificInfoLength;
-        mServiceSpecificInfo = serviceSpecificInfo;
-        mTxFilterLength = txFilterLength;
-        mTxFilter = txFilter;
-        mRxFilterLength = rxFilterLength;
-        mRxFilter = rxFilter;
-    }
-
-    @Override
-    public String toString() {
-        return "PublishData [mServiceName='" + mServiceName + "', mServiceSpecificInfo='"
-                + (new String(mServiceSpecificInfo, 0, mServiceSpecificInfoLength))
-                + "', mTxFilter="
-                + (new TlvBufferUtils.TlvIterable(0, 1, mTxFilter, mTxFilterLength)).toString()
-                + ", mRxFilter="
-                + (new TlvBufferUtils.TlvIterable(0, 1, mRxFilter, mRxFilterLength)).toString()
-                + "']";
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeString(mServiceName);
-        dest.writeInt(mServiceSpecificInfoLength);
-        if (mServiceSpecificInfoLength != 0) {
-            dest.writeByteArray(mServiceSpecificInfo, 0, mServiceSpecificInfoLength);
-        }
-        dest.writeInt(mTxFilterLength);
-        if (mTxFilterLength != 0) {
-            dest.writeByteArray(mTxFilter, 0, mTxFilterLength);
-        }
-        dest.writeInt(mRxFilterLength);
-        if (mRxFilterLength != 0) {
-            dest.writeByteArray(mRxFilter, 0, mRxFilterLength);
-        }
-    }
-
-    public static final Creator<PublishData> CREATOR = new Creator<PublishData>() {
-        @Override
-        public PublishData[] newArray(int size) {
-            return new PublishData[size];
-        }
-
-        @Override
-        public PublishData createFromParcel(Parcel in) {
-            String serviceName = in.readString();
-            int ssiLength = in.readInt();
-            byte[] ssi = new byte[ssiLength];
-            if (ssiLength != 0) {
-                in.readByteArray(ssi);
-            }
-            int txFilterLength = in.readInt();
-            byte[] txFilter = new byte[txFilterLength];
-            if (txFilterLength != 0) {
-                in.readByteArray(txFilter);
-            }
-            int rxFilterLength = in.readInt();
-            byte[] rxFilter = new byte[rxFilterLength];
-            if (rxFilterLength != 0) {
-                in.readByteArray(rxFilter);
-            }
-
-            return new PublishData(serviceName, ssi, ssiLength, txFilter, txFilterLength, rxFilter,
-                    rxFilterLength);
-        }
-    };
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-
-        if (!(o instanceof PublishData)) {
-            return false;
-        }
-
-        PublishData lhs = (PublishData) o;
-
-        if (!mServiceName.equals(lhs.mServiceName)
-                || mServiceSpecificInfoLength != lhs.mServiceSpecificInfoLength
-                || mTxFilterLength != lhs.mTxFilterLength
-                || mRxFilterLength != lhs.mRxFilterLength) {
-            return false;
-        }
-
-        if (mServiceSpecificInfo != null && lhs.mServiceSpecificInfo != null) {
-            for (int i = 0; i < mServiceSpecificInfoLength; ++i) {
-                if (mServiceSpecificInfo[i] != lhs.mServiceSpecificInfo[i]) {
-                    return false;
-                }
-            }
-        } else if (mServiceSpecificInfoLength != 0) {
-            return false; // invalid != invalid
-        }
-
-        if (mTxFilter != null && lhs.mTxFilter != null) {
-            for (int i = 0; i < mTxFilterLength; ++i) {
-                if (mTxFilter[i] != lhs.mTxFilter[i]) {
-                    return false;
-                }
-            }
-        } else if (mTxFilterLength != 0) {
-            return false; // invalid != invalid
-        }
-
-        if (mRxFilter != null && lhs.mRxFilter != null) {
-            for (int i = 0; i < mRxFilterLength; ++i) {
-                if (mRxFilter[i] != lhs.mRxFilter[i]) {
-                    return false;
-                }
-            }
-        } else if (mRxFilterLength != 0) {
-            return false; // invalid != invalid
-        }
-
-        return true;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = 17;
-
-        result = 31 * result + mServiceName.hashCode();
-        result = 31 * result + mServiceSpecificInfoLength;
-        result = 31 * result + Arrays.hashCode(mServiceSpecificInfo);
-        result = 31 * result + mTxFilterLength;
-        result = 31 * result + Arrays.hashCode(mTxFilter);
-        result = 31 * result + mRxFilterLength;
-        result = 31 * result + Arrays.hashCode(mRxFilter);
-
-        return result;
-    }
-
-    /**
-     * Builder used to build {@link PublishData} objects.
-     */
-    public static final class Builder {
-        private String mServiceName;
-        private int mServiceSpecificInfoLength;
-        private byte[] mServiceSpecificInfo = new byte[0];
-        private int mTxFilterLength;
-        private byte[] mTxFilter = new byte[0];
-        private int mRxFilterLength;
-        private byte[] mRxFilter = new byte[0];
-
-        /**
-         * Specify the service name of the publish session. The actual on-air
-         * value is a 6 byte hashed representation of this string.
-         *
-         * @param serviceName The service name for the publish session.
-         * @return The builder to facilitate chaining
-         *         {@code builder.setXXX(..).setXXX(..)}.
-         */
-        public Builder setServiceName(String serviceName) {
-            mServiceName = serviceName;
-            return this;
-        }
-
-        /**
-         * Specify service specific information for the publish session. This is
-         * a free-form byte array available to the application to send
-         * additional information as part of the discovery operation - i.e. it
-         * will not be used to determine whether a publish/subscribe match
-         * occurs.
-         *
-         * @param serviceSpecificInfo A byte-array for the service-specific
-         *            information field.
-         * @param serviceSpecificInfoLength The length of the byte-array to be
-         *            used.
-         * @return The builder to facilitate chaining
-         *         {@code builder.setXXX(..).setXXX(..)}.
-         */
-        public Builder setServiceSpecificInfo(byte[] serviceSpecificInfo,
-                int serviceSpecificInfoLength) {
-            if (serviceSpecificInfoLength != 0 && (serviceSpecificInfo == null
-                    || serviceSpecificInfo.length < serviceSpecificInfoLength)) {
-                throw new IllegalArgumentException("Non-matching combination of "
-                        + "serviceSpecificInfo and serviceSpecificInfoLength");
-            }
-            mServiceSpecificInfoLength = serviceSpecificInfoLength;
-            mServiceSpecificInfo = serviceSpecificInfo;
-            return this;
-        }
-
-        /**
-         * Specify service specific information for the publish session - same
-         * as {@link PublishData.Builder#setServiceSpecificInfo(byte[], int)}
-         * but obtaining the data from a String.
-         *
-         * @param serviceSpecificInfoStr The service specific information string
-         *            to be included (as a byte array) in the publish
-         *            information.
-         * @return The builder to facilitate chaining
-         *         {@code builder.setXXX(..).setXXX(..)}.
-         */
-        public Builder setServiceSpecificInfo(String serviceSpecificInfoStr) {
-            mServiceSpecificInfoLength = serviceSpecificInfoStr.length();
-            mServiceSpecificInfo = serviceSpecificInfoStr.getBytes();
-            return this;
-        }
-
-        /**
-         * The transmit filter for an active publish session
-         * {@link PublishSettings.Builder#setPublishType(int)} and
-         * {@link PublishSettings#PUBLISH_TYPE_UNSOLICITED}. Included in
-         * transmitted publish packets and used by receivers (subscribers) to
-         * determine whether they match - in addition to just relying on the
-         * service name.
-         * <p>
-         * Format is an LV byte array - the {@link TlvBufferUtils} utility class
-         * is available to form and parse.
-         *
-         * @param txFilter The byte-array containing the LV formatted transmit
-         *            filter.
-         * @param txFilterLength The number of bytes in the transmit filter
-         *            argument.
-         * @return The builder to facilitate chaining
-         *         {@code builder.setXXX(..).setXXX(..)}.
-         */
-        public Builder setTxFilter(byte[] txFilter, int txFilterLength) {
-            if (txFilterLength != 0 && (txFilter == null || txFilter.length < txFilterLength)) {
-                throw new IllegalArgumentException(
-                        "Non-matching combination of txFilter and txFilterLength");
-            }
-            mTxFilter = txFilter;
-            mTxFilterLength = txFilterLength;
-            return this;
-        }
-
-        /**
-         * The transmit filter for a passive publish session
-         * {@link PublishSettings.Builder#setPublishType(int)} and
-         * {@link PublishSettings#PUBLISH_TYPE_SOLICITED}. Used by the publisher
-         * to determine whether they match transmitted subscriber packets
-         * (active subscribers) - in addition to just relying on the service
-         * name.
-         * <p>
-         * Format is an LV byte array - the {@link TlvBufferUtils} utility class
-         * is available to form and parse.
-         *
-         * @param rxFilter The byte-array containing the LV formatted receive
-         *            filter.
-         * @param rxFilterLength The number of bytes in the receive filter
-         *            argument.
-         * @return The builder to facilitate chaining
-         *         {@code builder.setXXX(..).setXXX(..)}.
-         */
-        public Builder setRxFilter(byte[] rxFilter, int rxFilterLength) {
-            if (rxFilterLength != 0 && (rxFilter == null || rxFilter.length < rxFilterLength)) {
-                throw new IllegalArgumentException(
-                        "Non-matching combination of rxFilter and rxFilterLength");
-            }
-            mRxFilter = rxFilter;
-            mRxFilterLength = rxFilterLength;
-            return this;
-        }
-
-        /**
-         * Build {@link PublishData} given the current requests made on the
-         * builder.
-         */
-        public PublishData build() {
-            return new PublishData(mServiceName, mServiceSpecificInfo, mServiceSpecificInfoLength,
-                    mTxFilter, mTxFilterLength, mRxFilter, mRxFilterLength);
-        }
-    }
-}
diff --git a/wifi/java/android/net/wifi/nan/PublishSettings.aidl b/wifi/java/android/net/wifi/nan/PublishSettings.aidl
deleted file mode 100644
index ff69293..0000000
--- a/wifi/java/android/net/wifi/nan/PublishSettings.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.wifi.nan;
-
-parcelable PublishSettings;
diff --git a/wifi/java/android/net/wifi/nan/PublishSettings.java b/wifi/java/android/net/wifi/nan/PublishSettings.java
deleted file mode 100644
index bbc5340..0000000
--- a/wifi/java/android/net/wifi/nan/PublishSettings.java
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.wifi.nan;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Defines the settings (configuration) for a NAN publish session. Built using
- * {@link PublishSettings.Builder}. Publish is done using
- * {@link WifiNanManager#publish(PublishData, PublishSettings, WifiNanSessionListener, int)}
- * or {@link WifiNanPublishSession#publish(PublishData, PublishSettings)}.
- *
- * @hide PROPOSED_NAN_API
- */
-public class PublishSettings implements Parcelable {
-
-    /**
-     * Defines an unsolicited publish session - i.e. a publish session where
-     * publish packets are transmitted over-the-air. Configuration is done using
-     * {@link PublishSettings.Builder#setPublishType(int)}.
-     */
-    public static final int PUBLISH_TYPE_UNSOLICITED = 0;
-
-    /**
-     * Defines a solicited publish session - i.e. a publish session where
-     * publish packets are not transmitted over-the-air and the device listens
-     * and matches to transmitted subscribe packets. Configuration is done using
-     * {@link PublishSettings.Builder#setPublishType(int)}.
-     */
-    public static final int PUBLISH_TYPE_SOLICITED = 1;
-
-    /**
-     * @hide
-     */
-    public final int mPublishType;
-
-    /**
-     * @hide
-     */
-    public final int mPublishCount;
-
-    /**
-     * @hide
-     */
-    public final int mTtlSec;
-
-    private PublishSettings(int publishType, int publichCount, int ttlSec) {
-        mPublishType = publishType;
-        mPublishCount = publichCount;
-        mTtlSec = ttlSec;
-    }
-
-    @Override
-    public String toString() {
-        return "PublishSettings [mPublishType=" + mPublishType + ", mPublishCount=" + mPublishCount
-                + ", mTtlSec=" + mTtlSec + "]";
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(mPublishType);
-        dest.writeInt(mPublishCount);
-        dest.writeInt(mTtlSec);
-    }
-
-    public static final Creator<PublishSettings> CREATOR = new Creator<PublishSettings>() {
-        @Override
-        public PublishSettings[] newArray(int size) {
-            return new PublishSettings[size];
-        }
-
-        @Override
-        public PublishSettings createFromParcel(Parcel in) {
-            int publishType = in.readInt();
-            int publishCount = in.readInt();
-            int ttlSec = in.readInt();
-            return new PublishSettings(publishType, publishCount, ttlSec);
-        }
-    };
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-
-        if (!(o instanceof PublishSettings)) {
-            return false;
-        }
-
-        PublishSettings lhs = (PublishSettings) o;
-
-        return mPublishType == lhs.mPublishType && mPublishCount == lhs.mPublishCount
-                && mTtlSec == lhs.mTtlSec;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = 17;
-
-        result = 31 * result + mPublishType;
-        result = 31 * result + mPublishCount;
-        result = 31 * result + mTtlSec;
-
-        return result;
-    }
-
-    /**
-     * Builder used to build {@link PublishSettings} objects.
-     */
-    public static final class Builder {
-        int mPublishType;
-        int mPublishCount;
-        int mTtlSec;
-
-        /**
-         * Sets the type of the publish session: solicited (aka active - publish
-         * packets are transmitted over-the-air), or unsolicited (aka passive -
-         * no publish packets are transmitted, a match is made against an active
-         * subscribe session whose packets are transmitted over-the-air).
-         *
-         * @param publishType Publish session type: solicited (
-         *            {@link PublishSettings#PUBLISH_TYPE_SOLICITED}) or
-         *            unsolicited (
-         *            {@link PublishSettings#PUBLISH_TYPE_UNSOLICITED}).
-         * @return The builder to facilitate chaining
-         *         {@code builder.setXXX(..).setXXX(..)}.
-         */
-        public Builder setPublishType(int publishType) {
-            if (publishType < PUBLISH_TYPE_UNSOLICITED || publishType > PUBLISH_TYPE_SOLICITED) {
-                throw new IllegalArgumentException("Invalid publishType - " + publishType);
-            }
-            mPublishType = publishType;
-            return this;
-        }
-
-        /**
-         * Sets the number of times a solicited (
-         * {@link PublishSettings.Builder#setPublishType(int)}) publish session
-         * will transmit a packet. When the count is reached an event will be
-         * generated for {@link WifiNanSessionListener#onPublishTerminated(int)}
-         * with reason={@link WifiNanSessionListener#TERMINATE_REASON_DONE}.
-         *
-         * @param publishCount Number of publish packets to transmit.
-         * @return The builder to facilitate chaining
-         *         {@code builder.setXXX(..).setXXX(..)}.
-         */
-        public Builder setPublishCount(int publishCount) {
-            if (publishCount < 0) {
-                throw new IllegalArgumentException("Invalid publishCount - must be non-negative");
-            }
-            mPublishCount = publishCount;
-            return this;
-        }
-
-        /**
-         * Sets the time interval (in seconds) a solicited (
-         * {@link PublishSettings.Builder#setPublishCount(int)}) publish session
-         * will be alive - i.e. transmitting a packet. When the TTL is reached
-         * an event will be generated for
-         * {@link WifiNanSessionListener#onPublishTerminated(int)} with reason=
-         * {@link WifiNanSessionListener#TERMINATE_REASON_DONE}.
-         *
-         * @param ttlSec Lifetime of a publish session in seconds.
-         * @return The builder to facilitate chaining
-         *         {@code builder.setXXX(..).setXXX(..)}.
-         */
-        public Builder setTtlSec(int ttlSec) {
-            if (ttlSec < 0) {
-                throw new IllegalArgumentException("Invalid ttlSec - must be non-negative");
-            }
-            mTtlSec = ttlSec;
-            return this;
-        }
-
-        /**
-         * Build {@link PublishSettings} given the current requests made on the
-         * builder.
-         */
-        public PublishSettings build() {
-            return new PublishSettings(mPublishType, mPublishCount, mTtlSec);
-        }
-    }
-}
diff --git a/wifi/java/android/net/wifi/nan/SubscribeSettings.aidl b/wifi/java/android/net/wifi/nan/SubscribeConfig.aidl
similarity index 95%
copy from wifi/java/android/net/wifi/nan/SubscribeSettings.aidl
copy to wifi/java/android/net/wifi/nan/SubscribeConfig.aidl
index 44849bc..92344a4 100644
--- a/wifi/java/android/net/wifi/nan/SubscribeSettings.aidl
+++ b/wifi/java/android/net/wifi/nan/SubscribeConfig.aidl
@@ -16,4 +16,4 @@
 
 package android.net.wifi.nan;
 
-parcelable SubscribeSettings;
+parcelable SubscribeConfig;
diff --git a/wifi/java/android/net/wifi/nan/SubscribeConfig.java b/wifi/java/android/net/wifi/nan/SubscribeConfig.java
new file mode 100644
index 0000000..7904875
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/SubscribeConfig.java
@@ -0,0 +1,444 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nan;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import libcore.util.HexEncoding;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+/**
+ * Defines the configuration of a NAN subscribe session. Built using
+ * {@link SubscribeConfig.Builder}. Subscribe is done using
+ * {@link WifiNanManager#subscribe(SubscribeConfig, WifiNanSessionCallback)} or
+ * {@link WifiNanSubscribeSession#updateSubscribe(SubscribeConfig)}.
+ *
+ * @hide PROPOSED_NAN_API
+ */
+public class SubscribeConfig implements Parcelable {
+    /** @hide */
+    @IntDef({
+            SUBSCRIBE_TYPE_PASSIVE, SUBSCRIBE_TYPE_ACTIVE })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SubscribeTypes {
+    }
+
+    /**
+     * Defines a passive subscribe session - a subscribe session where
+     * subscribe packets are not transmitted over-the-air and the device listens
+     * and matches to transmitted publish packets. Configuration is done using
+     * {@link SubscribeConfig.Builder#setSubscribeType(int)}.
+     */
+    public static final int SUBSCRIBE_TYPE_PASSIVE = 0;
+
+    /**
+     * Defines an active subscribe session - a subscribe session where
+     * subscribe packets are transmitted over-the-air. Configuration is done
+     * using {@link SubscribeConfig.Builder#setSubscribeType(int)}.
+     */
+    public static final int SUBSCRIBE_TYPE_ACTIVE = 1;
+
+    /** @hide */
+    @IntDef({
+            MATCH_STYLE_FIRST_ONLY, MATCH_STYLE_ALL })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface MatchStyles {
+    }
+
+    /**
+     * Specifies that only the first match of a set of identical matches (same
+     * publish) will be reported to the subscriber. Configuration is done using
+     * {@link SubscribeConfig.Builder#setMatchStyle(int)}.
+     */
+    public static final int MATCH_STYLE_FIRST_ONLY = 0;
+
+    /**
+     * Specifies that all matches of a set of identical matches (same publish)
+     * will be reported to the subscriber. Configuration is done using
+     * {@link SubscribeConfig.Builder#setMatchStyle(int)}.
+     */
+    public static final int MATCH_STYLE_ALL = 1;
+
+    /** @hide */
+    public final byte[] mServiceName;
+
+    /** @hide */
+    public final byte[] mServiceSpecificInfo;
+
+    /** @hide */
+    public final byte[] mMatchFilter;
+
+    /** @hide */
+    public final int mSubscribeType;
+
+    /** @hide */
+    public final int mSubscribeCount;
+
+    /** @hide */
+    public final int mTtlSec;
+
+    /** @hide */
+    public final int mMatchStyle;
+
+    /** @hide */
+    public final boolean mEnableTerminateNotification;
+
+    private SubscribeConfig(byte[] serviceName, byte[] serviceSpecificInfo, byte[] matchFilter,
+            int subscribeType, int publichCount, int ttlSec, int matchStyle,
+            boolean enableTerminateNotification) {
+        mServiceName = serviceName;
+        mServiceSpecificInfo = serviceSpecificInfo;
+        mMatchFilter = matchFilter;
+        mSubscribeType = subscribeType;
+        mSubscribeCount = publichCount;
+        mTtlSec = ttlSec;
+        mMatchStyle = matchStyle;
+        mEnableTerminateNotification = enableTerminateNotification;
+    }
+
+    @Override
+    public String toString() {
+        return "SubscribeConfig [mServiceName='" + mServiceName + ", mServiceSpecificInfo='" + (
+                (mServiceSpecificInfo == null) ? "null" : HexEncoding.encode(mServiceSpecificInfo))
+                + ", mMatchFilter=" + (new LvBufferUtils.LvIterable(1, mMatchFilter)).toString()
+                + ", mSubscribeType=" + mSubscribeType + ", mSubscribeCount=" + mSubscribeCount
+                + ", mTtlSec=" + mTtlSec + ", mMatchType=" + mMatchStyle
+                + ", mEnableTerminateNotification=" + mEnableTerminateNotification + "]";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeByteArray(mServiceName);
+        dest.writeByteArray(mServiceSpecificInfo);
+        dest.writeByteArray(mMatchFilter);
+        dest.writeInt(mSubscribeType);
+        dest.writeInt(mSubscribeCount);
+        dest.writeInt(mTtlSec);
+        dest.writeInt(mMatchStyle);
+        dest.writeInt(mEnableTerminateNotification ? 1 : 0);
+    }
+
+    public static final Creator<SubscribeConfig> CREATOR = new Creator<SubscribeConfig>() {
+        @Override
+        public SubscribeConfig[] newArray(int size) {
+            return new SubscribeConfig[size];
+        }
+
+        @Override
+        public SubscribeConfig createFromParcel(Parcel in) {
+            byte[] serviceName = in.createByteArray();
+            byte[] ssi = in.createByteArray();
+            byte[] matchFilter = in.createByteArray();
+            int subscribeType = in.readInt();
+            int subscribeCount = in.readInt();
+            int ttlSec = in.readInt();
+            int matchStyle = in.readInt();
+            boolean enableTerminateNotification = in.readInt() != 0;
+
+            return new SubscribeConfig(serviceName, ssi, matchFilter, subscribeType, subscribeCount,
+                    ttlSec, matchStyle, enableTerminateNotification);
+        }
+    };
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+
+        if (!(o instanceof SubscribeConfig)) {
+            return false;
+        }
+
+        SubscribeConfig lhs = (SubscribeConfig) o;
+
+        return Arrays.equals(mServiceName, lhs.mServiceName) && Arrays.equals(mServiceSpecificInfo,
+                lhs.mServiceSpecificInfo) && Arrays.equals(mMatchFilter, lhs.mMatchFilter)
+                && mSubscribeType == lhs.mSubscribeType && mSubscribeCount == lhs.mSubscribeCount
+                && mTtlSec == lhs.mTtlSec && mMatchStyle == lhs.mMatchStyle
+                && mEnableTerminateNotification == lhs.mEnableTerminateNotification;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+
+        result = 31 * result + Arrays.hashCode(mServiceName);
+        result = 31 * result + Arrays.hashCode(mServiceSpecificInfo);
+        result = 31 * result + Arrays.hashCode(mMatchFilter);
+        result = 31 * result + mSubscribeType;
+        result = 31 * result + mSubscribeCount;
+        result = 31 * result + mTtlSec;
+        result = 31 * result + mMatchStyle;
+        result = 31 * result + (mEnableTerminateNotification ? 1 : 0);
+
+        return result;
+    }
+
+    /**
+     * Verifies that the contents of the SubscribeConfig are valid. Otherwise
+     * throws an IllegalArgumentException.
+     *
+     * @hide
+     */
+    public void validate() throws IllegalArgumentException {
+        WifiNanUtils.validateServiceName(mServiceName);
+
+        if (!LvBufferUtils.isValid(mMatchFilter, 1)) {
+            throw new IllegalArgumentException(
+                    "Invalid matchFilter configuration - LV fields do not match up to length");
+        }
+        if (mSubscribeType < SUBSCRIBE_TYPE_PASSIVE || mSubscribeType > SUBSCRIBE_TYPE_ACTIVE) {
+            throw new IllegalArgumentException("Invalid subscribeType - " + mSubscribeType);
+        }
+        if (mSubscribeCount < 0) {
+            throw new IllegalArgumentException("Invalid subscribeCount - must be non-negative");
+        }
+        if (mTtlSec < 0) {
+            throw new IllegalArgumentException("Invalid ttlSec - must be non-negative");
+        }
+        if (mMatchStyle != MATCH_STYLE_FIRST_ONLY && mMatchStyle != MATCH_STYLE_ALL) {
+            throw new IllegalArgumentException(
+                    "Invalid matchType - must be MATCH_FIRST_ONLY or MATCH_ALL");
+        }
+    }
+
+    /**
+     * Builder used to build {@link SubscribeConfig} objects.
+     */
+    public static final class Builder {
+        private byte[] mServiceName;
+        private byte[] mServiceSpecificInfo;
+        private byte[] mMatchFilter;
+        private int mSubscribeType = SUBSCRIBE_TYPE_PASSIVE;
+        private int mSubscribeCount = 0;
+        private int mTtlSec = 0;
+        private int mMatchStyle = MATCH_STYLE_ALL;
+        private boolean mEnableTerminateNotification = true;
+
+        /**
+         * Specify the service name of the subscribe session. The actual on-air
+         * value is a 6 byte hashed representation of this string.
+         * <p>
+         * The Service Name is a UTF-8 encoded string from 1 to 255 bytes in length.
+         * The only acceptable single-byte UTF-8 symbols for a Service Name are alphanumeric
+         * values (A-Z, a-z, 0-9), the hyphen ('-'), and the period ('.'). All valid multi-byte
+         * UTF-8 characters are acceptable in a Service Name.
+         * <p>
+         * Must be called - an empty ServiceName is not valid.
+         *
+         * @param serviceName The service name for the subscribe session.
+         *
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setServiceName(@NonNull String serviceName) {
+            if (serviceName == null) {
+                throw new IllegalArgumentException("Invalid service name - must be non-null");
+            }
+            mServiceName = serviceName.getBytes(StandardCharsets.UTF_8);
+            return this;
+        }
+
+        /**
+         * Specify service specific information for the subscribe session. This is
+         * a free-form byte array available to the application to send
+         * additional information as part of the discovery operation - i.e. it
+         * will not be used to determine whether a publish/subscribe match
+         * occurs.
+         * <p>
+         *     Optional. Empty by default.
+         *
+         * @param serviceSpecificInfo A byte-array for the service-specific
+         *            information field.
+         *
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setServiceSpecificInfo(@Nullable byte[] serviceSpecificInfo) {
+            mServiceSpecificInfo = serviceSpecificInfo;
+            return this;
+        }
+
+        /**
+         * Specify service specific information for the subscribe session - a simple wrapper
+         * of {@link SubscribeConfig.Builder#setServiceSpecificInfo(byte[])}
+         * obtaining the data from a String.
+         * <p>
+         *     Optional. Empty by default.
+         *
+         * @param serviceSpecificInfoStr The service specific information string
+         *            to be included (as a byte array) in the subscribe
+         *            information.
+         *
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setServiceSpecificInfo(@NonNull String serviceSpecificInfoStr) {
+            mServiceSpecificInfo = serviceSpecificInfoStr.getBytes();
+            return this;
+        }
+
+        /**
+         * The match filter for a subscribe session. Used to determine whether a service
+         * discovery occurred - in addition to relying on the service name.
+         * <p>
+         * Format is an LV byte array: a single byte Length field followed by L bytes (the value of
+         * the Length field) of a value blob.
+         * <p>
+         *     Optional. Empty by default.
+         *
+         * @param matchFilter The byte-array containing the LV formatted match filter.
+         *
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setMatchFilter(@Nullable byte[] matchFilter) {
+            mMatchFilter = matchFilter;
+            return this;
+        }
+
+        /**
+         * Sets the type of the subscribe session: active (subscribe packets are
+         * transmitted over-the-air), or passive (no subscribe packets are
+         * transmitted, a match is made against a solicited/active publish
+         * session whose packets are transmitted over-the-air).
+         *
+         * @param subscribeType Subscribe session type:
+         *            {@link SubscribeConfig#SUBSCRIBE_TYPE_ACTIVE} or
+         *            {@link SubscribeConfig#SUBSCRIBE_TYPE_PASSIVE}.
+         *
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setSubscribeType(@SubscribeTypes int subscribeType) {
+            if (subscribeType < SUBSCRIBE_TYPE_PASSIVE || subscribeType > SUBSCRIBE_TYPE_ACTIVE) {
+                throw new IllegalArgumentException("Invalid subscribeType - " + subscribeType);
+            }
+            mSubscribeType = subscribeType;
+            return this;
+        }
+
+        /**
+         * Sets the number of times an active (
+         * {@link SubscribeConfig.Builder#setSubscribeType(int)}) subscribe session
+         * will broadcast. When the count is reached an event will be
+         * generated for {@link WifiNanSessionCallback#onSessionTerminated(int)}
+         * with {@link WifiNanSessionCallback#TERMINATE_REASON_DONE}.
+         * <p>
+         *     Optional. 0 by default - indicating the session doesn't terminate on its own.
+         *     Session will be terminated when {@link WifiNanSession#terminate()} is called.
+         *
+         * @param subscribeCount Number of subscribe packets to broadcast.
+         *
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setSubscribeCount(int subscribeCount) {
+            if (subscribeCount < 0) {
+                throw new IllegalArgumentException("Invalid subscribeCount - must be non-negative");
+            }
+            mSubscribeCount = subscribeCount;
+            return this;
+        }
+
+        /**
+         * Sets the time interval (in seconds) an active (
+         * {@link SubscribeConfig.Builder#setSubscribeType(int)}) subscribe session
+         * will be alive - i.e. broadcasting a packet. When the TTL is reached
+         * an event will be generated for
+         * {@link WifiNanSessionCallback#onSessionTerminated(int)} with
+         * {@link WifiNanSessionCallback#TERMINATE_REASON_DONE}.
+         * <p>
+         *     Optional. 0 by default - indicating the session doesn't terminate on its own.
+         *     Session will be terminated when {@link WifiNanSession#terminate()} is called.
+         *
+         * @param ttlSec Lifetime of a subscribe session in seconds.
+         *
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setTtlSec(int ttlSec) {
+            if (ttlSec < 0) {
+                throw new IllegalArgumentException("Invalid ttlSec - must be non-negative");
+            }
+            mTtlSec = ttlSec;
+            return this;
+        }
+
+        /**
+         * Sets the match style of the subscription - how are matches from a
+         * single match session (corresponding to the same publish action on the
+         * peer) reported to the host (using the
+         * {@link WifiNanSessionCallback#onMatch(int, byte[], byte[])}
+         * ). The options are: only report the first match and ignore the rest
+         * {@link SubscribeConfig#MATCH_STYLE_FIRST_ONLY} or report every single
+         * match {@link SubscribeConfig#MATCH_STYLE_ALL} (the default).
+         *
+         * @param matchStyle The reporting style for the discovery match.
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setMatchStyle(@MatchStyles int matchStyle) {
+            if (matchStyle != MATCH_STYLE_FIRST_ONLY && matchStyle != MATCH_STYLE_ALL) {
+                throw new IllegalArgumentException(
+                        "Invalid matchType - must be MATCH_FIRST_ONLY or MATCH_ALL");
+            }
+            mMatchStyle = matchStyle;
+            return this;
+        }
+
+        /**
+         * Configure whether a subscribe terminate notification
+         * {@link WifiNanSessionCallback#onSessionTerminated(int)} is reported
+         * back to the callback.
+         *
+         * @param enable If true the terminate callback will be called when the
+         *            subscribe is terminated. Otherwise it will not be called.
+         *
+         * @return The builder to facilitate chaining
+         *         {@code builder.setXXX(..).setXXX(..)}.
+         */
+        public Builder setEnableTerminateNotification(boolean enable) {
+            mEnableTerminateNotification = enable;
+            return this;
+        }
+
+        /**
+         * Build {@link SubscribeConfig} given the current requests made on the
+         * builder.
+         */
+        public SubscribeConfig build() {
+            return new SubscribeConfig(mServiceName, mServiceSpecificInfo, mMatchFilter,
+                    mSubscribeType, mSubscribeCount, mTtlSec, mMatchStyle,
+                    mEnableTerminateNotification);
+        }
+    }
+}
diff --git a/wifi/java/android/net/wifi/nan/SubscribeData.aidl b/wifi/java/android/net/wifi/nan/SubscribeData.aidl
deleted file mode 100644
index 662fdb8..0000000
--- a/wifi/java/android/net/wifi/nan/SubscribeData.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.wifi.nan;
-
-parcelable SubscribeData;
diff --git a/wifi/java/android/net/wifi/nan/SubscribeData.java b/wifi/java/android/net/wifi/nan/SubscribeData.java
deleted file mode 100644
index cd6e918..0000000
--- a/wifi/java/android/net/wifi/nan/SubscribeData.java
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.wifi.nan;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.Arrays;
-
-/**
- * Defines the data for a NAN subscribe session. Built using
- * {@link SubscribeData.Builder}. Subscribe is done using
- * {@link WifiNanManager#subscribe(SubscribeData, SubscribeSettings, WifiNanSessionListener, int)}
- * or
- * {@link WifiNanSubscribeSession#subscribe(SubscribeData, SubscribeSettings)}.
- * @hide PROPOSED_NAN_API
- */
-public class SubscribeData implements Parcelable {
-    /**
-     * @hide
-     */
-    public final String mServiceName;
-
-    /**
-     * @hide
-     */
-    public final int mServiceSpecificInfoLength;
-
-    /**
-     * @hide
-     */
-    public final byte[] mServiceSpecificInfo;
-
-    /**
-     * @hide
-     */
-    public final int mTxFilterLength;
-
-    /**
-     * @hide
-     */
-    public final byte[] mTxFilter;
-
-    /**
-     * @hide
-     */
-    public final int mRxFilterLength;
-
-    /**
-     * @hide
-     */
-    public final byte[] mRxFilter;
-
-    private SubscribeData(String serviceName, byte[] serviceSpecificInfo,
-            int serviceSpecificInfoLength, byte[] txFilter, int txFilterLength, byte[] rxFilter,
-            int rxFilterLength) {
-        mServiceName = serviceName;
-        mServiceSpecificInfoLength = serviceSpecificInfoLength;
-        mServiceSpecificInfo = serviceSpecificInfo;
-        mTxFilterLength = txFilterLength;
-        mTxFilter = txFilter;
-        mRxFilterLength = rxFilterLength;
-        mRxFilter = rxFilter;
-    }
-
-    @Override
-    public String toString() {
-        return "SubscribeData [mServiceName='" + mServiceName + "', mServiceSpecificInfo='"
-                + (new String(mServiceSpecificInfo, 0, mServiceSpecificInfoLength))
-                + "', mTxFilter="
-                + (new TlvBufferUtils.TlvIterable(0, 1, mTxFilter, mTxFilterLength)).toString()
-                + ", mRxFilter="
-                + (new TlvBufferUtils.TlvIterable(0, 1, mRxFilter, mRxFilterLength)).toString()
-                + "']";
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeString(mServiceName);
-        dest.writeInt(mServiceSpecificInfoLength);
-        if (mServiceSpecificInfoLength != 0) {
-            dest.writeByteArray(mServiceSpecificInfo, 0, mServiceSpecificInfoLength);
-        }
-        dest.writeInt(mTxFilterLength);
-        if (mTxFilterLength != 0) {
-            dest.writeByteArray(mTxFilter, 0, mTxFilterLength);
-        }
-        dest.writeInt(mRxFilterLength);
-        if (mRxFilterLength != 0) {
-            dest.writeByteArray(mRxFilter, 0, mRxFilterLength);
-        }
-    }
-
-    public static final Creator<SubscribeData> CREATOR = new Creator<SubscribeData>() {
-        @Override
-        public SubscribeData[] newArray(int size) {
-            return new SubscribeData[size];
-        }
-
-        @Override
-        public SubscribeData createFromParcel(Parcel in) {
-            String serviceName = in.readString();
-            int ssiLength = in.readInt();
-            byte[] ssi = new byte[ssiLength];
-            if (ssiLength != 0) {
-                in.readByteArray(ssi);
-            }
-            int txFilterLength = in.readInt();
-            byte[] txFilter = new byte[txFilterLength];
-            if (txFilterLength != 0) {
-                in.readByteArray(txFilter);
-            }
-            int rxFilterLength = in.readInt();
-            byte[] rxFilter = new byte[rxFilterLength];
-            if (rxFilterLength != 0) {
-                in.readByteArray(rxFilter);
-            }
-
-            return new SubscribeData(serviceName, ssi, ssiLength, txFilter, txFilterLength,
-                    rxFilter, rxFilterLength);
-        }
-    };
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-
-        if (!(o instanceof SubscribeData)) {
-            return false;
-        }
-
-        SubscribeData lhs = (SubscribeData) o;
-
-        if (!mServiceName.equals(lhs.mServiceName)
-                || mServiceSpecificInfoLength != lhs.mServiceSpecificInfoLength
-                || mTxFilterLength != lhs.mTxFilterLength
-                || mRxFilterLength != lhs.mRxFilterLength) {
-            return false;
-        }
-
-        if (mServiceSpecificInfo != null && lhs.mServiceSpecificInfo != null) {
-            for (int i = 0; i < mServiceSpecificInfoLength; ++i) {
-                if (mServiceSpecificInfo[i] != lhs.mServiceSpecificInfo[i]) {
-                    return false;
-                }
-            }
-        } else if (mServiceSpecificInfoLength != 0) {
-            return false; // invalid != invalid
-        }
-
-        if (mTxFilter != null && lhs.mTxFilter != null) {
-            for (int i = 0; i < mTxFilterLength; ++i) {
-                if (mTxFilter[i] != lhs.mTxFilter[i]) {
-                    return false;
-                }
-            }
-        } else if (mTxFilterLength != 0) {
-            return false; // invalid != invalid
-        }
-
-        if (mRxFilter != null && lhs.mRxFilter != null) {
-            for (int i = 0; i < mRxFilterLength; ++i) {
-                if (mRxFilter[i] != lhs.mRxFilter[i]) {
-                    return false;
-                }
-            }
-        } else if (mRxFilterLength != 0) {
-            return false; // invalid != invalid
-        }
-
-        return true;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = 17;
-
-        result = 31 * result + mServiceName.hashCode();
-        result = 31 * result + mServiceSpecificInfoLength;
-        result = 31 * result + Arrays.hashCode(mServiceSpecificInfo);
-        result = 31 * result + mTxFilterLength;
-        result = 31 * result + Arrays.hashCode(mTxFilter);
-        result = 31 * result + mRxFilterLength;
-        result = 31 * result + Arrays.hashCode(mRxFilter);
-
-        return result;
-    }
-
-    /**
-     * Builder used to build {@link SubscribeData} objects.
-     */
-    public static final class Builder {
-        private String mServiceName;
-        private int mServiceSpecificInfoLength;
-        private byte[] mServiceSpecificInfo = new byte[0];
-        private int mTxFilterLength;
-        private byte[] mTxFilter = new byte[0];
-        private int mRxFilterLength;
-        private byte[] mRxFilter = new byte[0];
-
-        /**
-         * Specify the service name of the subscribe session. The actual on-air
-         * value is a 6 byte hashed representation of this string.
-         *
-         * @param serviceName The service name for the subscribe session.
-         * @return The builder to facilitate chaining
-         *         {@code builder.setXXX(..).setXXX(..)}.
-         */
-        public Builder setServiceName(String serviceName) {
-            mServiceName = serviceName;
-            return this;
-        }
-
-        /**
-         * Specify service specific information for the subscribe session. This
-         * is a free-form byte array available to the application to send
-         * additional information as part of the discovery operation - i.e. it
-         * will not be used to determine whether a publish/subscribe match
-         * occurs.
-         *
-         * @param serviceSpecificInfo A byte-array for the service-specific
-         *            information field.
-         * @param serviceSpecificInfoLength The length of the byte-array to be
-         *            used.
-         * @return The builder to facilitate chaining
-         *         {@code builder.setXXX(..).setXXX(..)}.
-         */
-        public Builder setServiceSpecificInfo(byte[] serviceSpecificInfo,
-                int serviceSpecificInfoLength) {
-            mServiceSpecificInfoLength = serviceSpecificInfoLength;
-            mServiceSpecificInfo = serviceSpecificInfo;
-            return this;
-        }
-
-        /**
-         * Specify service specific information for the subscribe session - same
-         * as {@link SubscribeData.Builder#setServiceSpecificInfo(byte[], int)}
-         * but obtaining the data from a String.
-         *
-         * @param serviceSpecificInfoStr The service specific information string
-         *            to be included (as a byte array) in the subscribe
-         *            information.
-         * @return The builder to facilitate chaining
-         *         {@code builder.setXXX(..).setXXX(..)}.
-         */
-        public Builder setServiceSpecificInfo(String serviceSpecificInfoStr) {
-            mServiceSpecificInfoLength = serviceSpecificInfoStr.length();
-            mServiceSpecificInfo = serviceSpecificInfoStr.getBytes();
-            return this;
-        }
-
-        /**
-         * The transmit filter for an active subscribe session
-         * {@link SubscribeSettings.Builder#setSubscribeType(int)} and
-         * {@link SubscribeSettings#SUBSCRIBE_TYPE_ACTIVE}. Included in
-         * transmitted subscribe packets and used by receivers (passive
-         * publishers) to determine whether they match - in addition to just
-         * relying on the service name.
-         * <p>
-         * Format is an LV byte array - the {@link TlvBufferUtils} utility class
-         * is available to form and parse.
-         *
-         * @param txFilter The byte-array containing the LV formatted transmit
-         *            filter.
-         * @param txFilterLength The number of bytes in the transmit filter
-         *            argument.
-         * @return The builder to facilitate chaining
-         *         {@code builder.setXXX(..).setXXX(..)}.
-         */
-        public Builder setTxFilter(byte[] txFilter, int txFilterLength) {
-            mTxFilter = txFilter;
-            mTxFilterLength = txFilterLength;
-            return this;
-        }
-
-        /**
-         * The transmit filter for a passive subsribe session
-         * {@link SubscribeSettings.Builder#setSubscribeType(int)} and
-         * {@link SubscribeSettings#SUBSCRIBE_TYPE_PASSIVE}. Used by the
-         * subscriber to determine whether they match transmitted publish
-         * packets - in addition to just relying on the service name.
-         * <p>
-         * Format is an LV byte array - the {@link TlvBufferUtils} utility class
-         * is available to form and parse.
-         *
-         * @param rxFilter The byte-array containing the LV formatted receive
-         *            filter.
-         * @param rxFilterLength The number of bytes in the receive filter
-         *            argument.
-         * @return The builder to facilitate chaining
-         *         {@code builder.setXXX(..).setXXX(..)}.
-         */
-        public Builder setRxFilter(byte[] rxFilter, int rxFilterLength) {
-            mRxFilter = rxFilter;
-            mRxFilterLength = rxFilterLength;
-            return this;
-        }
-
-        /**
-         * Build {@link SubscribeData} given the current requests made on the
-         * builder.
-         */
-        public SubscribeData build() {
-            return new SubscribeData(mServiceName, mServiceSpecificInfo, mServiceSpecificInfoLength,
-                    mTxFilter, mTxFilterLength, mRxFilter, mRxFilterLength);
-        }
-    }
-}
diff --git a/wifi/java/android/net/wifi/nan/SubscribeSettings.java b/wifi/java/android/net/wifi/nan/SubscribeSettings.java
deleted file mode 100644
index 5c4f8fb..0000000
--- a/wifi/java/android/net/wifi/nan/SubscribeSettings.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.wifi.nan;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Defines the settings (configuration) for a NAN subscribe session. Built using
- * {@link SubscribeSettings.Builder}. Subscribe is done using
- * {@link WifiNanManager#subscribe(SubscribeData, SubscribeSettings, WifiNanSessionListener, int)}
- * or {@link WifiNanSubscribeSession#subscribe(SubscribeData, SubscribeSettings)}.
- *
- * @hide PROPOSED_NAN_API
- */
-public class SubscribeSettings implements Parcelable {
-
-    /**
-     * Defines a passive subscribe session - i.e. a subscribe session where
-     * subscribe packets are not transmitted over-the-air and the device listens
-     * and matches to transmitted publish packets. Configuration is done using
-     * {@link SubscribeSettings.Builder#setSubscribeType(int)}.
-     */
-    public static final int SUBSCRIBE_TYPE_PASSIVE = 0;
-
-    /**
-     * Defines an active subscribe session - i.e. a subscribe session where
-     * subscribe packets are transmitted over-the-air. Configuration is done
-     * using {@link SubscribeSettings.Builder#setSubscribeType(int)}.
-     */
-    public static final int SUBSCRIBE_TYPE_ACTIVE = 1;
-
-    /**
-     * @hide
-     */
-    public final int mSubscribeType;
-
-    /**
-     * @hide
-     */
-    public final int mSubscribeCount;
-
-    /**
-     * @hide
-     */
-    public final int mTtlSec;
-
-    private SubscribeSettings(int subscribeType, int publichCount, int ttlSec) {
-        mSubscribeType = subscribeType;
-        mSubscribeCount = publichCount;
-        mTtlSec = ttlSec;
-    }
-
-    @Override
-    public String toString() {
-        return "SubscribeSettings [mSubscribeType=" + mSubscribeType + ", mSubscribeCount="
-                + mSubscribeCount + ", mTtlSec=" + mTtlSec + "]";
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(mSubscribeType);
-        dest.writeInt(mSubscribeCount);
-        dest.writeInt(mTtlSec);
-    }
-
-    public static final Creator<SubscribeSettings> CREATOR = new Creator<SubscribeSettings>() {
-        @Override
-        public SubscribeSettings[] newArray(int size) {
-            return new SubscribeSettings[size];
-        }
-
-        @Override
-        public SubscribeSettings createFromParcel(Parcel in) {
-            int subscribeType = in.readInt();
-            int subscribeCount = in.readInt();
-            int ttlSec = in.readInt();
-            return new SubscribeSettings(subscribeType, subscribeCount, ttlSec);
-        }
-    };
-
-    @Override
-    public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-
-        if (!(o instanceof SubscribeSettings)) {
-            return false;
-        }
-
-        SubscribeSettings lhs = (SubscribeSettings) o;
-
-        return mSubscribeType == lhs.mSubscribeType && mSubscribeCount == lhs.mSubscribeCount
-                && mTtlSec == lhs.mTtlSec;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = 17;
-
-        result = 31 * result + mSubscribeType;
-        result = 31 * result + mSubscribeCount;
-        result = 31 * result + mTtlSec;
-
-        return result;
-    }
-
-    /**
-     * Builder used to build {@link SubscribeSettings} objects.
-     */
-    public static final class Builder {
-        int mSubscribeType;
-        int mSubscribeCount;
-        int mTtlSec;
-
-        /**
-         * Sets the type of the subscribe session: active (subscribe packets are
-         * transmitted over-the-air), or passive (no subscribe packets are
-         * transmitted, a match is made against a solicited/active publish
-         * session whose packets are transmitted over-the-air).
-         *
-         * @param subscribeType Subscribe session type: active (
-         *            {@link SubscribeSettings#SUBSCRIBE_TYPE_ACTIVE}) or
-         *            passive ( {@link SubscribeSettings#SUBSCRIBE_TYPE_PASSIVE}
-         *            ).
-         * @return The builder to facilitate chaining
-         *         {@code builder.setXXX(..).setXXX(..)}.
-         */
-        public Builder setSubscribeType(int subscribeType) {
-            if (subscribeType < SUBSCRIBE_TYPE_PASSIVE || subscribeType > SUBSCRIBE_TYPE_ACTIVE) {
-                throw new IllegalArgumentException("Invalid subscribeType - " + subscribeType);
-            }
-            mSubscribeType = subscribeType;
-            return this;
-        }
-
-        /**
-         * Sets the number of times an active (
-         * {@link SubscribeSettings.Builder#setSubscribeType(int)}) subscribe
-         * session will transmit a packet. When the count is reached an event
-         * will be generated for
-         * {@link WifiNanSessionListener#onSubscribeTerminated(int)} with reason=
-         * {@link WifiNanSessionListener#TERMINATE_REASON_DONE}.
-         *
-         * @param subscribeCount Number of subscribe packets to transmit.
-         * @return The builder to facilitate chaining
-         *         {@code builder.setXXX(..).setXXX(..)}.
-         */
-        public Builder setSubscribeCount(int subscribeCount) {
-            if (subscribeCount < 0) {
-                throw new IllegalArgumentException("Invalid subscribeCount - must be non-negative");
-            }
-            mSubscribeCount = subscribeCount;
-            return this;
-        }
-
-        /**
-         * Sets the time interval (in seconds) an active (
-         * {@link SubscribeSettings.Builder#setSubscribeType(int)}) subscribe
-         * session will be alive - i.e. transmitting a packet. When the TTL is
-         * reached an event will be generated for
-         * {@link WifiNanSessionListener#onSubscribeTerminated(int)} with reason=
-         * {@link WifiNanSessionListener#TERMINATE_REASON_DONE}.
-         *
-         * @param ttlSec Lifetime of a subscribe session in seconds.
-         * @return The builder to facilitate chaining
-         *         {@code builder.setXXX(..).setXXX(..)}.
-         */
-        public Builder setTtlSec(int ttlSec) {
-            if (ttlSec < 0) {
-                throw new IllegalArgumentException("Invalid ttlSec - must be non-negative");
-            }
-            mTtlSec = ttlSec;
-            return this;
-        }
-
-        /**
-         * Build {@link SubscribeSettings} given the current requests made on
-         * the builder.
-         */
-        public SubscribeSettings build() {
-            return new SubscribeSettings(mSubscribeType, mSubscribeCount, mTtlSec);
-        }
-    }
-}
diff --git a/wifi/java/android/net/wifi/nan/TlvBufferUtils.java b/wifi/java/android/net/wifi/nan/TlvBufferUtils.java
index ea8785a..2c5aab4 100644
--- a/wifi/java/android/net/wifi/nan/TlvBufferUtils.java
+++ b/wifi/java/android/net/wifi/nan/TlvBufferUtils.java
@@ -16,11 +16,15 @@
 
 package android.net.wifi.nan;
 
+import android.annotation.Nullable;
+
 import libcore.io.Memory;
 
 import java.nio.BufferOverflowException;
 import java.nio.ByteOrder;
+import java.util.Arrays;
 import java.util.Iterator;
+import java.util.NoSuchElementException;
 
 /**
  * Utility class to construct and parse byte arrays using the TLV format -
@@ -50,8 +54,7 @@
      * Values are added to the structure using the {@code TlvConstructor.put*()}
      * methods.
      * <p>
-     * The final byte array is obtained using {@link TlvConstructor#getArray()}
-     * and {@link TlvConstructor#getActualLength()} methods.
+     * The final byte array is obtained using {@link TlvConstructor#getArray()}.
      */
     public static class TlvConstructor {
         private int mTypeSize;
@@ -88,9 +91,9 @@
          * @return The constructor to facilitate chaining
          *         {@code ctr.putXXX(..).putXXX(..)}.
          */
-        public TlvConstructor wrap(byte[] array) {
+        public TlvConstructor wrap(@Nullable byte[] array) {
             mArray = array;
-            mArrayLength = array.length;
+            mArrayLength = (array == null) ? 0 : array.length;
             return this;
         }
 
@@ -137,10 +140,13 @@
          * @return The constructor to facilitate chaining
          *         {@code ctr.putXXX(..).putXXX(..)}.
          */
-        public TlvConstructor putByteArray(int type, byte[] array, int offset, int length) {
+        public TlvConstructor putByteArray(int type, @Nullable byte[] array, int offset,
+                int length) {
             checkLength(length);
             addHeader(type, length);
-            System.arraycopy(array, offset, mArray, mPosition, length);
+            if (length != 0) {
+                System.arraycopy(array, offset, mArray, mPosition, length);
+            }
             mPosition += length;
             return this;
         }
@@ -155,8 +161,8 @@
          * @return The constructor to facilitate chaining
          *         {@code ctr.putXXX(..).putXXX(..)}.
          */
-        public TlvConstructor putByteArray(int type, byte[] array) {
-            return putByteArray(type, array, 0, array.length);
+        public TlvConstructor putByteArray(int type, @Nullable byte[] array) {
+            return putByteArray(type, array, 0, (array == null) ? 0 : array.length);
         }
 
         /**
@@ -223,23 +229,25 @@
          * @return The constructor to facilitate chaining
          *         {@code ctr.putXXX(..).putXXX(..)}.
          */
-        public TlvConstructor putString(int type, String data) {
-            return putByteArray(type, data.getBytes(), 0, data.length());
+        public TlvConstructor putString(int type, @Nullable String data) {
+            byte[] bytes = null;
+            int length = 0;
+            if (data != null) {
+                bytes = data.getBytes();
+                length = bytes.length;
+            }
+            return putByteArray(type, bytes, 0, length);
         }
 
         /**
-         * Returns the constructed TLV formatted byte-array. Note that the
-         * returned array is the fully wrapped (
-         * {@link TlvConstructor#wrap(byte[])}) or allocated (
-         * {@link TlvConstructor#allocate(int)}) array - which isn't necessarily
-         * the actual size of the formatted data. Use
-         * {@link TlvConstructor#getActualLength()} to obtain the size of the
-         * formatted data.
+         * Returns the constructed TLV formatted byte-array. This array is a copy of the wrapped
+         * or allocated array - truncated to just the significant bytes - i.e. those written into
+         * the (T)LV.
          *
          * @return The byte array containing the TLV formatted structure.
          */
         public byte[] getArray() {
-            return mArray;
+            return Arrays.copyOf(mArray, getActualLength());
         }
 
         /**
@@ -249,7 +257,7 @@
          *
          * @return The size of the TLV formatted portion of the byte array.
          */
-        public int getActualLength() {
+        private int getActualLength() {
             return mPosition;
         }
 
@@ -287,75 +295,75 @@
          * formatted byte-arrays (i.e. TLV whose Type/T size is 0) the value of
          * this field is undefined.
          */
-        public int mType;
+        public int type;
 
         /**
          * The Length (L) field of the current TLV element.
          */
-        public int mLength;
+        public int length;
 
         /**
          * The Value (V) field - a raw byte array representing the current TLV
-         * element where the entry starts at {@link TlvElement#mOffset}.
+         * element where the entry starts at {@link TlvElement#offset}.
          */
-        public byte[] mRefArray;
+        public byte[] refArray;
 
         /**
-         * The offset to be used into {@link TlvElement#mRefArray} to access the
+         * The offset to be used into {@link TlvElement#refArray} to access the
          * raw data representing the current TLV element.
          */
-        public int mOffset;
+        public int offset;
 
-        private TlvElement(int type, int length, byte[] refArray, int offset) {
-            mType = type;
-            mLength = length;
-            mRefArray = refArray;
-            mOffset = offset;
+        private TlvElement(int type, int length, @Nullable byte[] refArray, int offset) {
+            this.type = type;
+            this.length = length;
+            this.refArray = refArray;
+            this.offset = offset;
         }
 
         /**
          * Utility function to return a byte representation of a TLV element of
          * length 1. Note: an attempt to call this function on a TLV item whose
-         * {@link TlvElement#mLength} is != 1 will result in an exception.
+         * {@link TlvElement#length} is != 1 will result in an exception.
          *
          * @return byte representation of current TLV element.
          */
         public byte getByte() {
-            if (mLength != 1) {
+            if (length != 1) {
                 throw new IllegalArgumentException(
-                        "Accesing a byte from a TLV element of length " + mLength);
+                        "Accesing a byte from a TLV element of length " + length);
             }
-            return mRefArray[mOffset];
+            return refArray[offset];
         }
 
         /**
          * Utility function to return a short representation of a TLV element of
          * length 2. Note: an attempt to call this function on a TLV item whose
-         * {@link TlvElement#mLength} is != 2 will result in an exception.
+         * {@link TlvElement#length} is != 2 will result in an exception.
          *
          * @return short representation of current TLV element.
          */
         public short getShort() {
-            if (mLength != 2) {
+            if (length != 2) {
                 throw new IllegalArgumentException(
-                        "Accesing a short from a TLV element of length " + mLength);
+                        "Accesing a short from a TLV element of length " + length);
             }
-            return Memory.peekShort(mRefArray, mOffset, ByteOrder.BIG_ENDIAN);
+            return Memory.peekShort(refArray, offset, ByteOrder.BIG_ENDIAN);
         }
 
         /**
          * Utility function to return an integer representation of a TLV element
          * of length 4. Note: an attempt to call this function on a TLV item
-         * whose {@link TlvElement#mLength} is != 4 will result in an exception.
+         * whose {@link TlvElement#length} is != 4 will result in an exception.
          *
          * @return integer representation of current TLV element.
          */
         public int getInt() {
-            if (mLength != 4) {
+            if (length != 4) {
                 throw new IllegalArgumentException(
-                        "Accesing an int from a TLV element of length " + mLength);
+                        "Accesing an int from a TLV element of length " + length);
             }
-            return Memory.peekInt(mRefArray, mOffset, ByteOrder.BIG_ENDIAN);
+            return Memory.peekInt(refArray, offset, ByteOrder.BIG_ENDIAN);
         }
 
         /**
@@ -364,7 +372,7 @@
          * @return String repersentation of the current TLV element.
          */
         public String getString() {
-            return new String(mRefArray, mOffset, mLength);
+            return new String(refArray, offset, length);
         }
     }
 
@@ -388,10 +396,8 @@
          * @param lengthSize Number of bytes sued for the Length (L) field.
          *            Values values are 1 or 2 bytes.
          * @param array The TLV formatted byte-array to parse.
-         * @param length The number of bytes of the array to be used in the
-         *            parsing.
          */
-        public TlvIterable(int typeSize, int lengthSize, byte[] array, int length) {
+        public TlvIterable(int typeSize, int lengthSize, @Nullable byte[] array) {
             if (typeSize < 0 || typeSize > 2 || lengthSize <= 0 || lengthSize > 2) {
                 throw new IllegalArgumentException(
                         "Invalid sizes - typeSize=" + typeSize + ", lengthSize=" + lengthSize);
@@ -399,7 +405,7 @@
             mTypeSize = typeSize;
             mLengthSize = lengthSize;
             mArray = array;
-            mArrayLength = length;
+            mArrayLength = (array == null) ? 0 : array.length;
         }
 
         /**
@@ -420,21 +426,21 @@
                 first = false;
                 builder.append(" (");
                 if (mTypeSize != 0) {
-                    builder.append("T=" + tlv.mType + ",");
+                    builder.append("T=" + tlv.type + ",");
                 }
-                builder.append("L=" + tlv.mLength + ") ");
-                if (tlv.mLength == 0) {
+                builder.append("L=" + tlv.length + ") ");
+                if (tlv.length == 0) {
                     builder.append("<null>");
-                } else if (tlv.mLength == 1) {
+                } else if (tlv.length == 1) {
                     builder.append(tlv.getByte());
-                } else if (tlv.mLength == 2) {
+                } else if (tlv.length == 2) {
                     builder.append(tlv.getShort());
-                } else if (tlv.mLength == 4) {
+                } else if (tlv.length == 4) {
                     builder.append(tlv.getInt());
                 } else {
                     builder.append("<bytes>");
                 }
-                if (tlv.mLength != 0) {
+                if (tlv.length != 0) {
                     builder.append(" (S='" + tlv.getString() + "')");
                 }
             }
@@ -459,6 +465,10 @@
 
                 @Override
                 public TlvElement next() {
+                    if (!hasNext()) {
+                        throw new NoSuchElementException();
+                    }
+
                     int type = 0;
                     if (mTypeSize == 1) {
                         type = mArray[mOffset];
@@ -487,4 +497,40 @@
             };
         }
     }
+
+    /**
+     * Validates that a (T)LV array is constructed correctly. I.e. that its specified Length
+     * fields correctly fill the specified length (and do not overshoot).
+     *
+     * @param array The (T)LV array to verify.
+     * @param typeSize The size (in bytes) of the type field. Valid values are 0, 1, or 2.
+     * @param lengthSize The size (in bytes) of the length field. Valid values are 1 or 2.
+     * @return A boolean indicating whether the array is valid (true) or invalid (false).
+     */
+    public static boolean isValid(@Nullable byte[] array, int typeSize, int lengthSize) {
+        if (typeSize < 0 || typeSize > 2) {
+            throw new IllegalArgumentException(
+                    "Invalid arguments - typeSize must be 0, 1, or 2: typeSize=" + typeSize);
+        }
+        if (lengthSize <= 0 || lengthSize > 2) {
+            throw new IllegalArgumentException(
+                    "Invalid arguments - lengthSize must be 1 or 2: lengthSize=" + lengthSize);
+        }
+        if (array == null) {
+            return true;
+        }
+
+        int nextTlvIndex = 0;
+        while (nextTlvIndex + typeSize + lengthSize <= array.length) {
+            nextTlvIndex += typeSize;
+            if (lengthSize == 1) {
+                nextTlvIndex += lengthSize + array[nextTlvIndex];
+            } else {
+                nextTlvIndex += lengthSize + Memory.peekShort(array, nextTlvIndex,
+                        ByteOrder.BIG_ENDIAN);
+            }
+        }
+
+        return nextTlvIndex == array.length;
+    }
 }
diff --git a/wifi/java/android/net/wifi/nan/WifiNanEventCallback.java b/wifi/java/android/net/wifi/nan/WifiNanEventCallback.java
new file mode 100644
index 0000000..6e714f1
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/WifiNanEventCallback.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nan;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Base class for NAN events callbacks. Should be extended by applications and set when calling
+ * {@link WifiNanManager#connect(android.os.Looper, WifiNanEventCallback)}. These are callbacks
+ * applying to the NAN connection as a whole - not to specific publish or subscribe sessions -
+ * for that see {@link WifiNanSessionCallback}.
+ *
+ * @hide PROPOSED_NAN_API
+ */
+public class WifiNanEventCallback {
+    /** @hide */
+    @IntDef({
+            REASON_INVALID_ARGS, REASON_ALREADY_CONNECTED_INCOMPAT_CONFIG, REASON_OTHER
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface EventReasonCodes {
+    }
+
+    /**
+     * Indicates invalid argument in the requested operation. Failure reason flag for
+     * {@link WifiNanEventCallback#onConnectFail(int)}.
+     */
+    public static final int REASON_INVALID_ARGS = 1000;
+
+    /**
+     * Indicates that a {@link ConfigRequest} passed in
+     * {@link WifiNanManager#connect(android.os.Looper, ConfigRequest, WifiNanEventCallback)}
+     * couldn't be applied since other connections already exist with an incompatible
+     * configurations. Failure reason flag for {@link WifiNanEventCallback#onConnectFail(int)}.
+     */
+    public static final int REASON_ALREADY_CONNECTED_INCOMPAT_CONFIG = 1001;
+
+    /**
+     * Indicates an unspecified error occurred during the operation. Failure reason flag for
+     * {@link WifiNanEventCallback#onConnectFail(int)}.
+     */
+    public static final int REASON_OTHER = 1002;
+
+    /**
+     * Called when NAN connect operation
+     * {@link WifiNanManager#connect(android.os.Looper, WifiNanEventCallback)}
+     * is completed and that we can now start discovery sessions or connections.
+     */
+    public void onConnectSuccess() {
+        /* empty */
+    }
+
+    /**
+     * Called when NAN connect operation
+     * {@link WifiNanManager#connect(android.os.Looper, WifiNanEventCallback)} failed.
+     *
+     * @param reason Failure reason code, see
+     *            {@code WifiNanEventCallback.REASON_*}.
+     */
+    public void onConnectFail(@EventReasonCodes int reason) {
+        /* empty */
+    }
+
+    /**
+     * Called when NAN identity (the MAC address representing our NAN discovery interface) has
+     * changed. Change may be due to device joining a cluster, starting a cluster, or discovery
+     * interface change (addresses are randomized at regular intervals). The implication is that
+     * peers you've been communicating with may no longer recognize you and you need to
+     * re-establish your identity - e.g. by starting a discovery session. This actual MAC address
+     * of the interface may also be useful if the application uses alternative (non-NAN)
+     * discovery but needs to set up a NAN connection. The provided NAN discovery interface MAC
+     * address can then be used in
+     * {@link WifiNanManager#createNetworkSpecifier(int, byte[], byte[])}.
+     * <p>
+     *     This callback is only called if the NAN connection enables it using
+     *     {@link ConfigRequest.Builder#setEnableIdentityChangeCallback(boolean)} in
+     *     {@link WifiNanManager#connect(android.os.Looper, ConfigRequest, WifiNanEventCallback)}
+     *     . It is disabled by default since it may result in additional wake-ups of the host -
+     *     increasing power.
+     *
+     * @param mac The MAC address of the NAN discovery interface. The application must have the
+     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} to get the actual MAC address,
+     *            otherwise all 0's will be provided.
+     */
+    public void onIdentityChanged(byte[] mac) {
+        /* empty */
+    }
+}
diff --git a/wifi/java/android/net/wifi/nan/WifiNanEventListener.java b/wifi/java/android/net/wifi/nan/WifiNanEventListener.java
deleted file mode 100644
index 9e6ed4e..0000000
--- a/wifi/java/android/net/wifi/nan/WifiNanEventListener.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.wifi.nan;
-
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Log;
-
-/**
- * Base class for NAN events callbacks. Should be extended by applications
- * wanting notifications. These are callbacks applying to the NAN connection as
- * a whole - not to specific publish or subscribe sessions - for that see
- * {@link WifiNanSessionListener}.
- * <p>
- * During registration specify which specific events are desired using a set of
- * {@code NanEventListener.LISTEN_*} flags OR'd together. Only those events will
- * be delivered to the registered listener. Override those callbacks
- * {@code NanEventListener.on*} for the registered events.
- *
- * @hide PROPOSED_NAN_API
- */
-public class WifiNanEventListener {
-    private static final String TAG = "WifiNanEventListener";
-    private static final boolean DBG = false;
-    private static final boolean VDBG = false; // STOPSHIP if true
-
-    /**
-     * Configuration completion callback event registration flag. Corresponding
-     * callback is {@link WifiNanEventListener#onConfigCompleted(ConfigRequest)}.
-     */
-    public static final int LISTEN_CONFIG_COMPLETED = 0x1 << 0;
-
-    /**
-     * Configuration failed callback event registration flag. Corresponding
-     * callback is
-     * {@link WifiNanEventListener#onConfigFailed(ConfigRequest, int)}.
-     */
-    public static final int LISTEN_CONFIG_FAILED = 0x1 << 1;
-
-    /**
-     * NAN cluster is down callback event registration flag. Corresponding
-     * callback is {@link WifiNanEventListener#onNanDown(int)}.
-     */
-    public static final int LISTEN_NAN_DOWN = 0x1 << 2;
-
-    /**
-     * NAN identity has changed event registration flag. This may be due to
-     * joining a cluster, starting a cluster, or discovery interface change. The
-     * implication is that peers you've been communicating with may no longer
-     * recognize you and you need to re-establish your identity. Corresponding
-     * callback is {@link WifiNanEventListener#onIdentityChanged()}.
-     */
-    public static final int LISTEN_IDENTITY_CHANGED = 0x1 << 3;
-
-    private final Handler mHandler;
-
-    /**
-     * Constructs a {@link WifiNanEventListener} using the looper of the current
-     * thread. I.e. all callbacks will be delivered on the current thread.
-     */
-    public WifiNanEventListener() {
-        this(Looper.myLooper());
-    }
-
-    /**
-     * Constructs a {@link WifiNanEventListener} using the specified looper. I.e.
-     * all callbacks will delivered on the thread of the specified looper.
-     *
-     * @param looper The looper on which to execute the callbacks.
-     */
-    public WifiNanEventListener(Looper looper) {
-        if (VDBG) Log.v(TAG, "ctor: looper=" + looper);
-        mHandler = new Handler(looper) {
-            @Override
-            public void handleMessage(Message msg) {
-                if (DBG) Log.d(TAG, "What=" + msg.what + ", msg=" + msg);
-                switch (msg.what) {
-                    case LISTEN_CONFIG_COMPLETED:
-                        WifiNanEventListener.this.onConfigCompleted((ConfigRequest) msg.obj);
-                        break;
-                    case LISTEN_CONFIG_FAILED:
-                        WifiNanEventListener.this.onConfigFailed((ConfigRequest) msg.obj, msg.arg1);
-                        break;
-                    case LISTEN_NAN_DOWN:
-                        WifiNanEventListener.this.onNanDown(msg.arg1);
-                        break;
-                    case LISTEN_IDENTITY_CHANGED:
-                        WifiNanEventListener.this.onIdentityChanged();
-                        break;
-                }
-            }
-        };
-    }
-
-    /**
-     * Called when NAN configuration is completed. Event will only be delivered
-     * if registered using {@link WifiNanEventListener#LISTEN_CONFIG_COMPLETED}. A
-     * dummy (empty implementation printing out a warning). Make sure to
-     * override if registered.
-     *
-     * @param completedConfig The actual configuration request which was
-     *            completed. Note that it may be different from that requested
-     *            by the application. The service combines configuration
-     *            requests from all applications.
-     */
-    public void onConfigCompleted(ConfigRequest completedConfig) {
-        Log.w(TAG, "onConfigCompleted: called in stub - override if interested or disable");
-    }
-
-    /**
-     * Called when NAN configuration failed. Event will only be delivered if
-     * registered using {@link WifiNanEventListener#LISTEN_CONFIG_FAILED}. A dummy
-     * (empty implementation printing out a warning). Make sure to override if
-     * registered.
-     *
-     * @param reason Failure reason code, see {@code NanSessionListener.FAIL_*}.
-     */
-    public void onConfigFailed(ConfigRequest failedConfig, int reason) {
-        Log.w(TAG, "onConfigFailed: called in stub - override if interested or disable");
-    }
-
-    /**
-     * Called when NAN cluster is down. Event will only be delivered if
-     * registered using {@link WifiNanEventListener#LISTEN_NAN_DOWN}. A dummy (empty
-     * implementation printing out a warning). Make sure to override if
-     * registered.
-     *
-     * @param reason Reason code for event, see {@code NanSessionListener.FAIL_*}.
-     */
-    public void onNanDown(int reason) {
-        Log.w(TAG, "onNanDown: called in stub - override if interested or disable");
-    }
-
-    /**
-     * Called when NAN identity has changed. This may be due to joining a
-     * cluster, starting a cluster, or discovery interface change. The
-     * implication is that peers you've been communicating with may no longer
-     * recognize you and you need to re-establish your identity. Event will only
-     * be delivered if registered using
-     * {@link WifiNanEventListener#LISTEN_IDENTITY_CHANGED}. A dummy (empty
-     * implementation printing out a warning). Make sure to override if
-     * registered.
-     */
-    public void onIdentityChanged() {
-        if (VDBG) Log.v(TAG, "onIdentityChanged: called in stub - override if interested");
-    }
-
-    /**
-     * {@hide}
-     */
-    public IWifiNanEventListener callback = new IWifiNanEventListener.Stub() {
-        @Override
-        public void onConfigCompleted(ConfigRequest completedConfig) {
-            if (VDBG) Log.v(TAG, "onConfigCompleted: configRequest=" + completedConfig);
-
-            Message msg = mHandler.obtainMessage(LISTEN_CONFIG_COMPLETED);
-            msg.obj = completedConfig;
-            mHandler.sendMessage(msg);
-        }
-
-        @Override
-        public void onConfigFailed(ConfigRequest failedConfig, int reason) {
-            if (VDBG) {
-                Log.v(TAG, "onConfigFailed: failedConfig=" + failedConfig + ", reason=" + reason);
-            }
-
-            Message msg = mHandler.obtainMessage(LISTEN_CONFIG_FAILED);
-            msg.arg1 = reason;
-            msg.obj = failedConfig;
-            mHandler.sendMessage(msg);
-        }
-
-        @Override
-        public void onNanDown(int reason) {
-            if (VDBG) Log.v(TAG, "onNanDown: reason=" + reason);
-
-            Message msg = mHandler.obtainMessage(LISTEN_NAN_DOWN);
-            msg.arg1 = reason;
-            mHandler.sendMessage(msg);
-        }
-
-        @Override
-        public void onIdentityChanged() {
-            if (VDBG) Log.v(TAG, "onIdentityChanged");
-
-            Message msg = mHandler.obtainMessage(LISTEN_IDENTITY_CHANGED);
-            mHandler.sendMessage(msg);
-        }
-    };
-}
diff --git a/wifi/java/android/net/wifi/nan/WifiNanManager.java b/wifi/java/android/net/wifi/nan/WifiNanManager.java
index 1b78beb..82d22bc 100644
--- a/wifi/java/android/net/wifi/nan/WifiNanManager.java
+++ b/wifi/java/android/net/wifi/nan/WifiNanManager.java
@@ -16,23 +16,103 @@
 
 package android.net.wifi.nan;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkRequest;
+import android.net.wifi.RttManager;
 import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.os.RemoteException;
+import android.util.Base64;
 import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+import dalvik.system.CloseGuard;
+
+import libcore.util.HexEncoding;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
 
 /**
- * This class provides the primary API for managing Wi-Fi NAN operation:
- * including discovery and data-links. Get an instance of this class by calling
+ * This class provides the primary API for managing Wi-Fi NAN operations:
+ * discovery and peer-to-peer data connections. Get an instance of this class by calling
  * {@link android.content.Context#getSystemService(String)
  * Context.getSystemService(Context.WIFI_NAN_SERVICE)}.
  * <p>
  * The class provides access to:
  * <ul>
- * <li>Configure a NAN connection and register for events.
- * <li>Create publish and subscribe sessions.
- * <li>Create NAN network specifier to be used to create a NAN network.
+ * <li>Initialize a NAN cluster (peer-to-peer synchronization). Refer to
+ * {@link #connect(Looper, WifiNanEventCallback)}.
+ * <li>Create discovery sessions (publish or subscribe sessions).
+ * Refer to {@link #publish(PublishConfig, WifiNanSessionCallback)} and
+ * {@link #subscribe(SubscribeConfig, WifiNanSessionCallback)}.
+ * <li>Create a NAN network specifier to be used with
+ * {@link ConnectivityManager#requestNetwork(NetworkRequest, ConnectivityManager.NetworkCallback)}
+ * to set-up a NAN connection with a peer. Refer to
+ * {@link WifiNanSession#createNetworkSpecifier(int, int, byte[])} and
+ * {@link #createNetworkSpecifier(int, byte[], byte[])}.
  * </ul>
+ * <p>
+ *     NAN may not be usable when Wi-Fi is disabled (and other conditions). To validate that
+ *     the functionality is available use the {@link #isUsageEnabled()} function. To track
+ *     changes in NAN usability register for the {@link #ACTION_WIFI_NAN_STATE_CHANGED} broadcast.
+ *     Note that this broadcast is not sticky - you should register for it and then check the
+ *     above API to avoid a race condition.
+ * <p>
+ *     An application must use {@link #connect(Looper, WifiNanEventCallback)} to initialize a NAN
+ *     cluster - before making any other NAN operation. NAN cluster membership is a device-wide
+ *     operation - the API guarantees that the device is in a cluster or joins a NAN cluster (or
+ *     starts one if none can be found). Information about connection success (or failure) are
+ *     returned in callbacks of {@link WifiNanEventCallback}. Proceed with NAN discovery or
+ *     connection setup only after receiving confirmation that NAN connection succeeded -
+ *     {@link WifiNanEventCallback#onConnectSuccess()}.
+ *     When an application is finished using NAN it <b>must</b> use the {@link #disconnect()} API
+ *     to indicate to the NAN service that the device may disconnect from the NAN cluster. The
+ *     device will actually disconnect from the NAN cluster once the last application disconnects.
+ * <p>
+ *     Once a NAN connection is confirmed use the
+ *     {@link #publish(PublishConfig, WifiNanSessionCallback)} or
+ *     {@link #subscribe(SubscribeConfig, WifiNanSessionCallback)} to create publish or subscribe
+ *     NAN discovery sessions. Events are called on the provided callback object
+ *     {@link WifiNanSessionCallback}. Specifically, the
+ *     {@link WifiNanSessionCallback#onPublishStarted(WifiNanPublishSession)} and
+ *     {@link WifiNanSessionCallback#onSubscribeStarted(WifiNanSubscribeSession)} return
+ *     {@link WifiNanPublishSession} and {@link WifiNanSubscribeSession} objects respectively on
+ *     which additional session operations can be performed, e.g. updating the session
+ *     {@link WifiNanPublishSession#updatePublish(PublishConfig)} and
+ *     {@link WifiNanSubscribeSession#updateSubscribe(SubscribeConfig)}. Sessions can also be
+ *     used to send messages using the {@link WifiNanSession#sendMessage(int, byte[], int)} APIs.
+ *     When an application is finished with a discovery session it <b>must</b> terminate it using
+ *     the {@link WifiNanSession#terminate()} API.
+ * <p>
+ *    Creating connections between NAN devices is managed by the standard
+ *    {@link ConnectivityManager#requestNetwork(NetworkRequest, ConnectivityManager.NetworkCallback)}.
+ *    The {@link NetworkRequest} object should be constructed with:
+ *    <ul>
+ *        <li>{@link NetworkRequest.Builder#addTransportType(int)} of
+ *        {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_NAN}.
+ *        <li>{@link NetworkRequest.Builder#setNetworkSpecifier(String)} using
+ *        {@link #createNetworkSpecifier(int, byte[], byte[])} or
+ *        {@link WifiNanSession#createNetworkSpecifier(int, int, byte[])}.
+ *    </ul>
  *
  * @hide PROPOSED_NAN_API
  */
@@ -41,292 +121,1080 @@
     private static final boolean DBG = false;
     private static final boolean VDBG = false; // STOPSHIP if true
 
-    private IBinder mBinder;
-
-    private IWifiNanManager mService;
+    private static final int INVALID_CLIENT_ID = 0;
 
     /**
-     * {@hide}
+     * Keys used to generate a Network Specifier for the NAN network request. The network specifier
+     * is formatted as a JSON string.
      */
-    public WifiNanManager(IWifiNanManager service) {
+
+    /**
+     * TYPE_1A: role, client_id, session_id, peer_id, token
+     * @hide
+     */
+    public static final int NETWORK_SPECIFIER_TYPE_1A = 0;
+
+    /**
+     * TYPE_1B: role, client_id, session_id, peer_id [only permitted for RESPONDER]
+     * @hide
+     */
+    public static final int NETWORK_SPECIFIER_TYPE_1B = 1;
+
+    /**
+     * TYPE_1C: role, client_id, session_id, token [only permitted for RESPONDER]
+     * @hide
+     */
+    public static final int NETWORK_SPECIFIER_TYPE_1C = 2;
+
+    /**
+     * TYPE_1C: role, client_id, session_id [only permitted for RESPONDER]
+     * @hide
+     */
+    public static final int NETWORK_SPECIFIER_TYPE_1D = 3;
+
+    /**
+     * TYPE_2A: role, client_id, peer_mac, token
+     * @hide
+     */
+    public static final int NETWORK_SPECIFIER_TYPE_2A = 4;
+
+    /**
+     * TYPE_2B: role, client_id, peer_mac [only permitted for RESPONDER]
+     * @hide
+     */
+    public static final int NETWORK_SPECIFIER_TYPE_2B = 5;
+
+    /**
+     * TYPE_2C: role, client_id, token [only permitted for RESPONDER]
+     * @hide
+     */
+    public static final int NETWORK_SPECIFIER_TYPE_2C = 6;
+
+    /**
+     * TYPE_2D: role, client_id [only permitted for RESPONDER]
+     * @hide
+     */
+    public static final int NETWORK_SPECIFIER_TYPE_2D = 7;
+
+    /** @hide */
+    public static final int NETWORK_SPECIFIER_TYPE_MAX_VALID = NETWORK_SPECIFIER_TYPE_2D;
+
+    /** @hide */
+    public static final String NETWORK_SPECIFIER_KEY_TYPE = "type";
+
+    /** @hide */
+    public static final String NETWORK_SPECIFIER_KEY_ROLE = "role";
+
+    /** @hide */
+    public static final String NETWORK_SPECIFIER_KEY_CLIENT_ID = "client_id";
+
+    /** @hide */
+    public static final String NETWORK_SPECIFIER_KEY_SESSION_ID = "session_id";
+
+    /** @hide */
+    public static final String NETWORK_SPECIFIER_KEY_PEER_ID = "peer_id";
+
+    /** @hide */
+    public static final String NETWORK_SPECIFIER_KEY_PEER_MAC = "peer_mac";
+
+    /** @hide */
+    public static final String NETWORK_SPECIFIER_KEY_TOKEN = "token";
+
+    /**
+     * Broadcast intent action to indicate whether Wi-Fi NAN is enabled or
+     * disabled. An extra {@link #EXTRA_WIFI_STATE} provides the state
+     * information as int using {@link #WIFI_NAN_STATE_DISABLED} and
+     * {@link #WIFI_NAN_STATE_ENABLED} constants. This broadcast is <b>not</b> sticky,
+     * use the {@link #isUsageEnabled()} API after registering the broadcast to check the current
+     * state of Wi-Fi NAN.
+     *
+     * @see #EXTRA_WIFI_STATE
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_WIFI_NAN_STATE_CHANGED =
+            "android.net.wifi.nan.action.WIFI_NAN_STATE_CHANGED";
+
+    /**
+     * The lookup key for an int value indicating whether Wi-Fi NAN is enabled or
+     * disabled. Retrieve it with
+     * {@link android.content.Intent#getIntExtra(String,int)}.
+     *
+     * @see #WIFI_NAN_STATE_DISABLED
+     * @see #WIFI_NAN_STATE_ENABLED
+     */
+    public static final String EXTRA_WIFI_STATE = "android.net.wifi.nan.extra.WIFI_STATE";
+
+    /**
+     * Wi-Fi NAN is disabled.
+     *
+     * @see #ACTION_WIFI_NAN_STATE_CHANGED
+     */
+    public static final int WIFI_NAN_STATE_DISABLED = 1;
+
+    /**
+     * Wi-Fi NAN is enabled.
+     *
+     * @see #ACTION_WIFI_NAN_STATE_CHANGED
+     */
+    public static final int WIFI_NAN_STATE_ENABLED = 2;
+
+    /** @hide */
+    @IntDef({
+            WIFI_NAN_DATA_PATH_ROLE_INITIATOR, WIFI_NAN_DATA_PATH_ROLE_RESPONDER})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DataPathRole {
+    }
+
+    /**
+     * Connection creation role is that of INITIATOR. Used to create a network specifier string
+     * when requesting a NAN network.
+     *
+     * @see WifiNanSession#createNetworkSpecifier(int, int, byte[])
+     * @see #createNetworkSpecifier(int, byte[], byte[])
+     */
+    public static final int WIFI_NAN_DATA_PATH_ROLE_INITIATOR = 0;
+
+    /**
+     * Connection creation role is that of RESPONDER. Used to create a network specifier string
+     * when requesting a NAN network.
+     *
+     * @see WifiNanSession#createNetworkSpecifier(int, int, byte[])
+     * @see #createNetworkSpecifier(int, byte[], byte[])
+     */
+    public static final int WIFI_NAN_DATA_PATH_ROLE_RESPONDER = 1;
+
+    private final Context mContext;
+    private final IWifiNanManager mService;
+    private final CloseGuard mCloseGuard = CloseGuard.get();
+
+    private final Object mLock = new Object(); // lock access to the following vars
+
+    @GuardedBy("mLock")
+    private final IBinder mBinder = new Binder();
+
+    @GuardedBy("mLock")
+    private int mClientId = INVALID_CLIENT_ID;
+
+    @GuardedBy("mLock")
+    private Looper mLooper;
+
+    @GuardedBy("mLock")
+    private SparseArray<RttManager.RttListener> mRangingListeners = new SparseArray<>();
+
+    /** @hide */
+    public WifiNanManager(Context context, IWifiNanManager service) {
+        mContext = context;
         mService = service;
     }
 
     /**
-     * Re-connect to the Wi-Fi NAN service - enabling the application to execute
-     * {@link WifiNanManager} APIs. Application don't normally need to call this
-     * API since it is executed in the constructor. However, applications which
-     * have explicitly {@link WifiNanManager#disconnect()} need to call this
-     * function to re-connect.
+     * Enable the usage of the NAN API. Doesn't actually turn on NAN cluster formation - that only
+     * happens when a connection is made. {@link #ACTION_WIFI_NAN_STATE_CHANGED} broadcast will be
+     * triggered.
      *
-     * @param listener A listener extended from {@link WifiNanEventListener}.
-     * @param events The set of events to be delivered to the {@code listener}.
-     *            OR'd event flags from {@link WifiNanEventListener
-     *            NanEventListener.LISTEN*}.
+     * @hide
      */
-    public void connect(WifiNanEventListener listener, int events) {
+    public void enableUsage() {
         try {
-            if (VDBG) Log.v(TAG, "connect()");
-            if (listener == null) {
-                throw new IllegalArgumentException("Invalid listener - must not be null");
-            }
-            if (mBinder == null) {
-                mBinder = new Binder();
-            }
-            mService.connect(mBinder, listener.callback, events);
+            mService.enableUsage();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
 
     /**
-     * Disconnect from the Wi-Fi NAN service and destroy all outstanding
-     * operations - i.e. all publish and subscribes are terminated, any
-     * outstanding data-link is shut-down, and all requested NAN configurations
-     * are cancelled.
+     * Disable the usage of the NAN API. All attempts to connect() will be rejected. All open
+     * connections and sessions will be terminated. {@link #ACTION_WIFI_NAN_STATE_CHANGED} broadcast
+     * will be triggered.
+     *
+     * @hide
+     */
+    public void disableUsage() {
+        try {
+            mService.disableUsage();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the current status of NAN API: whether or not usage is enabled. To track changes
+     * in the state of NAN API register for the {@link #ACTION_WIFI_NAN_STATE_CHANGED} broadcast.
+     *
+     * @return A boolean indicating whether the app can use the NAN API (true)
+     *         or not (false).
+     */
+    public boolean isUsageEnabled() {
+        try {
+            return mService.isUsageEnabled();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Connect to the Wi-Fi NAN service - enabling the application to create discovery session or
+     * create connection to peers. The device will connect to an existing cluster if it can find
+     * one or create a new cluster (if it is the first to enable NAN in its vicinity). Results
+     * (e.g. successful connection to a cluster) are provided to the {@code callback} object.
+     * An application <b>must</b> call {@link #disconnect()} when done with the Wi-Fi NAN
+     * connection.
      * <p>
-     * An application may then re-connect using
-     * {@link WifiNanManager#connect(WifiNanEventListener, int)} .
+     * Note: a NAN cluster is a shared resource - if the device is already connected to a cluster
+     * than this function will simply indicate success immediately.
+     *
+     * @param looper The Looper on which to execute all callbacks related to the
+     *            connection - including all sessions opened as part of this
+     *            connection.
+     * @param callback A callback extended from {@link WifiNanEventCallback}.
+     */
+    public void connect(@NonNull Looper looper, @NonNull WifiNanEventCallback callback) {
+        connect(looper, null, callback);
+    }
+
+    /**
+     * Connect to the Wi-Fi NAN service - enabling the application to create discovery session or
+     * create connection to peers. The device will connect to an existing cluster if it can find
+     * one or create a new cluster (if it is the first to enable NAN in its vicinity). Results
+     * (e.g. successful connection to a cluster) are provided to the {@code callback} object.
+     * An application <b>must</b> call {@link #disconnect()} when done with the Wi-Fi NAN
+     * connection. Allows requesting a specific configuration using {@link ConfigRequest}. If not
+     * necessary (default configuration should usually work) use the
+     * {@link #connect(Looper, WifiNanEventCallback)} method instead.
+     * <p>
+     * Note: a NAN cluster is a shared resource - if the device is already connected to a cluster
+     * than this function will simply indicate success immediately.
+     *
+     * @param looper The Looper on which to execute all callbacks related to the
+     *            connection - including all sessions opened as part of this
+     *            connection.
+     * @param configRequest The requested NAN configuration.
+     * @param callback A callback extended from {@link WifiNanEventCallback}.
+     */
+    public void connect(@NonNull Looper looper, @Nullable ConfigRequest configRequest,
+            @NonNull WifiNanEventCallback callback) {
+        if (VDBG) {
+            Log.v(TAG, "connect(): looper=" + looper + ", callback=" + callback + ", configRequest="
+                    + configRequest);
+        }
+
+        synchronized (mLock) {
+            mLooper = looper;
+
+            try {
+                mClientId = mService.connect(mBinder, mContext.getOpPackageName(),
+                        new WifiNanEventCallbackProxy(this, looper, callback), configRequest);
+            } catch (RemoteException e) {
+                mClientId = INVALID_CLIENT_ID;
+                mLooper = null;
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        mCloseGuard.open("disconnect");
+    }
+
+    /**
+     * Disconnect from the Wi-Fi NAN service and, if no other applications are connected to NAN,
+     * also disconnect from the NAN cluster. This method destroys all outstanding operations -
+     * i.e. all publish and subscribes are terminated, and any outstanding data-links are
+     * shut-down. However, it is good practice to terminate these discovery sessions and
+     * connections explicitly before a disconnect.
+     * <p>
+     * An application may re-connect after a disconnect using
+     * {@link WifiNanManager#connect(Looper, WifiNanEventCallback)} .
      */
     public void disconnect() {
-        try {
-            if (VDBG) Log.v(TAG, "disconnect()");
-            mService.disconnect(mBinder);
-            mBinder = null;
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
+        if (VDBG) Log.v(TAG, "disconnect()");
 
-    /**
-     * Requests a NAN configuration, specified by {@link ConfigRequest}. Note
-     * that NAN is a shared resource and the device can only be a member of a
-     * single cluster. Thus the service may merge configuration requests from
-     * multiple applications and configure NAN differently from individual
-     * requests.
-     * <p>
-     * The {@link WifiNanEventListener#onConfigCompleted(ConfigRequest)} will be
-     * called when configuration is completed (if a listener is registered for
-     * this specific event).
-     *
-     * @param configRequest The requested NAN configuration.
-     */
-    public void requestConfig(ConfigRequest configRequest) {
-        if (VDBG) Log.v(TAG, "requestConfig(): configRequest=" + configRequest);
-        try {
-            mService.requestConfig(configRequest);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Request a NAN publish session. The results of the publish session
-     * operation will result in callbacks to the indicated listener:
-     * {@link WifiNanSessionListener NanSessionListener.on*}.
-     *
-     * @param publishData The {@link PublishData} specifying the contents of the
-     *            publish session.
-     * @param publishSettings The {@link PublishSettings} specifying the
-     *            settings for the publish session.
-     * @param listener The {@link WifiNanSessionListener} derived objects to be used
-     *            for the event callbacks specified by {@code events}.
-     * @param events The list of events to be delivered to the {@code listener}
-     *            object. An OR'd value of {@link WifiNanSessionListener
-     *            NanSessionListener.LISTEN_*}.
-     * @return The {@link WifiNanPublishSession} which can be used to further
-     *         control the publish session.
-     */
-    public WifiNanPublishSession publish(PublishData publishData, PublishSettings publishSettings,
-            WifiNanSessionListener listener, int events) {
-        return publishRaw(publishData, publishSettings, listener,
-                events | WifiNanSessionListener.LISTEN_HIDDEN_FLAGS);
-    }
-
-    /**
-     * Same as publish(*) but does not modify the event flag
-     *
-     * @hide
-     */
-    public WifiNanPublishSession publishRaw(PublishData publishData,
-            PublishSettings publishSettings, WifiNanSessionListener listener, int events) {
-        if (VDBG) Log.v(TAG, "publish(): data='" + publishData + "', settings=" + publishSettings);
-
-        if (publishSettings.mPublishType == PublishSettings.PUBLISH_TYPE_UNSOLICITED
-                && publishData.mRxFilterLength != 0) {
-            throw new IllegalArgumentException("Invalid publish data & settings: UNSOLICITED "
-                    + "publishes (active) can't have an Rx filter");
-        }
-        if (publishSettings.mPublishType == PublishSettings.PUBLISH_TYPE_SOLICITED
-                && publishData.mTxFilterLength != 0) {
-            throw new IllegalArgumentException("Invalid publish data & settings: SOLICITED "
-                    + "publishes (passive) can't have a Tx filter");
-        }
-        if (listener == null) {
-            throw new IllegalArgumentException("Invalid listener - must not be null");
-        }
-
-        int sessionId;
-
-        try {
-            sessionId = mService.createSession(listener.callback, events);
-            if (DBG) Log.d(TAG, "publish: session created - sessionId=" + sessionId);
-            mService.publish(sessionId, publishData, publishSettings);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-
-        return new WifiNanPublishSession(this, sessionId);
-    }
-
-    /**
-     * {@hide}
-     */
-    public void publish(int sessionId, PublishData publishData, PublishSettings publishSettings) {
-        if (VDBG) Log.v(TAG, "publish(): data='" + publishData + "', settings=" + publishSettings);
-
-        if (publishSettings.mPublishType == PublishSettings.PUBLISH_TYPE_UNSOLICITED
-                && publishData.mRxFilterLength != 0) {
-            throw new IllegalArgumentException("Invalid publish data & settings: UNSOLICITED "
-                    + "publishes (active) can't have an Rx filter");
-        }
-        if (publishSettings.mPublishType == PublishSettings.PUBLISH_TYPE_SOLICITED
-                && publishData.mTxFilterLength != 0) {
-            throw new IllegalArgumentException("Invalid publish data & settings: SOLICITED "
-                    + "publishes (passive) can't have a Tx filter");
-        }
-
-        try {
-            mService.publish(sessionId, publishData, publishSettings);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-    /**
-     * Request a NAN subscribe session. The results of the subscribe session
-     * operation will result in callbacks to the indicated listener:
-     * {@link WifiNanSessionListener NanSessionListener.on*}.
-     *
-     * @param subscribeData The {@link SubscribeData} specifying the contents of
-     *            the subscribe session.
-     * @param subscribeSettings The {@link SubscribeSettings} specifying the
-     *            settings for the subscribe session.
-     * @param listener The {@link WifiNanSessionListener} derived objects to be used
-     *            for the event callbacks specified by {@code events}.
-     * @param events The list of events to be delivered to the {@code listener}
-     *            object. An OR'd value of {@link WifiNanSessionListener
-     *            NanSessionListener.LISTEN_*}.
-     * @return The {@link WifiNanSubscribeSession} which can be used to further
-     *         control the subscribe session.
-     */
-    public WifiNanSubscribeSession subscribe(SubscribeData subscribeData,
-            SubscribeSettings subscribeSettings,
-            WifiNanSessionListener listener, int events) {
-        return subscribeRaw(subscribeData, subscribeSettings, listener,
-                events | WifiNanSessionListener.LISTEN_HIDDEN_FLAGS);
-    }
-
-    /**
-     * Same as subscribe(*) but does not modify the event flag
-     *
-     * @hide
-     */
-    public WifiNanSubscribeSession subscribeRaw(SubscribeData subscribeData,
-            SubscribeSettings subscribeSettings, WifiNanSessionListener listener, int events) {
-        if (VDBG) {
-            Log.v(TAG, "subscribe(): data='" + subscribeData + "', settings=" + subscribeSettings);
-        }
-
-        if (subscribeSettings.mSubscribeType == SubscribeSettings.SUBSCRIBE_TYPE_ACTIVE
-                && subscribeData.mRxFilterLength != 0) {
-            throw new IllegalArgumentException(
-                    "Invalid subscribe data & settings: ACTIVE subscribes can't have an Rx filter");
-        }
-        if (subscribeSettings.mSubscribeType == SubscribeSettings.SUBSCRIBE_TYPE_PASSIVE
-                && subscribeData.mTxFilterLength != 0) {
-            throw new IllegalArgumentException(
-                    "Invalid subscribe data & settings: PASSIVE subscribes can't have a Tx filter");
-        }
-
-        int sessionId;
-
-        try {
-            sessionId = mService.createSession(listener.callback, events);
-            if (DBG) Log.d(TAG, "subscribe: session created - sessionId=" + sessionId);
-            mService.subscribe(sessionId, subscribeData, subscribeSettings);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-
-        return new WifiNanSubscribeSession(this, sessionId);
-    }
-
-    /**
-     * {@hide}
-     */
-    public void subscribe(int sessionId, SubscribeData subscribeData,
-            SubscribeSettings subscribeSettings) {
-        if (VDBG) {
-            Log.v(TAG, "subscribe(): data='" + subscribeData + "', settings=" + subscribeSettings);
-        }
-
-        if (subscribeSettings.mSubscribeType == SubscribeSettings.SUBSCRIBE_TYPE_ACTIVE
-                && subscribeData.mRxFilterLength != 0) {
-            throw new IllegalArgumentException(
-                    "Invalid subscribe data & settings: ACTIVE subscribes can't have an Rx filter");
-        }
-        if (subscribeSettings.mSubscribeType == SubscribeSettings.SUBSCRIBE_TYPE_PASSIVE
-                && subscribeData.mTxFilterLength != 0) {
-            throw new IllegalArgumentException(
-                    "Invalid subscribe data & settings: PASSIVE subscribes can't have a Tx filter");
-        }
-
-        try {
-            mService.subscribe(sessionId, subscribeData, subscribeSettings);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * {@hide}
-     */
-    public void stopSession(int sessionId) {
-        if (DBG) Log.d(TAG, "Stop NAN session #" + sessionId);
-
-        try {
-            mService.stopSession(sessionId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * {@hide}
-     */
-    public void destroySession(int sessionId) {
-        if (DBG) Log.d(TAG, "Destroy NAN session #" + sessionId);
-
-        try {
-            mService.destroySession(sessionId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * {@hide}
-     */
-    public void sendMessage(int sessionId, int peerId, byte[] message, int messageLength,
-            int messageId) {
-        try {
-            if (VDBG) {
-                Log.v(TAG, "sendMessage(): sessionId=" + sessionId + ", peerId=" + peerId
-                        + ", messageLength=" + messageLength + ", messageId=" + messageId);
+        IBinder binder;
+        int clientId;
+        synchronized (mLock) {
+            if (mClientId == INVALID_CLIENT_ID) {
+                Log.w(TAG, "disconnect(): called with invalid client ID - not connected first?");
+                return;
             }
-            mService.sendMessage(sessionId, peerId, message, messageLength, messageId);
+
+            binder = mBinder;
+            clientId = mClientId;
+
+            mLooper = null;
+            mClientId = INVALID_CLIENT_ID;
+        }
+
+        mCloseGuard.close();
+        try {
+            mService.disconnect(clientId, binder);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /** @hide */
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            mCloseGuard.warnIfOpen();
+            disconnect();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Issue a request to the NAN service to create a new NAN publish discovery session, using
+     * the specified {@code publishConfig} configuration. The results of the publish operation
+     * are routed to the callbacks of {@link WifiNanSessionCallback}:
+     * <ul>
+     *     <li>{@link WifiNanSessionCallback#onPublishStarted(WifiNanPublishSession)} is called
+     *     when the publish session is created and provides a handle to the session. Further
+     *     operations on the publish session can be executed on that object.
+     *     <li>{@link WifiNanSessionCallback#onSessionConfigFail(int)} is called if the publish
+     *     operation failed.
+     * </ul>
+     * <p>
+     * Other results of the publish session operations will also be routed to callbacks
+     * on the {@code callback} object. The resulting publish session can be modified using
+     * {@link WifiNanPublishSession#updatePublish(PublishConfig)}.
+     * <p>
+     *      An application must use the {@link WifiNanSession#terminate()} to terminate the publish
+     *      discovery session once it isn't needed. This will free resources as well terminate
+     *      any on-air transmissions.
+     *
+     * @param publishConfig The {@link PublishConfig} specifying the
+     *            configuration of the requested publish session.
+     * @param callback A {@link WifiNanSessionCallback} derived object to be used for session
+     *                 event callbacks.
+     */
+    public void publish(@NonNull PublishConfig publishConfig,
+            @NonNull WifiNanSessionCallback callback) {
+        if (VDBG) Log.v(TAG, "publish(): config=" + publishConfig);
+
+        int clientId;
+        Looper looper;
+        synchronized (mLock) {
+            if (mLooper == null || mClientId == INVALID_CLIENT_ID) {
+                Log.e(TAG, "publish(): called with null looper or invalid client ID - "
+                        + "not connected first?");
+                return;
+            }
+
+            clientId = mClientId;
+            looper = mLooper;
+        }
+        try {
+            mService.publish(clientId, publishConfig,
+                    new WifiNanSessionCallbackProxy(this, looper, true, callback));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    public void updatePublish(int sessionId, PublishConfig publishConfig) {
+        if (VDBG) Log.v(TAG, "updatePublish(): config=" + publishConfig);
+
+        int clientId;
+        synchronized (mLock) {
+            if (mClientId == INVALID_CLIENT_ID) {
+                Log.e(TAG, "updatePublish(): called with invalid client ID - not connected first?");
+                return;
+            }
+
+            clientId = mClientId;
+        }
+        try {
+            mService.updatePublish(clientId, sessionId, publishConfig);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Issue a request to the NAN service to create a new NAN subscribe discovery session, using
+     * the specified {@code subscribeConfig} configuration. The results of the subscribe
+     * operation are routed to the callbacks of {@link WifiNanSessionCallback}:
+     * <ul>
+     *     <li>{@link WifiNanSessionCallback#onSubscribeStarted(WifiNanSubscribeSession)} is called
+     *     when the subscribe session is created and provides a handle to the session. Further
+     *     operations on the subscribe session can be executed on that object.
+     *     <li>{@link WifiNanSessionCallback#onSessionConfigFail(int)} is called if the subscribe
+     *     operation failed.
+     * </ul>
+     * <p>
+     * Other results of the subscribe session operations will also be routed to callbacks
+     * on the {@code callback} object. The resulting subscribe session can be modified using
+     * {@link WifiNanSubscribeSession#updateSubscribe(SubscribeConfig)}.
+     * <p>
+     *      An application must use the {@link WifiNanSession#terminate()} to terminate the
+     *      subscribe discovery session once it isn't needed. This will free resources as well
+     *      terminate any on-air transmissions.
+     *
+     * @param subscribeConfig The {@link SubscribeConfig} specifying the
+     *            configuration of the requested subscribe session.
+     * @param callback A {@link WifiNanSessionCallback} derived object to be used for session
+     *                 event callbacks.
+     */
+    public void subscribe(@NonNull SubscribeConfig subscribeConfig,
+            @NonNull WifiNanSessionCallback callback) {
+        if (VDBG) {
+            Log.v(TAG, "subscribe(): config=" + subscribeConfig);
+        }
+
+        int clientId;
+        Looper looper;
+        synchronized (mLock) {
+            if (mLooper == null || mClientId == INVALID_CLIENT_ID) {
+                Log.e(TAG, "subscribe(): called with null looper or invalid client ID - "
+                        + "not connected first?");
+                return;
+            }
+
+            clientId = mClientId;
+            looper = mLooper;
+        }
+
+        try {
+            mService.subscribe(clientId, subscribeConfig,
+                    new WifiNanSessionCallbackProxy(this, looper, false, callback));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    public void updateSubscribe(int sessionId, SubscribeConfig subscribeConfig) {
+        if (VDBG) {
+            Log.v(TAG, "subscribe(): config=" + subscribeConfig);
+        }
+
+        int clientId;
+        synchronized (mLock) {
+            if (mClientId == INVALID_CLIENT_ID) {
+                Log.e(TAG,
+                        "updateSubscribe(): called with invalid client ID - not connected first?");
+                return;
+            }
+
+            clientId = mClientId;
+        }
+
+        try {
+            mService.updateSubscribe(clientId, sessionId, subscribeConfig);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    public void terminateSession(int sessionId) {
+        if (DBG) Log.d(TAG, "Terminate NAN session #" + sessionId);
+
+        int clientId;
+        synchronized (mLock) {
+            if (mClientId == INVALID_CLIENT_ID) {
+                Log.e(TAG,
+                        "terminateSession(): called with invalid client ID - not connected first?");
+                return;
+            }
+
+            clientId = mClientId;
+        }
+
+        try {
+            mService.terminateSession(clientId, sessionId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    public void sendMessage(int sessionId, int peerId, byte[] message, int messageId,
+            int retryCount) {
+        if (VDBG) {
+            Log.v(TAG, "sendMessage(): sessionId=" + sessionId + ", peerId=" + peerId
+                    + ", messageId=" + messageId + ", retryCount=" + retryCount);
+        }
+
+        int clientId;
+        synchronized (mLock) {
+            if (mClientId == INVALID_CLIENT_ID) {
+                Log.e(TAG, "sendMessage(): called with invalid client ID - not connected first?");
+                return;
+            }
+
+            clientId = mClientId;
+        }
+
+        try {
+            mService.sendMessage(clientId, sessionId, peerId, message, messageId, retryCount);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    public void startRanging(int sessionId, RttManager.RttParams[] params,
+                             RttManager.RttListener listener) {
+        if (VDBG) {
+            Log.v(TAG, "startRanging: sessionId=" + sessionId + ", " + "params="
+                    + Arrays.toString(params) + ", listener=" + listener);
+        }
+
+        int clientId;
+        synchronized (mLock) {
+            if (mClientId == INVALID_CLIENT_ID) {
+                Log.e(TAG, "startRanging(): called with invalid client ID - not connected first?");
+                return;
+            }
+
+            clientId = mClientId;
+        }
+
+        int rangingKey = 0;
+        try {
+            rangingKey = mService.startRanging(clientId, sessionId,
+                    new RttManager.ParcelableRttParams(params));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        synchronized (mLock) {
+            mRangingListeners.put(rangingKey, listener);
+        }
+    }
+
+    /** @hide */
+    public String createNetworkSpecifier(@DataPathRole int role, int sessionId, int peerId,
+            byte[] token) {
+        if (VDBG) {
+            Log.v(TAG, "createNetworkSpecifier: role=" + role + ", sessionId=" + sessionId
+                    + ", peerId=" + peerId + ", token=" + token);
+        }
+
+        int type;
+        if (token != null && peerId != 0) {
+            type = NETWORK_SPECIFIER_TYPE_1A;
+        } else if (token == null && peerId != 0) {
+            type = NETWORK_SPECIFIER_TYPE_1B;
+        } else if (token != null && peerId == 0) {
+            type = NETWORK_SPECIFIER_TYPE_1C;
+        } else {
+            type = NETWORK_SPECIFIER_TYPE_1D;
+        }
+
+        if (role != WIFI_NAN_DATA_PATH_ROLE_INITIATOR
+                && role != WIFI_NAN_DATA_PATH_ROLE_RESPONDER) {
+            throw new IllegalArgumentException(
+                    "createNetworkSpecifier: Invalid 'role' argument when creating a network "
+                            + "specifier");
+        }
+        if (role == WIFI_NAN_DATA_PATH_ROLE_INITIATOR) {
+            if (token == null) {
+                throw new IllegalArgumentException(
+                        "createNetworkSpecifier: Invalid null token - not permitted on INITIATOR");
+            }
+            if (peerId == 0) {
+                throw new IllegalArgumentException(
+                        "createNetworkSpecifier: Invalid peer ID (value of 0) - not permitted on "
+                                + "INITIATOR");
+            }
+        }
+
+        int clientId;
+        synchronized (mLock) {
+            if (mClientId == INVALID_CLIENT_ID) {
+                Log.e(TAG,
+                        "createNetworkSpecifier: called with invalid client ID - not connected "
+                                + "first?");
+                return null;
+            }
+
+            clientId = mClientId;
+        }
+
+        JSONObject json;
+        try {
+            json = new JSONObject();
+            json.put(NETWORK_SPECIFIER_KEY_TYPE, type);
+            json.put(NETWORK_SPECIFIER_KEY_ROLE, role);
+            json.put(NETWORK_SPECIFIER_KEY_CLIENT_ID, clientId);
+            json.put(NETWORK_SPECIFIER_KEY_SESSION_ID, sessionId);
+            if (peerId != 0) {
+                json.put(NETWORK_SPECIFIER_KEY_PEER_ID, peerId);
+            }
+            if (token != null) {
+                json.put(NETWORK_SPECIFIER_KEY_TOKEN,
+                        Base64.encodeToString(token, 0, token.length, Base64.DEFAULT));
+            }
+        } catch (JSONException e) {
+            return "";
+        }
+
+        return json.toString();
+    }
+
+    /**
+     * Create a {@link NetworkRequest.Builder#setNetworkSpecifier(String)} for a
+     * WiFi NAN connection to the specified peer. The
+     * {@link NetworkRequest.Builder#addTransportType(int)} should be set to
+     * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_NAN}.
+     * <p>
+     *     This API is targeted for applications which can obtain the peer MAC address using OOB
+     *     (out-of-band) discovery. NAN discovery does not provide the MAC address of the peer -
+     *     when using NAN discovery use the alternative network specifier method -
+     *     {@link WifiNanSession#createNetworkSpecifier(int, int, byte[])}.
+     *
+     * @param role  The role of this device:
+     *              {@link WifiNanManager#WIFI_NAN_DATA_PATH_ROLE_INITIATOR} or
+     *              {@link WifiNanManager#WIFI_NAN_DATA_PATH_ROLE_RESPONDER}
+     * @param peer  The MAC address of the peer's NAN discovery interface. On a RESPONDER this
+     *              value is used to gate the acceptance of a connection request from only that
+     *              peer. A RESPONDER may specified a null - indicating that it will accept
+     *              connection requests from any device.
+     * @param token An arbitrary token (message) to be used to match connection initiation request
+     *              to a responder setup. A RESPONDER is set up with a {@code token} which must
+     *              be matched by the token provided by the INITIATOR. A null token is permitted
+     *              on the RESPONDER and matches any peer token. An empty ({@code ""}) token is
+     *              not the same as a null token and requires the peer token to be empty as well.
+     *
+     * @return A string to be used to construct
+     * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} to pass to {@link
+     * android.net.ConnectivityManager#requestNetwork(NetworkRequest, ConnectivityManager.NetworkCallback)}
+     * [or other varieties of that API].
+     */
+    public String createNetworkSpecifier(@DataPathRole int role, @Nullable byte[] peer,
+            @Nullable byte[] token) {
+        if (VDBG) {
+            Log.v(TAG, "createNetworkSpecifier: role=" + role + ", token=" + token);
+        }
+
+        int type;
+        if (token != null && peer != null) {
+            type = NETWORK_SPECIFIER_TYPE_2A;
+        } else if (token == null && peer != null) {
+            type = NETWORK_SPECIFIER_TYPE_2B;
+        } else if (token != null && peer == null) {
+            type = NETWORK_SPECIFIER_TYPE_2C;
+        } else { // both are null
+            type = NETWORK_SPECIFIER_TYPE_2D;
+        }
+
+        if (role != WIFI_NAN_DATA_PATH_ROLE_INITIATOR
+                && role != WIFI_NAN_DATA_PATH_ROLE_RESPONDER) {
+            throw new IllegalArgumentException(
+                    "createNetworkSpecifier: Invalid 'role' argument when creating a network "
+                            + "specifier");
+        }
+        if (role == WIFI_NAN_DATA_PATH_ROLE_INITIATOR) {
+            if (peer == null || peer.length != 6) {
+                throw new IllegalArgumentException(
+                        "createNetworkSpecifier: Invalid peer MAC address");
+            }
+            if (token == null) {
+                throw new IllegalArgumentException(
+                        "createNetworkSpecifier: Invalid null token - not permitted on INITIATOR");
+            }
+        } else {
+            if (peer != null && peer.length != 6) {
+                throw new IllegalArgumentException(
+                        "createNetworkSpecifier: Invalid peer MAC address");
+            }
+        }
+
+        int clientId;
+        synchronized (mLock) {
+            if (mClientId == INVALID_CLIENT_ID) {
+                Log.e(TAG,
+                        "createNetworkSpecifier: called with invalid client ID - not connected "
+                                + "first?");
+                return null;
+            }
+
+            clientId = mClientId;
+        }
+
+        JSONObject json;
+        try {
+            json = new JSONObject();
+            json.put(NETWORK_SPECIFIER_KEY_TYPE, type);
+            json.put(NETWORK_SPECIFIER_KEY_ROLE, role);
+            json.put(NETWORK_SPECIFIER_KEY_CLIENT_ID, clientId);
+            if (peer != null) {
+                json.put(NETWORK_SPECIFIER_KEY_PEER_MAC, new String(HexEncoding.encode(peer)));
+            }
+            if (token != null) {
+                json.put(NETWORK_SPECIFIER_KEY_TOKEN,
+                        Base64.encodeToString(token, 0, token.length, Base64.DEFAULT));
+            }
+        } catch (JSONException e) {
+            return "";
+        }
+
+        return json.toString();
+    }
+
+    private static class WifiNanEventCallbackProxy extends IWifiNanEventCallback.Stub {
+        private static final int CALLBACK_CONNECT_SUCCESS = 0;
+        private static final int CALLBACK_CONNECT_FAIL = 1;
+        private static final int CALLBACK_IDENTITY_CHANGED = 2;
+        private static final int CALLBACK_RANGING_SUCCESS = 3;
+        private static final int CALLBACK_RANGING_FAILURE = 4;
+        private static final int CALLBACK_RANGING_ABORTED = 5;
+
+        private final Handler mHandler;
+        private final WeakReference<WifiNanManager> mNanManager;
+
+        RttManager.RttListener getAndRemoveRangingListener(int rangingId) {
+            WifiNanManager mgr = mNanManager.get();
+            if (mgr == null) {
+                Log.w(TAG, "getAndRemoveRangingListener: called post GC");
+                return null;
+            }
+
+            synchronized (mgr.mLock) {
+                RttManager.RttListener listener = mgr.mRangingListeners.get(rangingId);
+                mgr.mRangingListeners.delete(rangingId);
+                return listener;
+            }
+        }
+
+        /**
+         * Constructs a {@link WifiNanEventCallback} using the specified looper.
+         * All callbacks will delivered on the thread of the specified looper.
+         *
+         * @param looper The looper on which to execute the callbacks.
+         */
+        WifiNanEventCallbackProxy(WifiNanManager mgr, Looper looper,
+                final WifiNanEventCallback originalCallback) {
+            mNanManager = new WeakReference<>(mgr);
+
+            if (VDBG) Log.v(TAG, "WifiNanEventCallbackProxy ctor: looper=" + looper);
+            mHandler = new Handler(looper) {
+                @Override
+                public void handleMessage(Message msg) {
+                    if (DBG) {
+                        Log.d(TAG, "WifiNanEventCallbackProxy: What=" + msg.what + ", msg=" + msg);
+                    }
+
+                    WifiNanManager mgr = mNanManager.get();
+                    if (mgr == null) {
+                        Log.w(TAG, "WifiNanEventCallbackProxy: handleMessage post GC");
+                        return;
+                    }
+
+                    switch (msg.what) {
+                        case CALLBACK_CONNECT_SUCCESS:
+                            originalCallback.onConnectSuccess();
+                            break;
+                        case CALLBACK_CONNECT_FAIL:
+                            synchronized (mgr.mLock) {
+                                mgr.mLooper = null;
+                                mgr.mClientId = INVALID_CLIENT_ID;
+                            }
+                            mNanManager.clear();
+                            originalCallback.onConnectFail(msg.arg1);
+                            break;
+                        case CALLBACK_IDENTITY_CHANGED:
+                            originalCallback.onIdentityChanged((byte[]) msg.obj);
+                            break;
+                        case CALLBACK_RANGING_SUCCESS: {
+                            RttManager.RttListener listener = getAndRemoveRangingListener(msg.arg1);
+                            if (listener == null) {
+                                Log.e(TAG, "CALLBACK_RANGING_SUCCESS rangingId=" + msg.arg1
+                                        + ": no listener registered (anymore)");
+                            } else {
+                                listener.onSuccess(
+                                        ((RttManager.ParcelableRttResults) msg.obj).mResults);
+                            }
+                            break;
+                        }
+                        case CALLBACK_RANGING_FAILURE: {
+                            RttManager.RttListener listener = getAndRemoveRangingListener(msg.arg1);
+                            if (listener == null) {
+                                Log.e(TAG, "CALLBACK_RANGING_SUCCESS rangingId=" + msg.arg1
+                                        + ": no listener registered (anymore)");
+                            } else {
+                                listener.onFailure(msg.arg2, (String) msg.obj);
+                            }
+                            break;
+                        }
+                        case CALLBACK_RANGING_ABORTED: {
+                            RttManager.RttListener listener = getAndRemoveRangingListener(msg.arg1);
+                            if (listener == null) {
+                                Log.e(TAG, "CALLBACK_RANGING_SUCCESS rangingId=" + msg.arg1
+                                        + ": no listener registered (anymore)");
+                            } else {
+                                listener.onAborted();
+                            }
+                            break;
+                        }
+                    }
+                }
+            };
+        }
+
+        @Override
+        public void onConnectSuccess() {
+            if (VDBG) Log.v(TAG, "onConnectSuccess");
+
+            Message msg = mHandler.obtainMessage(CALLBACK_CONNECT_SUCCESS);
+            mHandler.sendMessage(msg);
+        }
+
+        @Override
+        public void onConnectFail(int reason) {
+            if (VDBG) Log.v(TAG, "onConfigFailed: reason=" + reason);
+
+            Message msg = mHandler.obtainMessage(CALLBACK_CONNECT_FAIL);
+            msg.arg1 = reason;
+            mHandler.sendMessage(msg);
+        }
+
+        @Override
+        public void onIdentityChanged(byte[] mac) {
+            if (VDBG) Log.v(TAG, "onIdentityChanged: mac=" + new String(HexEncoding.encode(mac)));
+
+            Message msg = mHandler.obtainMessage(CALLBACK_IDENTITY_CHANGED);
+            msg.obj = mac;
+            mHandler.sendMessage(msg);
+        }
+
+        @Override
+        public void onRangingSuccess(int rangingId, RttManager.ParcelableRttResults results) {
+            if (VDBG) {
+                Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId + ", results=" + results);
+            }
+
+            Message msg = mHandler.obtainMessage(CALLBACK_RANGING_SUCCESS);
+            msg.arg1 = rangingId;
+            msg.obj = results;
+            mHandler.sendMessage(msg);
+        }
+
+        @Override
+        public void onRangingFailure(int rangingId, int reason, String description) {
+            if (VDBG) {
+                Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId + ", reason=" + reason
+                        + ", description=" + description);
+            }
+
+            Message msg = mHandler.obtainMessage(CALLBACK_RANGING_FAILURE);
+            msg.arg1 = rangingId;
+            msg.arg2 = reason;
+            msg.obj = description;
+            mHandler.sendMessage(msg);
+
+        }
+
+        @Override
+        public void onRangingAborted(int rangingId) {
+            if (VDBG) Log.v(TAG, "onRangingAborted: rangingId=" + rangingId);
+
+            Message msg = mHandler.obtainMessage(CALLBACK_RANGING_ABORTED);
+            msg.arg1 = rangingId;
+            mHandler.sendMessage(msg);
+
+        }
+    }
+
+    private static class WifiNanSessionCallbackProxy extends IWifiNanSessionCallback.Stub {
+        private static final int CALLBACK_SESSION_STARTED = 0;
+        private static final int CALLBACK_SESSION_CONFIG_SUCCESS = 1;
+        private static final int CALLBACK_SESSION_CONFIG_FAIL = 2;
+        private static final int CALLBACK_SESSION_TERMINATED = 3;
+        private static final int CALLBACK_MATCH = 4;
+        private static final int CALLBACK_MESSAGE_SEND_SUCCESS = 5;
+        private static final int CALLBACK_MESSAGE_SEND_FAIL = 6;
+        private static final int CALLBACK_MESSAGE_RECEIVED = 7;
+
+        private static final String MESSAGE_BUNDLE_KEY_MESSAGE = "message";
+        private static final String MESSAGE_BUNDLE_KEY_MESSAGE2 = "message2";
+
+        private final WeakReference<WifiNanManager> mNanManager;
+        private final boolean mIsPublish;
+        private final WifiNanSessionCallback mOriginalCallback;
+
+        private final Handler mHandler;
+        private WifiNanSession mSession;
+
+        WifiNanSessionCallbackProxy(WifiNanManager mgr, Looper looper, boolean isPublish,
+                WifiNanSessionCallback originalCallback) {
+            mNanManager = new WeakReference<>(mgr);
+            mIsPublish = isPublish;
+            mOriginalCallback = originalCallback;
+
+            if (VDBG) Log.v(TAG, "WifiNanSessionCallbackProxy ctor: isPublish=" + isPublish);
+
+            mHandler = new Handler(looper) {
+                @Override
+                public void handleMessage(Message msg) {
+                    if (DBG) Log.d(TAG, "What=" + msg.what + ", msg=" + msg);
+
+                    if (mNanManager.get() == null) {
+                        Log.w(TAG, "WifiNanSessionCallbackProxy: handleMessage post GC");
+                        return;
+                    }
+
+                    switch (msg.what) {
+                        case CALLBACK_SESSION_STARTED:
+                            onProxySessionStarted(msg.arg1);
+                            break;
+                        case CALLBACK_SESSION_CONFIG_SUCCESS:
+                            mOriginalCallback.onSessionConfigSuccess();
+                            break;
+                        case CALLBACK_SESSION_CONFIG_FAIL:
+                            mOriginalCallback.onSessionConfigFail(msg.arg1);
+                            if (mSession == null) {
+                                /*
+                                 * creation failed (as opposed to update
+                                 * failing)
+                                 */
+                                mNanManager.clear();
+                            }
+                            break;
+                        case CALLBACK_SESSION_TERMINATED:
+                            onProxySessionTerminated(msg.arg1);
+                            break;
+                        case CALLBACK_MATCH:
+                            mOriginalCallback.onMatch(
+                                    msg.arg1,
+                                    msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE),
+                                    msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE2));
+                            break;
+                        case CALLBACK_MESSAGE_SEND_SUCCESS:
+                            mOriginalCallback.onMessageSendSuccess(msg.arg1);
+                            break;
+                        case CALLBACK_MESSAGE_SEND_FAIL:
+                            mOriginalCallback.onMessageSendFail(msg.arg1, msg.arg2);
+                            break;
+                        case CALLBACK_MESSAGE_RECEIVED:
+                            mOriginalCallback.onMessageReceived(msg.arg1, (byte[]) msg.obj);
+                            break;
+                    }
+                }
+            };
+        }
+
+        @Override
+        public void onSessionStarted(int sessionId) {
+            if (VDBG) Log.v(TAG, "onSessionStarted: sessionId=" + sessionId);
+
+            Message msg = mHandler.obtainMessage(CALLBACK_SESSION_STARTED);
+            msg.arg1 = sessionId;
+            mHandler.sendMessage(msg);
+        }
+
+        @Override
+        public void onSessionConfigSuccess() {
+            if (VDBG) Log.v(TAG, "onSessionConfigSuccess");
+
+            Message msg = mHandler.obtainMessage(CALLBACK_SESSION_CONFIG_SUCCESS);
+            mHandler.sendMessage(msg);
+        }
+
+        @Override
+        public void onSessionConfigFail(int reason) {
+            if (VDBG) Log.v(TAG, "onSessionConfigFail: reason=" + reason);
+
+            Message msg = mHandler.obtainMessage(CALLBACK_SESSION_CONFIG_FAIL);
+            msg.arg1 = reason;
+            mHandler.sendMessage(msg);
+        }
+
+        @Override
+        public void onSessionTerminated(int reason) {
+            if (VDBG) Log.v(TAG, "onSessionTerminated: reason=" + reason);
+
+            Message msg = mHandler.obtainMessage(CALLBACK_SESSION_TERMINATED);
+            msg.arg1 = reason;
+            mHandler.sendMessage(msg);
+        }
+
+        @Override
+        public void onMatch(int peerId, byte[] serviceSpecificInfo, byte[] matchFilter) {
+            if (VDBG) Log.v(TAG, "onMatch: peerId=" + peerId);
+
+            Bundle data = new Bundle();
+            data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE, serviceSpecificInfo);
+            data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE2, matchFilter);
+
+            Message msg = mHandler.obtainMessage(CALLBACK_MATCH);
+            msg.arg1 = peerId;
+            msg.setData(data);
+            mHandler.sendMessage(msg);
+        }
+
+        @Override
+        public void onMessageSendSuccess(int messageId) {
+            if (VDBG) Log.v(TAG, "onMessageSendSuccess");
+
+            Message msg = mHandler.obtainMessage(CALLBACK_MESSAGE_SEND_SUCCESS);
+            msg.arg1 = messageId;
+            mHandler.sendMessage(msg);
+        }
+
+        @Override
+        public void onMessageSendFail(int messageId, int reason) {
+            if (VDBG) Log.v(TAG, "onMessageSendFail: reason=" + reason);
+
+            Message msg = mHandler.obtainMessage(CALLBACK_MESSAGE_SEND_FAIL);
+            msg.arg1 = messageId;
+            msg.arg2 = reason;
+            mHandler.sendMessage(msg);
+        }
+
+        @Override
+        public void onMessageReceived(int peerId, byte[] message) {
+            if (VDBG) {
+                Log.v(TAG, "onMessageReceived: peerId='" + peerId);
+            }
+
+            Message msg = mHandler.obtainMessage(CALLBACK_MESSAGE_RECEIVED);
+            msg.arg1 = peerId;
+            msg.obj = message;
+            mHandler.sendMessage(msg);
+        }
+
+        /*
+         * Proxied methods
+         */
+        public void onProxySessionStarted(int sessionId) {
+            if (VDBG) Log.v(TAG, "Proxy: onSessionStarted: sessionId=" + sessionId);
+            if (mSession != null) {
+                Log.e(TAG,
+                        "onSessionStarted: sessionId=" + sessionId + ": session already created!?");
+                throw new IllegalStateException(
+                        "onSessionStarted: sessionId=" + sessionId + ": session already created!?");
+            }
+
+            WifiNanManager mgr = mNanManager.get();
+            if (mgr == null) {
+                Log.w(TAG, "onProxySessionStarted: mgr GC'd");
+                return;
+            }
+
+            if (mIsPublish) {
+                WifiNanPublishSession session = new WifiNanPublishSession(mgr, sessionId);
+                mSession = session;
+                mOriginalCallback.onPublishStarted(session);
+            } else {
+                WifiNanSubscribeSession session = new WifiNanSubscribeSession(mgr, sessionId);
+                mSession = session;
+                mOriginalCallback.onSubscribeStarted(session);
+            }
+        }
+
+        public void onProxySessionTerminated(int reason) {
+            if (VDBG) Log.v(TAG, "Proxy: onSessionTerminated: reason=" + reason);
+            if (mSession != null) {
+                mSession.setTerminated();
+                mSession = null;
+            } else {
+                Log.w(TAG, "Proxy: onSessionTerminated called but mSession is null!?");
+            }
+            mNanManager.clear();
+            mOriginalCallback.onSessionTerminated(reason);
+        }
+    }
 }
diff --git a/wifi/java/android/net/wifi/nan/WifiNanPublishSession.java b/wifi/java/android/net/wifi/nan/WifiNanPublishSession.java
index 81b38f4..ccd7fae 100644
--- a/wifi/java/android/net/wifi/nan/WifiNanPublishSession.java
+++ b/wifi/java/android/net/wifi/nan/WifiNanPublishSession.java
@@ -16,32 +16,54 @@
 
 package android.net.wifi.nan;
 
+import android.annotation.NonNull;
+import android.util.Log;
+
 /**
- * A representation of a NAN publish session. Created when
- * {@link WifiNanManager#publish(PublishData, PublishSettings, WifiNanSessionListener, int)}
- * is executed. The object can be used to stop and re-start (re-configure) the
- * publish session.
+ * A class representing a NAN publish session. Created when
+ * {@link WifiNanManager#publish(PublishConfig, WifiNanSessionCallback)} is called and a
+ * discovery session is created and returned in
+ * {@link WifiNanSessionCallback#onPublishStarted(WifiNanPublishSession)}. See baseline
+ * functionality of all discovery sessions in {@link WifiNanSession}. This object allows updating
+ * an existing/running publish discovery session using {@link #updatePublish(PublishConfig)}.
  *
  * @hide PROPOSED_NAN_API
  */
 public class WifiNanPublishSession extends WifiNanSession {
-    /**
-     * {@hide}
-     */
+    private static final String TAG = "WifiNanPublishSession";
+
+    /** @hide */
     public WifiNanPublishSession(WifiNanManager manager, int sessionId) {
         super(manager, sessionId);
     }
 
     /**
-     * Restart/re-configure the publish session. Note that the
-     * {@link WifiNanSessionListener} is not replaced - the same listener used at
-     * creation is still used.
+     * Re-configure the currently active publish session. The
+     * {@link WifiNanSessionCallback} is not replaced - the same listener used
+     * at creation is still used. The results of the configuration are returned using
+     * {@link WifiNanSessionCallback}:
+     * <ul>
+     *     <li>{@link WifiNanSessionCallback#onSessionConfigSuccess()}: configuration update
+     *     succeeded.
+     *     <li>{@link WifiNanSessionCallback#onSessionConfigFail(int)}: configuration update
+     *     failed. The publish discovery session is still running using its previous
+     *     configuration (i.e. update failure does not terminate the session).
+     * </ul>
      *
-     * @param publishData The data ({@link PublishData}) to publish.
-     * @param publishSettings The settings ({@link PublishSettings}) of the
-     *            publish session.
+     * @param publishConfig The new discovery publish session configuration ({@link PublishConfig}).
      */
-    public void publish(PublishData publishData, PublishSettings publishSettings) {
-        mManager.publish(mSessionId, publishData, publishSettings);
+    public void updatePublish(@NonNull PublishConfig publishConfig) {
+        if (mTerminated) {
+            Log.w(TAG, "updatePublish: called on terminated session");
+            return;
+        } else {
+            WifiNanManager mgr = mMgr.get();
+            if (mgr == null) {
+                Log.w(TAG, "updatePublish: called post GC on WifiNanManager");
+                return;
+            }
+
+            mgr.updatePublish(mSessionId, publishConfig);
+        }
     }
 }
diff --git a/wifi/java/android/net/wifi/nan/WifiNanSession.java b/wifi/java/android/net/wifi/nan/WifiNanSession.java
index bc1787f..005ca29 100644
--- a/wifi/java/android/net/wifi/nan/WifiNanSession.java
+++ b/wifi/java/android/net/wifi/nan/WifiNanSession.java
@@ -16,12 +16,28 @@
 
 package android.net.wifi.nan;
 
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.net.wifi.RttManager;
 import android.util.Log;
 
+import dalvik.system.CloseGuard;
+
+import java.lang.ref.WeakReference;
+
 /**
- * A representation of a single publish or subscribe NAN session. This object
+ * A class representing a single publish or subscribe NAN session. This object
  * will not be created directly - only its child classes are available:
- * {@link WifiNanPublishSession} and {@link WifiNanSubscribeSession}.
+ * {@link WifiNanPublishSession} and {@link WifiNanSubscribeSession}. This class provides
+ * functionality common to both publish and subscribe discovery sessions:
+ * <ul>
+ *     <li>Sending messages: {@link #sendMessage(int, byte[], int)} or
+ *     {@link #sendMessage(int, byte[], int, int)} methods.
+ *     <li>Creating a network-specifier when requesting a NAN connection:
+ *     {@link #createNetworkSpecifier(int, int, byte[])}.
+ * </ul>
+ * The {@link #terminate()} method must be called to terminate discovery sessions once they are
+ * no longer needed.
  *
  * @hide PROPOSED_NAN_API
  */
@@ -30,84 +46,234 @@
     private static final boolean DBG = false;
     private static final boolean VDBG = false; // STOPSHIP if true
 
-    /**
-     * {@hide}
-     */
-    protected WifiNanManager mManager;
+    private static final int MAX_SEND_RETRY_COUNT = 5;
+
+    /** @hide */
+    protected WeakReference<WifiNanManager> mMgr;
+    /** @hide */
+    protected final int mSessionId;
+    /** @hide */
+    protected boolean mTerminated = false;
+
+    private final CloseGuard mCloseGuard = CloseGuard.get();
 
     /**
-     * {@hide}
+     * Return the maximum permitted retry count when sending messages using
+     * {@link #sendMessage(int, byte[], int, int)}.
+     *
+     * @return Maximum retry count when sending messages.
      */
-    protected int mSessionId;
+    public static int getMaxSendRetryCount() {
+        return MAX_SEND_RETRY_COUNT;
+    }
 
-    /**
-     * {@hide}
-     */
-    private boolean mDestroyed;
-
-    /**
-     * {@hide}
-     */
+    /** @hide */
     public WifiNanSession(WifiNanManager manager, int sessionId) {
         if (VDBG) Log.v(TAG, "New client created: manager=" + manager + ", sessionId=" + sessionId);
 
-        mManager = manager;
+        mMgr = new WeakReference<>(manager);
         mSessionId = sessionId;
-        mDestroyed = false;
+
+        mCloseGuard.open("terminate");
     }
 
     /**
-     * Terminate the current publish or subscribe session - i.e. stop
-     * transmitting packet on-air (for an active session) or listening for
-     * matches (for a passive session). Note that the session may still receive
-     * incoming messages and may be re-configured/re-started at a later time.
+     * Terminate the publish or subscribe session - free any resources, and stop
+     * transmitting packets on-air (for an active session) or listening for
+     * matches (for a passive session). The session may not be used for any
+     * additional operations after termination.
+     * <p>
+     *     This operation must be done on a session which is no longer needed. Otherwise system
+     *     resources will continue to be utilized until the application terminates. The only
+     *     exception is a session for which we received a termination callback,
+     *     {@link WifiNanSessionCallback#onSessionTerminated(int)}.
      */
-    public void stop() {
-        mManager.stopSession(mSessionId);
+    public void terminate() {
+        WifiNanManager mgr = mMgr.get();
+        if (mgr == null) {
+            Log.w(TAG, "terminate: called post GC on WifiNanManager");
+            return;
+        }
+        mgr.terminateSession(mSessionId);
+        mTerminated = true;
+        mMgr.clear();
+        mCloseGuard.close();
     }
 
     /**
-     * Destroy the current publish or subscribe session. Performs a
-     * {@link WifiNanSession#stop()} function but in addition destroys the session -
-     * it will not be able to receive any messages or to be restarted at a later
-     * time.
+     * Sets the status of the session to terminated - i.e. an indication that
+     * already terminated rather than executing a termination.
+     *
+     * @hide
      */
-    public void destroy() {
-        mManager.destroySession(mSessionId);
-        mDestroyed = true;
+    public void setTerminated() {
+        if (mTerminated) {
+            Log.w(TAG, "terminate: already terminated.");
+            return;
+        }
+        mTerminated = true;
+        mMgr.clear();
+        mCloseGuard.close();
     }
 
-    /**
-     * {@hide}
-     */
+    /** @hide */
     @Override
     protected void finalize() throws Throwable {
-        if (!mDestroyed) {
-            Log.w(TAG, "WifiNanSession mSessionId=" + mSessionId
-                            + " was not explicitly destroyed. The session may use resources until "
-                            + "destroyed so step should be done explicitly");
+        try {
+            if (!mTerminated) {
+                mCloseGuard.warnIfOpen();
+                terminate();
+            }
+        } finally {
+            super.finalize();
         }
-        destroy();
     }
 
     /**
-     * Sends a message to the specified destination. Message transmission is
-     * part of the current discovery session - i.e. executed subsequent to a
-     * publish/subscribe
-     * {@link WifiNanSessionListener#onMatch(int, byte[], int, byte[], int)}
-     * event.
+     * Sends a message to the specified destination. NAN messages are transmitted in the context
+     * of a discovery session - executed subsequent to a publish/subscribe
+     * {@link WifiNanSessionCallback#onMatch(int, byte[], byte[])} event.
+     * <p>
+     *     NAN messages are not guaranteed delivery. Callbacks on {@link WifiNanSessionCallback}
+     *     indicate message was transmitted successfully,
+     *     {@link WifiNanSessionCallback#onMessageSendSuccess(int)}, or transmission failed
+     *     (possibly after several retries) -
+     *     {@link WifiNanSessionCallback#onMessageSendFail(int, int)}.
+     * <p>
+     *     The peer will get a callback indicating a message was received using
+     *     {@link WifiNanSessionCallback#onMessageReceived(int, byte[])}.
      *
      * @param peerId The peer's ID for the message. Must be a result of an
-     *            {@link WifiNanSessionListener#onMatch(int, byte[], int, byte[], int)}
-     *            event.
+     *            {@link WifiNanSessionCallback#onMatch(int, byte[], byte[])} or
+     *            {@link WifiNanSessionCallback#onMessageReceived(int, byte[])} events.
      * @param message The message to be transmitted.
-     * @param messageLength The number of bytes from the {@code message} to be
-     *            transmitted.
-     * @param messageId An arbitrary integer used by the caller to identify the
-     *            message. The same integer ID will be returned in the callbacks
-     *            indicated message send success or failure.
+     * @param messageId An arbitrary integer used by the caller to identify the message. The same
+     *            integer ID will be returned in the callbacks indicating message send success or
+     *            failure. The {@code messageId} is not used internally by the NAN service - it
+     *                  can be arbitrary and non-unique.
+     * @param retryCount An integer specifying how many additional service-level (as opposed to PHY
+     *            or MAC level) retries should be attempted if there is no ACK from the receiver
+     *            (note: no retransmissions are attempted in other failure cases). A value of 0
+     *            indicates no retries. Max permitted value is {@link #getMaxSendRetryCount()}.
      */
-    public void sendMessage(int peerId, byte[] message, int messageLength, int messageId) {
-        mManager.sendMessage(mSessionId, peerId, message, messageLength, messageId);
+    public void sendMessage(int peerId, @Nullable byte[] message, int messageId, int retryCount) {
+        if (mTerminated) {
+            Log.w(TAG, "sendMessage: called on terminated session");
+            return;
+        } else {
+            WifiNanManager mgr = mMgr.get();
+            if (mgr == null) {
+                Log.w(TAG, "sendMessage: called post GC on WifiNanManager");
+                return;
+            }
+
+            mgr.sendMessage(mSessionId, peerId, message, messageId, retryCount);
+        }
+    }
+
+    /**
+     * Sends a message to the specified destination. NAN messages are transmitted in the context
+     * of a discovery session - executed subsequent to a publish/subscribe
+     * {@link WifiNanSessionCallback#onMatch(int, byte[], byte[])} event.
+     * <p>
+     *     NAN messages are not guaranteed delivery. Callbacks on {@link WifiNanSessionCallback}
+     *     indicate message was transmitted successfully,
+     *     {@link WifiNanSessionCallback#onMessageSendSuccess(int)}, or transmission failed
+     *     (possibly after several retries) -
+     *     {@link WifiNanSessionCallback#onMessageSendFail(int, int)}.
+     * <p>
+     *     The peer will get a callback indicating a message was received using
+     *     {@link WifiNanSessionCallback#onMessageReceived(int, byte[])}.
+     * Equivalent to {@link #sendMessage(int, byte[], int, int)} with a {@code retryCount} of
+     * 0.
+     *
+     * @param peerId The peer's ID for the message. Must be a result of an
+     *            {@link WifiNanSessionCallback#onMatch(int, byte[], byte[])} or
+     *            {@link WifiNanSessionCallback#onMessageReceived(int, byte[])} events.
+     * @param message The message to be transmitted.
+     * @param messageId An arbitrary integer used by the caller to identify the message. The same
+     *            integer ID will be returned in the callbacks indicating message send success or
+     *            failure. The {@code messageId} is not used internally by the NAN service - it
+     *                  can be arbitrary and non-unique.
+     */
+    public void sendMessage(int peerId, @Nullable byte[] message, int messageId) {
+        sendMessage(peerId, message, messageId, 0);
+    }
+
+    /**
+     * Start a ranging operation with the specified peers. The peer IDs are obtained from an
+     * {@link WifiNanSessionCallback#onMatch(int, byte[], byte[])} or
+     * {@link WifiNanSessionCallback#onMessageReceived(int, byte[])} operation - can only
+     * range devices which are part of an ongoing discovery session.
+     *
+     * @param params   RTT parameters - each corresponding to a specific peer ID (the array sizes
+     *                 must be identical). The
+     *                 {@link android.net.wifi.RttManager.RttParams#bssid} member must be set to
+     *                 a peer ID - not to a MAC address.
+     * @param listener The listener to receive the results of the ranging session.
+     * @hide PROPOSED_NAN_SYSTEM_API [TODO: b/28847998 - track RTT API & visilibity]
+     */
+    public void startRanging(RttManager.RttParams[] params, RttManager.RttListener listener) {
+        if (mTerminated) {
+            Log.w(TAG, "startRanging: called on terminated session");
+            return;
+        } else {
+            WifiNanManager mgr = mMgr.get();
+            if (mgr == null) {
+                Log.w(TAG, "startRanging: called post GC on WifiNanManager");
+                return;
+            }
+
+            mgr.startRanging(mSessionId, params, listener);
+        }
+    }
+
+    /**
+     * Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} for a
+     * WiFi NAN connection to the specified peer. The
+     * {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
+     * {@link android.net.NetworkCapabilities#TRANSPORT_WIFI_NAN}.
+     * <p>
+     * This method should be used when setting up a connection with a peer discovered through NAN
+     * discovery or communication (in such scenarios the MAC address of the peer is shielded by
+     * an opaque peer ID handle). If a NAN connection is needed to a peer discovered using other
+     * OOB (out-of-band) mechanism then use the alternative
+     * {@link WifiNanManager#createNetworkSpecifier(int, byte[], byte[])} method - which uses the
+     * peer's MAC address.
+     *
+     * @param role The role of this device:
+     * {@link WifiNanManager#WIFI_NAN_DATA_PATH_ROLE_INITIATOR} or
+     * {@link WifiNanManager#WIFI_NAN_DATA_PATH_ROLE_RESPONDER}
+     * @param peerId The peer ID obtained through
+     * {@link WifiNanSessionCallback#onMatch(int, byte[], byte[])} or
+     * {@link WifiNanSessionCallback#onMessageReceived(int, byte[])}. On a RESPONDER this
+     *              value is used to gate the acceptance of a connection request from only that
+     *              peer. A RESPONDER may specified a 0 - indicating that it will accept
+     *              connection requests from any device.
+     * @param token An arbitrary token (message) to be used to match connection initiation request
+     *              to a responder setup. A RESPONDER is set up with a {@code token} which must
+     *              be matched by the token provided by the INITIATOR. A null token is permitted
+     *              on the RESPONDER and matches any peer token. An empty ({@code ""}) token is
+     *              not the same as a null token and requires the peer token to be empty as well.
+     *
+     * @return A string to be used to construct
+     * {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(String)} to pass to
+     * {@link android.net.ConnectivityManager#requestNetwork(android.net.NetworkRequest,android.net.ConnectivityManager.NetworkCallback)}
+     * [or other varieties of that API].
+     */
+    public String createNetworkSpecifier(@WifiNanManager.DataPathRole int role, int peerId,
+            @Nullable byte[] token) {
+        if (mTerminated) {
+            Log.w(TAG, "createNetworkSpecifier: called on terminated session");
+            return null;
+        } else {
+            WifiNanManager mgr = mMgr.get();
+            if (mgr == null) {
+                Log.w(TAG, "createNetworkSpecifier: called post GC on WifiNanManager");
+                return null;
+            }
+
+            return mgr.createNetworkSpecifier(role, mSessionId, peerId, token);
+        }
     }
 }
diff --git a/wifi/java/android/net/wifi/nan/WifiNanSessionCallback.java b/wifi/java/android/net/wifi/nan/WifiNanSessionCallback.java
new file mode 100644
index 0000000..8433b99
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/WifiNanSessionCallback.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nan;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Base class for NAN session events callbacks. Should be extended by
+ * applications wanting notifications. The callbacks are set when a
+ * publish or subscribe session is created using
+ * {@link WifiNanManager#publish(PublishConfig, WifiNanSessionCallback)} or
+ * {@link WifiNanManager#subscribe(SubscribeConfig, WifiNanSessionCallback)} .
+ * <p>
+ * A single callback is set at session creation - it cannot be replaced.
+ *
+ * @hide PROPOSED_NAN_API
+ */
+public class WifiNanSessionCallback {
+    /** @hide */
+    @IntDef({
+            REASON_NO_RESOURCES, REASON_INVALID_ARGS, REASON_NO_MATCH_SESSION,
+            REASON_TX_FAIL, REASON_OTHER })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SessionReasonCodes {
+    }
+
+    /** @hide */
+    @IntDef({
+            TERMINATE_REASON_DONE, TERMINATE_REASON_FAIL })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SessionTerminateCodes {
+    }
+
+    /**
+     * Indicates no resources to execute the requested operation.
+     * Failure reason flag for {@link WifiNanSessionCallback} callbacks.
+     */
+    public static final int REASON_NO_RESOURCES = 0;
+
+    /**
+     * Indicates invalid argument in the requested operation.
+     * Failure reason flag for {@link WifiNanSessionCallback} callbacks.
+     */
+    public static final int REASON_INVALID_ARGS = 1;
+
+    /**
+     * Indicates a message is transmitted without a match (a discovery) or received message
+     * from peer occurring first.
+     * Failure reason flag for {@link WifiNanSessionCallback} callbacks.
+     */
+    public static final int REASON_NO_MATCH_SESSION = 2;
+
+    /**
+     * Indicates transmission failure: this may be due to local transmission
+     * failure or to no ACK received - remote device didn't receive the
+     * sent message. Failure reason flag for
+     * {@link WifiNanSessionCallback#onMessageSendFail(int, int)} callback.
+     */
+    public static final int REASON_TX_FAIL = 3;
+
+    /**
+     * Indicates an unspecified error occurred during the operation.
+     * Failure reason flag for {@link WifiNanSessionCallback} callbacks.
+     */
+    public static final int REASON_OTHER = 4;
+
+    /**
+     * Indicates that publish or subscribe session is done - all the
+     * requested operations (per {@link PublishConfig} or
+     * {@link SubscribeConfig}) have been executed. Failure reason flag for
+     * {@link WifiNanSessionCallback#onSessionTerminated(int)} callback.
+     */
+    public static final int TERMINATE_REASON_DONE = 100;
+
+    /**
+     * Indicates that publish or subscribe session is terminated due to a
+     * failure.
+     * Failure reason flag for
+     * {@link WifiNanSessionCallback#onSessionTerminated(int)} callback.
+     */
+    public static final int TERMINATE_REASON_FAIL = 101;
+
+    /**
+     * Called when a publish operation is started successfully in response to a
+     * {@link WifiNanManager#publish(PublishConfig, WifiNanSessionCallback)} operation.
+     *
+     * @param session The {@link WifiNanPublishSession} used to control the
+     *            discovery session.
+     */
+    public void onPublishStarted(@NonNull WifiNanPublishSession session) {
+        /* empty */
+    }
+
+    /**
+     * Called when a subscribe operation is started successfully in response to a
+     * {@link WifiNanManager#subscribe(SubscribeConfig, WifiNanSessionCallback)} operation.
+     *
+     * @param session The {@link WifiNanSubscribeSession} used to control the
+     *            discovery session.
+     */
+    public void onSubscribeStarted(@NonNull WifiNanSubscribeSession session) {
+        /* empty */
+    }
+
+    /**
+     * Called when a publish or subscribe discovery session configuration is update request
+     * succeeds. Called in response to {@link WifiNanPublishSession#updatePublish(PublishConfig)}
+     * or {@link WifiNanSubscribeSession#updateSubscribe(SubscribeConfig)}.
+     */
+    public void onSessionConfigSuccess() {
+        /* empty */
+    }
+
+    /**
+     * Called when a publish or subscribe discovery session cannot be created:
+     * {@link WifiNanManager#publish(PublishConfig, WifiNanSessionCallback)} or
+     * {@link WifiNanManager#subscribe(SubscribeConfig, WifiNanSessionCallback)},
+     * or when a configuration update fails:
+     * {@link WifiNanPublishSession#updatePublish(PublishConfig)} or
+     * {@link WifiNanSubscribeSession#updateSubscribe(SubscribeConfig)}.
+     * <p>
+     *     For discovery session updates failure leaves the session running with its previous
+     *     configuration - the discovery session is not terminated.
+     *
+     * @param reason The failure reason using
+     *            {@code WifiNanSessionCallback.REASON_*} codes.
+     */
+    public void onSessionConfigFail(@SessionReasonCodes int reason) {
+        /* empty */
+    }
+
+    /**
+     * Called when a discovery session (publish or subscribe) terminates. Termination may be due
+     * to user-request (either directly through {@link WifiNanSession#terminate()} or
+     * application-specified expiration, e.g. {@link PublishConfig.Builder#setPublishCount(int)}
+     * or {@link SubscribeConfig.Builder#setTtlSec(int)}) or due to a failure.
+     *
+     * @param reason The termination reason using
+     *            {@code WifiNanSessionCallback.TERMINATE_*} codes.
+     */
+    public void onSessionTerminated(@SessionTerminateCodes int reason) {
+        /* empty */
+    }
+
+    /**
+     * Called when a discovery (publish or subscribe) operation results in a
+     * match - when a peer is discovered.
+     *
+     * @param peerId The ID of the peer matching our discovery operation.
+     * @param serviceSpecificInfo The service specific information (arbitrary
+     *            byte array) provided by the peer as part of its discovery
+     *            configuration.
+     * @param matchFilter The filter (Tx on advertiser and Rx on listener) which
+     *            resulted in this match.
+     */
+    public void onMatch(int peerId, byte[] serviceSpecificInfo, byte[] matchFilter) {
+        /* empty */
+    }
+
+    /**
+     * Called in response to {@link WifiNanSession#sendMessage(int, byte[], int)} when a message
+     * is transmitted successfully - when it was received successfully by the peer
+     * (corresponds to an ACK being received).
+     * <p>
+     * Note that either this callback or
+     * {@link WifiNanSessionCallback#onMessageSendFail(int, int)} will be
+     * received - never both.
+     *
+     * @param messageId The arbitrary message ID specified when sending the message.
+     */
+    public void onMessageSendSuccess(@SuppressWarnings("unused") int messageId) {
+        /* empty */
+    }
+
+    /**
+     * Called when message transmission fails - when no ACK is received from the peer.
+     * Retries when ACKs are not received are done by hardware, MAC, and in the NAN stack (using
+     * the {@link WifiNanSession#sendMessage(int, byte[], int, int)} method) - this
+     * event is received after all retries are exhausted.
+     * <p>
+     * Note that either this callback or
+     * {@link WifiNanSessionCallback#onMessageSendSuccess(int)} will be received
+     * - never both.
+     *
+     * @param messageId The arbitrary message ID specified when sending the message.
+     * @param reason The failure reason using
+     *            {@code WifiNanSessionCallback.REASON_*} codes.
+     */
+    public void onMessageSendFail(@SuppressWarnings("unused") int messageId,
+            @SessionReasonCodes int reason) {
+        /* empty */
+    }
+
+    /**
+     * Called when a message is received from a discovery session peer - in response to the
+     * peer's {@link WifiNanSession#sendMessage(int, byte[], int)} or
+     * {@link WifiNanSession#sendMessage(int, byte[], int, int)}.
+     *
+     * @param peerId The ID of the peer sending the message.
+     * @param message A byte array containing the message.
+     */
+    public void onMessageReceived(int peerId, byte[] message) {
+        /* empty */
+    }
+}
diff --git a/wifi/java/android/net/wifi/nan/WifiNanSessionListener.java b/wifi/java/android/net/wifi/nan/WifiNanSessionListener.java
deleted file mode 100644
index b9af7def..0000000
--- a/wifi/java/android/net/wifi/nan/WifiNanSessionListener.java
+++ /dev/null
@@ -1,439 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.wifi.nan;
-
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.util.Log;
-
-/**
- * Base class for NAN session events callbacks. Should be extended by
- * applications wanting notifications. The callbacks are registered when a
- * publish or subscribe session is created using
- * {@link WifiNanManager#publish(PublishData, PublishSettings, WifiNanSessionListener, int)}
- * or
- * {@link WifiNanManager#subscribe(SubscribeData, SubscribeSettings, WifiNanSessionListener, int)}
- * . These are callbacks applying to a specific NAN session. Events
- * corresponding to the NAN link are delivered using {@link WifiNanEventListener}.
- * <p>
- * A single listener is registered at session creation - it cannot be replaced.
- * <p>
- * During registration specify which specific events are desired using a set of
- * {@code NanSessionListener.LISTEN_*} flags OR'd together. Only those events
- * will be delivered to the registered listener. Override those callbacks
- * {@code NanSessionListener.on*} for the registered events.
- *
- * @hide PROPOSED_NAN_API
- */
-public class WifiNanSessionListener {
-    private static final String TAG = "WifiNanSessionListener";
-    private static final boolean DBG = false;
-    private static final boolean VDBG = false; // STOPSHIP if true
-
-    /**
-     * Publish fail callback event registration flag. Corresponding callback is
-     * {@link WifiNanSessionListener#onPublishFail(int)}.
-     *
-     * @hide
-     */
-    public static final int LISTEN_PUBLISH_FAIL = 0x1 << 0;
-
-    /**
-     * Publish terminated callback event registration flag. Corresponding
-     * callback is {@link WifiNanSessionListener#onPublishTerminated(int)}.
-     */
-    public static final int LISTEN_PUBLISH_TERMINATED = 0x1 << 1;
-
-    /**
-     * Subscribe fail callback event registration flag. Corresponding callback
-     * is {@link WifiNanSessionListener#onSubscribeFail(int)}.
-     *
-     * @hide
-     */
-    public static final int LISTEN_SUBSCRIBE_FAIL = 0x1 << 2;
-
-    /**
-     * Subscribe terminated callback event registration flag. Corresponding
-     * callback is {@link WifiNanSessionListener#onSubscribeTerminated(int)}.
-     */
-    public static final int LISTEN_SUBSCRIBE_TERMINATED = 0x1 << 3;
-
-    /**
-     * Match (discovery: publish or subscribe) callback event registration flag.
-     * Corresponding callback is
-     * {@link WifiNanSessionListener#onMatch(int, byte[], int, byte[], int)}.
-     *
-     * @hide
-     */
-    public static final int LISTEN_MATCH = 0x1 << 4;
-
-    /**
-     * Message sent successfully callback event registration flag. Corresponding
-     * callback is {@link WifiNanSessionListener#onMessageSendSuccess()}.
-     *
-     * @hide
-     */
-    public static final int LISTEN_MESSAGE_SEND_SUCCESS = 0x1 << 5;
-
-    /**
-     * Message sending failure callback event registration flag. Corresponding
-     * callback is {@link WifiNanSessionListener#onMessageSendFail(int)}.
-     *
-     * @hide
-     */
-    public static final int LISTEN_MESSAGE_SEND_FAIL = 0x1 << 6;
-
-    /**
-     * Message received callback event registration flag. Corresponding callback
-     * is {@link WifiNanSessionListener#onMessageReceived(int, byte[], int)}.
-     *
-     * @hide
-     */
-    public static final int LISTEN_MESSAGE_RECEIVED = 0x1 << 7;
-
-    /**
-     * List of hidden events: which are mandatory - i.e. they will be added to
-     * every request.
-     *
-     * @hide
-     */
-    public static final int LISTEN_HIDDEN_FLAGS = LISTEN_PUBLISH_FAIL | LISTEN_SUBSCRIBE_FAIL
-            | LISTEN_MATCH | LISTEN_MESSAGE_SEND_SUCCESS | LISTEN_MESSAGE_SEND_FAIL
-            | LISTEN_MESSAGE_RECEIVED;
-
-    /**
-     * Failure reason flag for {@link WifiNanEventListener} and
-     * {@link WifiNanSessionListener} callbacks. Indicates no resources to execute
-     * the requested operation.
-     */
-    public static final int FAIL_REASON_NO_RESOURCES = 0;
-
-    /**
-     * Failure reason flag for {@link WifiNanEventListener} and
-     * {@link WifiNanSessionListener} callbacks. Indicates invalid argument in the
-     * requested operation.
-     */
-    public static final int FAIL_REASON_INVALID_ARGS = 1;
-
-    /**
-     * Failure reason flag for {@link WifiNanEventListener} and
-     * {@link WifiNanSessionListener} callbacks. Indicates a message is transmitted
-     * without a match (i.e. a discovery) occurring first.
-     */
-    public static final int FAIL_REASON_NO_MATCH_SESSION = 2;
-
-    /**
-     * Failure reason flag for {@link WifiNanEventListener} and
-     * {@link WifiNanSessionListener} callbacks. Indicates an unspecified error
-     * occurred during the operation.
-     */
-    public static final int FAIL_REASON_OTHER = 3;
-
-    /**
-     * Failure reason flag for
-     * {@link WifiNanSessionListener#onPublishTerminated(int)} and
-     * {@link WifiNanSessionListener#onSubscribeTerminated(int)} callbacks.
-     * Indicates that publish or subscribe session is done - i.e. all the
-     * requested operations (per {@link PublishSettings} or
-     * {@link SubscribeSettings}) have been executed.
-     */
-    public static final int TERMINATE_REASON_DONE = 0;
-
-    /**
-     * Failure reason flag for
-     * {@link WifiNanSessionListener#onPublishTerminated(int)} and
-     * {@link WifiNanSessionListener#onSubscribeTerminated(int)} callbacks.
-     * Indicates that publish or subscribe session is terminated due to a
-     * failure.
-     */
-    public static final int TERMINATE_REASON_FAIL = 1;
-
-    private static final String MESSAGE_BUNDLE_KEY_PEER_ID = "peer_id";
-    private static final String MESSAGE_BUNDLE_KEY_MESSAGE = "message";
-    private static final String MESSAGE_BUNDLE_KEY_MESSAGE2 = "message2";
-
-    private final Handler mHandler;
-
-    /**
-     * Constructs a {@link WifiNanSessionListener} using the looper of the current
-     * thread. I.e. all callbacks will be delivered on the current thread.
-     */
-    public WifiNanSessionListener() {
-        this(Looper.myLooper());
-    }
-
-    /**
-     * Constructs a {@link WifiNanSessionListener} using the specified looper. I.e.
-     * all callbacks will delivered on the thread of the specified looper.
-     *
-     * @param looper The looper on which to execute the callbacks.
-     */
-    public WifiNanSessionListener(Looper looper) {
-        if (VDBG) Log.v(TAG, "ctor: looper=" + looper);
-        mHandler = new Handler(looper) {
-            @Override
-            public void handleMessage(Message msg) {
-                if (DBG) Log.d(TAG, "What=" + msg.what + ", msg=" + msg);
-                switch (msg.what) {
-                    case LISTEN_PUBLISH_FAIL:
-                        WifiNanSessionListener.this.onPublishFail(msg.arg1);
-                        break;
-                    case LISTEN_PUBLISH_TERMINATED:
-                        WifiNanSessionListener.this.onPublishTerminated(msg.arg1);
-                        break;
-                    case LISTEN_SUBSCRIBE_FAIL:
-                        WifiNanSessionListener.this.onSubscribeFail(msg.arg1);
-                        break;
-                    case LISTEN_SUBSCRIBE_TERMINATED:
-                        WifiNanSessionListener.this.onSubscribeTerminated(msg.arg1);
-                        break;
-                    case LISTEN_MATCH:
-                        WifiNanSessionListener.this.onMatch(
-                                msg.getData().getInt(MESSAGE_BUNDLE_KEY_PEER_ID),
-                                msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE), msg.arg1,
-                                msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE2), msg.arg2);
-                        break;
-                    case LISTEN_MESSAGE_SEND_SUCCESS:
-                        WifiNanSessionListener.this.onMessageSendSuccess(msg.arg1);
-                        break;
-                    case LISTEN_MESSAGE_SEND_FAIL:
-                        WifiNanSessionListener.this.onMessageSendFail(msg.arg1, msg.arg2);
-                        break;
-                    case LISTEN_MESSAGE_RECEIVED:
-                        WifiNanSessionListener.this.onMessageReceived(msg.arg2,
-                                msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_MESSAGE), msg.arg1);
-                        break;
-                }
-            }
-        };
-    }
-
-    /**
-     * Called when a publish operation fails. It is dummy method (empty
-     * implementation printing out a log message). Override to implement your
-     * custom response.
-     *
-     * @param reason The failure reason using {@code NanSessionListener.FAIL_*}
-     *            codes.
-     */
-    public void onPublishFail(int reason) {
-        if (VDBG) Log.v(TAG, "onPublishFail: called in stub - override if interested");
-    }
-
-    /**
-     * Called when a publish operation terminates. Event will only be delivered
-     * if registered using {@link WifiNanSessionListener#LISTEN_PUBLISH_TERMINATED}.
-     * A dummy (empty implementation printing out a warning). Make sure to
-     * override if registered.
-     *
-     * @param reason The termination reason using
-     *            {@code NanSessionListener.TERMINATE_*} codes.
-     */
-    public void onPublishTerminated(int reason) {
-        Log.w(TAG, "onPublishTerminated: called in stub - override if interested or disable");
-    }
-
-    /**
-     * Called when a subscribe operation fails. It is dummy method (empty
-     * implementation printing out a log message). Override to implement your
-     * custom response.
-     *
-     * @param reason The failure reason using {@code NanSessionListener.FAIL_*}
-     *            codes.
-     */
-    public void onSubscribeFail(int reason) {
-        if (VDBG) Log.v(TAG, "onSubscribeFail: called in stub - override if interested");
-    }
-
-    /**
-     * Called when a subscribe operation terminates. Event will only be
-     * delivered if registered using
-     * {@link WifiNanSessionListener#LISTEN_SUBSCRIBE_TERMINATED}. A dummy (empty
-     * implementation printing out a warning). Make sure to override if
-     * registered.
-     *
-     * @param reason The termination reason using
-     *            {@code NanSessionListener.TERMINATE_*} codes.
-     */
-    public void onSubscribeTerminated(int reason) {
-        Log.w(TAG, "onSubscribeTerminated: called in stub - override if interested or disable");
-    }
-
-    /**
-     * Called when a discovery (publish or subscribe) operation results in a
-     * match - i.e. when a peer is discovered. It is dummy method (empty
-     * implementation printing out a log message). Override to implement your
-     * custom response.
-     *
-     * @param peerId The ID of the peer matching our discovery operation.
-     * @param serviceSpecificInfo The service specific information (arbitrary
-     *            byte array) provided by the peer as part of its discovery
-     *            packet.
-     * @param serviceSpecificInfoLength The length of the service specific
-     *            information array.
-     * @param matchFilter The filter (Tx on advertiser and Rx on listener) which
-     *            resulted in this match.
-     * @param matchFilterLength The length of the match filter array.
-     */
-    public void onMatch(int peerId, byte[] serviceSpecificInfo,
-            int serviceSpecificInfoLength, byte[] matchFilter, int matchFilterLength) {
-        if (VDBG) Log.v(TAG, "onMatch: called in stub - override if interested");
-    }
-
-    /**
-     * Called when a message is transmitted successfully - i.e. when we know
-     * that it was received successfully (corresponding to an ACK being
-     * received). It is dummy method (empty implementation printing out a log
-     * message). Override to implement your custom response.
-     * <p>
-     * Note that either this callback or
-     * {@link WifiNanSessionListener#onMessageSendFail(int, int)} will be
-     * received - never both.
-     */
-    public void onMessageSendSuccess(int messageId) {
-        if (VDBG) Log.v(TAG, "onMessageSendSuccess: called in stub - override if interested");
-    }
-
-    /**
-     * Called when a message transmission fails - i.e. when no ACK is received.
-     * The hardware will usually attempt to re-transmit several times - this
-     * event is received after all retries are exhausted. There is a possibility
-     * that message was received by the destination successfully but the ACK was
-     * lost. It is dummy method (empty implementation printing out a log
-     * message). Override to implement your custom response.
-     * <p>
-     * Note that either this callback or
-     * {@link WifiNanSessionListener#onMessageSendSuccess(int)} will be received
-     * - never both
-     *
-     * @param reason The failure reason using {@code NanSessionListener.FAIL_*}
-     *            codes.
-     */
-    public void onMessageSendFail(int messageId, int reason) {
-        if (VDBG) Log.v(TAG, "onMessageSendFail: called in stub - override if interested");
-    }
-
-    /**
-     * Called when a message is received from a discovery session peer. It is
-     * dummy method (empty implementation printing out a log message). Override
-     * to implement your custom response.
-     *
-     * @param peerId The ID of the peer sending the message.
-     * @param message A byte array containing the message.
-     * @param messageLength The length of the byte array containing the relevant
-     *            message bytes.
-     */
-    public void onMessageReceived(int peerId, byte[] message, int messageLength) {
-        if (VDBG) Log.v(TAG, "onMessageReceived: called in stub - override if interested");
-    }
-
-    /**
-     * {@hide}
-     */
-    public IWifiNanSessionListener callback = new IWifiNanSessionListener.Stub() {
-        @Override
-        public void onPublishFail(int reason) {
-            if (VDBG) Log.v(TAG, "onPublishFail: reason=" + reason);
-
-            Message msg = mHandler.obtainMessage(LISTEN_PUBLISH_FAIL);
-            msg.arg1 = reason;
-            mHandler.sendMessage(msg);
-        }
-
-        @Override
-        public void onPublishTerminated(int reason) {
-            if (VDBG) Log.v(TAG, "onPublishResponse: reason=" + reason);
-
-            Message msg = mHandler.obtainMessage(LISTEN_PUBLISH_TERMINATED);
-            msg.arg1 = reason;
-            mHandler.sendMessage(msg);
-        }
-
-        @Override
-        public void onSubscribeFail(int reason) {
-            if (VDBG) Log.v(TAG, "onSubscribeFail: reason=" + reason);
-
-            Message msg = mHandler.obtainMessage(LISTEN_SUBSCRIBE_FAIL);
-            msg.arg1 = reason;
-            mHandler.sendMessage(msg);
-        }
-
-        @Override
-        public void onSubscribeTerminated(int reason) {
-            if (VDBG) Log.v(TAG, "onSubscribeTerminated: reason=" + reason);
-
-            Message msg = mHandler.obtainMessage(LISTEN_SUBSCRIBE_TERMINATED);
-            msg.arg1 = reason;
-            mHandler.sendMessage(msg);
-        }
-
-        @Override
-        public void onMatch(int peerId, byte[] serviceSpecificInfo,
-                int serviceSpecificInfoLength, byte[] matchFilter, int matchFilterLength) {
-            if (VDBG) Log.v(TAG, "onMatch: peerId=" + peerId);
-
-            Bundle data = new Bundle();
-            data.putInt(MESSAGE_BUNDLE_KEY_PEER_ID, peerId);
-            data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE, serviceSpecificInfo);
-            data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE2, matchFilter);
-
-            Message msg = mHandler.obtainMessage(LISTEN_MATCH);
-            msg.arg1 = serviceSpecificInfoLength;
-            msg.arg2 = matchFilterLength;
-            msg.setData(data);
-            mHandler.sendMessage(msg);
-        }
-
-        @Override
-        public void onMessageSendSuccess(int messageId) {
-            if (VDBG) Log.v(TAG, "onMessageSendSuccess");
-
-            Message msg = mHandler.obtainMessage(LISTEN_MESSAGE_SEND_SUCCESS);
-            msg.arg1 = messageId;
-            mHandler.sendMessage(msg);
-        }
-
-        @Override
-        public void onMessageSendFail(int messageId, int reason) {
-            if (VDBG) Log.v(TAG, "onMessageSendFail: reason=" + reason);
-
-            Message msg = mHandler.obtainMessage(LISTEN_MESSAGE_SEND_FAIL);
-            msg.arg1 = messageId;
-            msg.arg2 = reason;
-            mHandler.sendMessage(msg);
-        }
-
-        @Override
-        public void onMessageReceived(int peerId, byte[] message, int messageLength) {
-            if (VDBG) {
-                Log.v(TAG, "onMessageReceived: peerId='" + peerId + "', messageLength="
-                        + messageLength);
-            }
-
-            Bundle data = new Bundle();
-            data.putByteArray(MESSAGE_BUNDLE_KEY_MESSAGE, message);
-
-            Message msg = mHandler.obtainMessage(LISTEN_MESSAGE_RECEIVED);
-            msg.arg1 = messageLength;
-            msg.arg2 = peerId;
-            msg.setData(data);
-            mHandler.sendMessage(msg);
-        }
-    };
-}
diff --git a/wifi/java/android/net/wifi/nan/WifiNanSubscribeSession.java b/wifi/java/android/net/wifi/nan/WifiNanSubscribeSession.java
index 7dfdd32..d0e56c5 100644
--- a/wifi/java/android/net/wifi/nan/WifiNanSubscribeSession.java
+++ b/wifi/java/android/net/wifi/nan/WifiNanSubscribeSession.java
@@ -16,15 +16,22 @@
 
 package android.net.wifi.nan;
 
+import android.annotation.NonNull;
+import android.util.Log;
+
 /**
- * A representation of a NAN subscribe session. Created when
- * {@link WifiNanManager#subscribe(SubscribeData, SubscribeSettings, WifiNanSessionListener, int)}
- * is executed. The object can be used to stop and re-start (re-configure) the
- * subscribe session.
+ * A class representing a NAN subscribe session. Created when
+ * {@link WifiNanManager#subscribe(SubscribeConfig, WifiNanSessionCallback)} is called and a
+ * discovery session is created and returned in
+ * {@link WifiNanSessionCallback#onSubscribeStarted(WifiNanSubscribeSession)}. See baseline
+ * functionality of all discovery sessions in {@link WifiNanSession}. This object allows updating
+ * an existing/running subscribe discovery session using {@link #updateSubscribe(SubscribeConfig)}.
  *
  * @hide PROPOSED_NAN_API
  */
 public class WifiNanSubscribeSession extends WifiNanSession {
+    private static final String TAG = "WifiNanSubscribeSession";
+
     /**
      * {@hide}
      */
@@ -33,15 +40,33 @@
     }
 
     /**
-     * Restart/re-configure the subscribe session. Note that the
-     * {@link WifiNanSessionListener} is not replaced - the same listener used at
-     * creation is still used.
+     * Re-configure the currently active subscribe session. The
+     * {@link WifiNanSessionCallback} is not replaced - the same listener used
+     * at creation is still used. The results of the configuration are returned using
+     * {@link WifiNanSessionCallback}:
+     * <ul>
+     *     <li>{@link WifiNanSessionCallback#onSessionConfigSuccess()}: configuration update
+     *     succeeded.
+     *     <li>{@link WifiNanSessionCallback#onSessionConfigFail(int)}: configuration update
+     *     failed. The subscribe discovery session is still running using its previous
+     *     configuration (i.e. update failure does not terminate the session).
+     * </ul>
      *
-     * @param subscribeData The data ({@link SubscribeData}) to subscribe.
-     * @param subscribeSettings The settings ({@link SubscribeSettings}) of the
-     *            subscribe session.
+     * @param subscribeConfig The new discovery subscribe session configuration
+     *                        ({@link SubscribeConfig}).
      */
-    public void subscribe(SubscribeData subscribeData, SubscribeSettings subscribeSettings) {
-        mManager.subscribe(mSessionId, subscribeData, subscribeSettings);
+    public void updateSubscribe(@NonNull SubscribeConfig subscribeConfig) {
+        if (mTerminated) {
+            Log.w(TAG, "updateSubscribe: called on terminated session");
+            return;
+        } else {
+            WifiNanManager mgr = mMgr.get();
+            if (mgr == null) {
+                Log.w(TAG, "updateSubscribe: called post GC on WifiNanManager");
+                return;
+            }
+
+            mgr.updateSubscribe(mSessionId, subscribeConfig);
+        }
     }
 }
diff --git a/wifi/java/android/net/wifi/nan/WifiNanUtils.java b/wifi/java/android/net/wifi/nan/WifiNanUtils.java
new file mode 100644
index 0000000..c0f36b4
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/WifiNanUtils.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.nan;
+
+/**
+ * Provides utilities for the Wifi NAN manager/service.
+ *
+ * @hide
+ */
+public class WifiNanUtils {
+    /**
+     * Per spec: The Service Name is a UTF-8 encoded string from 1 to 255 bytes in length. The
+     * only acceptable single-byte UTF-8 symbols for a Service Name are alphanumeric values (A-Z,
+     * a-z, 0-9), the hyphen ('-'), and the period ('.'). All valid multi-byte UTF-8 characters
+     * are acceptable in a Service Name.
+     */
+    public static void validateServiceName(byte[] serviceNameData) throws IllegalArgumentException {
+        if (serviceNameData == null) {
+            throw new IllegalArgumentException("Invalid service name - null");
+        }
+
+        if (serviceNameData.length < 1 || serviceNameData.length > 255) {
+            throw new IllegalArgumentException("Invalid service name length - must be between "
+                    + "1 and 255 bytes (UTF-8 encoding)");
+        }
+
+        int index = 0;
+        while (index < serviceNameData.length) {
+            byte b = serviceNameData[index];
+            if ((b & 0x80) == 0x00) {
+                if (!((b >= '0' && b <= '9') || (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z')
+                        || b == '-' || b == '.')) {
+                    throw new IllegalArgumentException("Invalid service name - illegal characters,"
+                            + " allowed = (0-9, a-z,A-Z, -, .)");
+                }
+            }
+            ++index;
+        }
+    }
+}
diff --git a/wifi/java/android/net/wifi/nan/package.html b/wifi/java/android/net/wifi/nan/package.html
new file mode 100644
index 0000000..ae3cf6c
--- /dev/null
+++ b/wifi/java/android/net/wifi/nan/package.html
@@ -0,0 +1,43 @@
+<HTML>
+<BODY>
+<p>Provides classes which allow applications to use Wi-Fi NAN to discover peers and create
+    connections to them.</p>
+<p>Using the Wi-Fi NAN APIs, applications can advertise services, discover peers which are
+    advertising services, and connect to them.
+    Wi-Fi NAN is independent of Wi-Fi infrastructure (i.e. a device may or may
+    not be associated with an AP concurrent to using Wi-Fi NAN). </p>
+<p>The primary entry point to Wi-Fi NAN capabilities is the
+    {@link android.net.wifi.nan.WifiNanManager} class, which is acquired by calling
+    {@link android.content.Context#getSystemService(String)
+    Context.getSystemService(Context.WIFI_NAN_SERVICE)}</p>
+
+<p>Some APIs may require the following user permissions:</p>
+<ul>
+    <li>{@link android.Manifest.permission#ACCESS_WIFI_STATE}</li>
+    <li>{@link android.Manifest.permission#CHANGE_WIFI_STATE}</li>
+    <li>{@link android.Manifest.permission#ACCESS_COARSE_LOCATION}</li>
+</ul>
+
+<p class="note"><strong>Note:</strong> Not all Android-powered devices support Wi-Fi NAN
+    functionality.
+    If your application only works with Wi-Fi NAN (i.e. it should only be installed on devices which
+    support Wi-Fi NAN), declare so with a <a
+            href="{@docRoot}guide/topics/manifest/uses-feature-element.html">
+        {@code &lt;uses-feature&gt;}</a>
+    element in the manifest file:</p>
+<pre>
+&lt;manifest ...>
+    &lt;uses-feature android:name="android.hardware.wifi.nan" />
+    ...
+&lt;/manifest>
+</pre>
+<p>Alternatively, if you application does not require Wi-Fi NAN but can take advantage of it if
+    available, you can perform
+    the check at run-time in your code using {@link
+    android.content.pm.PackageManager#hasSystemFeature(String)} with {@link
+    android.content.pm.PackageManager#FEATURE_WIFI_NAN}:</p>
+<pre>
+    getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_NAN)
+</pre>
+</BODY>
+</HTML>
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
index a0cb035..a68bcd9 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
@@ -66,19 +66,28 @@
 
     /* Device Capability bitmap */
     private static final int DEVICE_CAPAB_SERVICE_DISCOVERY         = 1;
+    @SuppressWarnings("unused")
     private static final int DEVICE_CAPAB_CLIENT_DISCOVERABILITY    = 1<<1;
+    @SuppressWarnings("unused")
     private static final int DEVICE_CAPAB_CONCURRENT_OPER           = 1<<2;
+    @SuppressWarnings("unused")
     private static final int DEVICE_CAPAB_INFRA_MANAGED             = 1<<3;
+    @SuppressWarnings("unused")
     private static final int DEVICE_CAPAB_DEVICE_LIMIT              = 1<<4;
     private static final int DEVICE_CAPAB_INVITATION_PROCEDURE      = 1<<5;
 
     /* Group Capability bitmap */
     private static final int GROUP_CAPAB_GROUP_OWNER                = 1;
+    @SuppressWarnings("unused")
     private static final int GROUP_CAPAB_PERSISTENT_GROUP           = 1<<1;
     private static final int GROUP_CAPAB_GROUP_LIMIT                = 1<<2;
+    @SuppressWarnings("unused")
     private static final int GROUP_CAPAB_INTRA_BSS_DIST             = 1<<3;
+    @SuppressWarnings("unused")
     private static final int GROUP_CAPAB_CROSS_CONN                 = 1<<4;
+    @SuppressWarnings("unused")
     private static final int GROUP_CAPAB_PERSISTENT_RECONN          = 1<<5;
+    @SuppressWarnings("unused")
     private static final int GROUP_CAPAB_GROUP_FORMATION            = 1<<6;
 
     /**
@@ -305,6 +314,7 @@
         return other.deviceAddress.equals(deviceAddress);
     }
 
+    @Override
     public String toString() {
         StringBuffer sbuf = new StringBuffer();
         sbuf.append("Device: ").append(deviceName);
@@ -320,6 +330,7 @@
     }
 
     /** Implement the Parcelable interface */
+    @Override
     public int describeContents() {
         return 0;
     }
@@ -340,6 +351,7 @@
     }
 
     /** Implement the Parcelable interface */
+    @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(deviceName);
         dest.writeString(deviceAddress);
@@ -360,6 +372,7 @@
     /** Implement the Parcelable interface */
     public static final Creator<WifiP2pDevice> CREATOR =
         new Creator<WifiP2pDevice>() {
+            @Override
             public WifiP2pDevice createFromParcel(Parcel in) {
                 WifiP2pDevice device = new WifiP2pDevice();
                 device.deviceName = in.readString();
@@ -376,6 +389,7 @@
                 return device;
             }
 
+            @Override
             public WifiP2pDevice[] newArray(int size) {
                 return new WifiP2pDevice[size];
             }
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
index ca737f9..32673c7 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
@@ -146,7 +146,6 @@
                 }
 
                 if (nameValue[0].equals("persistent")) {
-                    mOwner = new WifiP2pDevice(sa);
                     mNetId = Integer.parseInt(nameValue[1]);
                     continue;
                 }