Merge changes from topic 'wifi-aosp-master' into stage-aosp-master

* changes:
  NetworkSelectionStatus: Fix copy constructor
  Deprecate QNS network disable reason BAD_LINK
  [NAN] API changes/fixes addressing apilint issues
  [NAN] Add method to ConfigRequest to determine if non-default
  [NAN] Simplify publish/subscribe configuration - merge match filters
  [NAN] Udpate NAN API documentation, clean-up visibility
  [NAN] Do not expose WifiManager.isNanSupported as new API
  [NAN] Update finalize to check and warn user if resources not freed
  [NAN] Fix bad merge use rethrowFromSystemServer
  Add Log.wtf when transitionTo is used improperly
  Ensure BSSIDs are correctly aged out in settings view
  [NAN] Provide calling package name to service
  Add WiFi toggle prompts - framework
  Reenable the unit tests for StateMachine
  Remove WiFi related methods from NetworkManagementService
  Don't pull in external/junit through mockito
  WifiConfiguration: Expose NetworkSelectionStatus strings
  Add Bluetooth toggle prompts - framework
  [NAN] Add LV utilities (TLV with T size of 0).
  [NAN] Fix TlvElement member names - reflect public access.
  TetherUtil: Use ConnectivityManager for tethering
  [NAN] TLV utilities iterator - throw correct exception
  [NAN] Modify arguments from "array, length" to "array"
  [NAN] NAN System Api: RTT
  Properly disable WiFi tethering
  [NAN] Provide inteface MAC address to app.
  [NAN] Add (T)LV validation testing
  Add IPv6 tethering coordinator
  Stop listening for WiFi interface status changes
  [NAN] Add validity checks for service name
  [NAN] Fix assumption about Strings containing single-byte characters.
  [NAN] NAN data-path API: network specifier
  DO NOT MERGE StateMachine: remove final from SendMessage
  wifi: allow config of default+verbose ring buffers
  Don't save the anonymous identity for EAP-SIM/AKA/AKA-Prime
  Add a WifiScanner API to listener for ongoing scans
  Fix invitation request from persistent GC
  SettingsBackupAgent: Remove redundant size parameter
  WifiConfiguration: Make NetworkSelectionStatus public
  Use WifiManager for wifi backup/restore
  Fix race conditions between Tethering and TetherInterfaceStateMachine
  WifiManager: Add new API to restore old backup data
  Tethering: Own WiFi tethering state and lifetime
  IpConfigStore: Changes for parsing old wifi backup
  WifiManager: Add new API's for backup/restore
  XmlUtils: Add missing readThisByteArrayXml method
  Use an ArrayMap instead of HashMap to track tether interfaces
  Consolidate cleanup logic in TetherInterfaceSM.TetheredState
  Annotate TetherInterfaceStateMachineTest for APCT
  Rely on Tethering mutex for TetherInterfaceSM
  Rename TetherInterfaceSM to TetherInterfaceStateMachine
  [NAN] Add retry count for transmitting L2 NAN messages
  Remove dead code from TetherInterfaceSM
  Remove transient StartingState from TetherInterfaceSM
  Expand test coverage of TetherInterfaceSM
  Add demonstration unittest for TetherInterfaceSM
  Extract TetherInterfaceSM to its own class.
  Make Tethering.TetherInterfaceSM more self contained
  WifiManager: add WIFI_MODE_NO_LOCKS_HELD
  Create frameworks-base-testutils lib
  Clean up class members in Tethering.TetherInterfaceSM
  Move android.net.util.IpUtilsTest into a util/ directory
  Fix trivial warnings in Tethering.java
  Fix some trivial warnings in WifiP2pDevice.java
  [NAN] Add support for NAN RTT.
  [NAN] Remove OnNanDown notification from API
  Remove unused method from NetworkManagementService
  WifiConfiguration: Add new selection disable reasons
  [NAN] Add type and nullability annotations. Update hide annotation.
  [NAN] Add master switch to enable/disable usage of NAN APIs.
  [NAN] Re-factor connect/config flow
  [NAN] Refactor session lifecycle to clarify API & eliminate race conditions
  [NAN] Removed NAN event registration - dispatch all callbacks.
  [NAN] Consolidate publish/subscribe callback sets into single set
  [NAN] Replace session callback configuration control
  [NAN] Expose match style configuration as public API.
  [NAN] API cleanup - rename FLAGS.
  [NAN] Renamed listeners to callback per API guidelines
  [NAN]: API update - consolidate Puslish|Subscribe Data+Subscribe -> Config
  NAN: Support multiple instantiation per UID - introduce a client ID.
diff --git a/Android.mk b/Android.mk
index 27cf90c..69e57b6 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 \
@@ -536,10 +536,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;
                 }