Merge "Modify the wake reasons data structure(2/2)" into mm-wireless-dev
diff --git a/api/system-current.txt b/api/system-current.txt
index a03f328..1de5a9d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -20815,6 +20815,7 @@
     field public int numberBurst;
     field public int preamble;
     field public int requestType;
+    field public boolean secure;
   }
 
   public static class RttManager.RttResult {
@@ -20846,6 +20847,7 @@
     field public deprecated long rtt_sd_ns;
     field public deprecated long rtt_spread_ns;
     field public int rxRate;
+    field public boolean secure;
     field public int status;
     field public int successMeasurementFrameNumber;
     field public long ts;
@@ -21209,6 +21211,7 @@
     method public void stopTrackingWifiChange(android.net.wifi.WifiScanner.WifiChangeListener);
     field public static final int MAX_SCAN_PERIOD_MS = 1024000; // 0xfa000
     field public static final int MIN_SCAN_PERIOD_MS = 1000; // 0x3e8
+    field public static final int REASON_DUPLICATE_REQEUST = -5; // 0xfffffffb
     field public static final int REASON_INVALID_LISTENER = -2; // 0xfffffffe
     field public static final int REASON_INVALID_REQUEST = -3; // 0xfffffffd
     field public static final int REASON_NOT_AUTHORIZED = -4; // 0xfffffffc
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 4487cab..c6d919f 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -57,90 +57,6 @@
     public native static int resetConnections(String interfaceName, int mask);
 
     /**
-     * Start the DHCP client daemon, in order to have it request addresses
-     * for the named interface.  This returns {@code true} if the DHCPv4 daemon
-     * starts, {@code false} otherwise.  This call blocks until such time as a
-     * result is available or the default discovery timeout has been reached.
-     * Callers should check {@link #getDhcpResults} to determine whether DHCP
-     * succeeded or failed, and if it succeeded, to fetch the {@link DhcpResults}.
-     * @param interfaceName the name of the interface to configure
-     * @return {@code true} for success, {@code false} for failure
-     */
-    public native static boolean startDhcp(String interfaceName);
-
-    /**
-     * Initiate renewal on the DHCP client daemon for the named interface.  This
-     * returns {@code true} if the DHCPv4 daemon has been notified, {@code false}
-     * otherwise.  This call blocks until such time as a result is available or
-     * the default renew timeout has been reached.  Callers should check
-     * {@link #getDhcpResults} to determine whether DHCP succeeded or failed,
-     * and if it succeeded, to fetch the {@link DhcpResults}.
-     * @param interfaceName the name of the interface to configure
-     * @return {@code true} for success, {@code false} for failure
-     */
-    public native static boolean startDhcpRenew(String interfaceName);
-
-    /**
-     * Start the DHCP client daemon, in order to have it request addresses
-     * for the named interface, and then configure the interface with those
-     * addresses. This call blocks until it obtains a result (either success
-     * or failure) from the daemon.
-     * @param interfaceName the name of the interface to configure
-     * @param dhcpResults if the request succeeds, this object is filled in with
-     * the IP address information.
-     * @return {@code true} for success, {@code false} for failure
-     */
-    public static boolean runDhcp(String interfaceName, DhcpResults dhcpResults) {
-        return startDhcp(interfaceName) && getDhcpResults(interfaceName, dhcpResults);
-    }
-
-    /**
-     * Initiate renewal on the DHCP client daemon. This call blocks until it obtains
-     * a result (either success or failure) from the daemon.
-     * @param interfaceName the name of the interface to configure
-     * @param dhcpResults if the request succeeds, this object is filled in with
-     * the IP address information.
-     * @return {@code true} for success, {@code false} for failure
-     */
-    public static boolean runDhcpRenew(String interfaceName, DhcpResults dhcpResults) {
-        return startDhcpRenew(interfaceName) && getDhcpResults(interfaceName, dhcpResults);
-    }
-
-    /**
-     * Fetch results from the DHCP client daemon. This call returns {@code true} if
-     * if there are results available to be read, {@code false} otherwise.
-     * @param interfaceName the name of the interface to configure
-     * @param dhcpResults if the request succeeds, this object is filled in with
-     * the IP address information.
-     * @return {@code true} for success, {@code false} for failure
-     */
-    public native static boolean getDhcpResults(String interfaceName, DhcpResults dhcpResults);
-
-    /**
-     * Shut down the DHCP client daemon.
-     * @param interfaceName the name of the interface for which the daemon
-     * should be stopped
-     * @return {@code true} for success, {@code false} for failure
-     */
-    public native static boolean stopDhcp(String interfaceName);
-
-    /**
-     * Release the current DHCP lease.
-     * @param interfaceName the name of the interface for which the lease should
-     * be released
-     * @return {@code true} for success, {@code false} for failure
-     */
-    public native static boolean releaseDhcpLease(String interfaceName);
-
-    /**
-     * Return the last DHCP-related error message that was recorded.
-     * <p/>NOTE: This string is not localized, but currently it is only
-     * used in logging.
-     * @return the most recent error message, if any
-     */
-    public native static String getDhcpError();
-
-    /**
      * Attaches a socket filter that accepts DHCP packets to the given socket.
      */
     public native static void attachDhcpFilter(FileDescriptor fd) throws SocketException;
diff --git a/core/java/com/android/internal/util/MessageUtils.java b/core/java/com/android/internal/util/MessageUtils.java
new file mode 100644
index 0000000..184245e
--- /dev/null
+++ b/core/java/com/android/internal/util/MessageUtils.java
@@ -0,0 +1,132 @@
+/*
+ * 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;
+
+import android.os.Message;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+/**
+ * Static utility class for dealing with {@link Message} objects.
+ */
+public class MessageUtils {
+
+    private static final String TAG = MessageUtils.class.getSimpleName();
+    private static final boolean DBG = false;
+
+    /** Thrown when two different constants have the same value. */
+    public static class DuplicateConstantError extends Error {
+        private DuplicateConstantError() {}
+        public DuplicateConstantError(String name1, String name2, int value) {
+            super(String.format("Duplicate constant value: both %s and %s = %d",
+                name1, name2, value));
+        }
+    }
+
+    /**
+     * Finds the names of integer constants. Searches the specified {@code classes}, looking for
+     * accessible static integer fields whose names begin with one of the specified {@prefixes}.
+     *
+     * @param classes the classes to examine.
+     * @prefixes only consider fields names starting with one of these prefixes.
+     * @return a {@link SparseArray} mapping integer constants to their names.
+     */
+    public static SparseArray<String> findMessageNames(Class[] classes, String[] prefixes) {
+        SparseArray<String> messageNames = new SparseArray<>();
+        for (Class c : classes) {
+            String className = c.getName();
+            if (DBG) Log.d(TAG, "Examining class " + className);
+
+            Field[] fields;
+            try {
+                fields = c.getDeclaredFields();
+            } catch (SecurityException e) {
+                Log.e(TAG, "Can't list fields of class " + className);
+                continue;
+            }
+
+            for (Field field : fields) {
+                int modifiers = field.getModifiers();
+                if (!Modifier.isStatic(modifiers) | !Modifier.isFinal(modifiers)) {
+                    continue;
+                }
+
+                String name = field.getName();
+                for (String prefix : prefixes) {
+                    // Does this look like a constant?
+                    if (!name.startsWith(prefix)) {
+                        continue;
+                    }
+
+                    try {
+                        // TODO: can we have the caller try to access the field instead, so we don't
+                        // expose constants it does not have access to?
+                        field.setAccessible(true);
+
+                        // Fetch the constant's value.
+                        int value;
+                        try {
+                            value = field.getInt(null);
+                        } catch (IllegalArgumentException | ExceptionInInitializerError e) {
+                            // The field is not an integer (or short or byte), or c's static
+                            // initializer failed and we have no idea what its value is.
+                            // Either way, give up on this field.
+                            break;
+                        }
+
+                        // Check for duplicate values.
+                        String previousName = messageNames.get(value);
+                        if (previousName != null && !previousName.equals(name)) {
+                            throw new DuplicateConstantError(name, previousName, value);
+                        }
+
+                        messageNames.put(value, name);
+                        if (DBG) {
+                            Log.d(TAG, String.format("Found constant: %s.%s = %d",
+                                    className, name, value));
+                        }
+                    } catch (SecurityException | IllegalAccessException e) {
+                        // Not allowed to make the field accessible, or no access. Ignore.
+                        continue;
+                    }
+                }
+            }
+        }
+        return messageNames;
+    }
+
+    /**
+     * Default prefixes for constants.
+     */
+    public static final String[] DEFAULT_PREFIXES = {"CMD_", "EVENT_"};
+
+    /**
+     * Finds the names of integer constants. Searches the specified {@code classes}, looking for
+     * accessible static integer values whose names begin with {@link #DEFAULT_PREFIXES}.
+     *
+     * @param classNames the classes to examine.
+     * @prefixes only consider fields names starting with one of these prefixes.
+     * @return a {@link SparseArray} mapping integer constants to their names.
+     */
+    public static SparseArray<String> findMessageNames(Class[] classNames) {
+        return findMessageNames(classNames, DEFAULT_PREFIXES);
+    }
+}
+
diff --git a/core/java/com/android/internal/util/WakeupMessage.java b/core/java/com/android/internal/util/WakeupMessage.java
new file mode 100644
index 0000000..77859b8
--- /dev/null
+++ b/core/java/com/android/internal/util/WakeupMessage.java
@@ -0,0 +1,79 @@
+/*
+ * 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 com.android.internal.util;
+
+import android.app.AlarmManager;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
+
+ /**
+ * An AlarmListener that sends the specified message to a Handler and keeps the system awake until
+ * the message is processed.
+ *
+ * This is useful when using the AlarmManager direct callback interface to wake up the system and
+ * request that an object whose API consists of messages (such as a StateMachine) perform some
+ * action.
+ *
+ * In this situation, using AlarmManager.onAlarmListener by itself will wake up the system to send
+ * the message, but does not guarantee that the system will be awake until the target object has
+ * processed it. This is because as soon as the onAlarmListener sends the message and returns, the
+ * AlarmManager releases its wakelock and the system is free to go to sleep again.
+ *
+ */
+public class WakeupMessage implements AlarmManager.OnAlarmListener {
+    private static AlarmManager sAlarmManager;
+    private final Handler mHandler;
+    private final String mCmdName;
+    private final int mCmd, mArg1, mArg2;
+
+    public WakeupMessage(Context context, Handler handler,
+            String cmdName, int cmd, int arg1, int arg2) {
+        if (sAlarmManager == null) {
+            sAlarmManager = context.getSystemService(AlarmManager.class);
+        }
+        mHandler = handler;
+        mCmdName = cmdName;
+        mCmd = cmd;
+        mArg1 = arg1;
+        mArg2 = arg2;
+    }
+
+    public WakeupMessage(Context context, Handler handler, String cmdName, int cmd, int arg1) {
+        this(context, handler, cmdName, cmd, arg1, 0);
+    }
+
+    public WakeupMessage(Context context, Handler handler, String cmdName, int cmd) {
+        this(context, handler, cmdName, cmd, 0, 0);
+    }
+
+    public void schedule(long when) {
+        sAlarmManager.setExact(
+                AlarmManager.ELAPSED_REALTIME_WAKEUP, when, mCmdName, this, mHandler);
+    }
+
+    public void cancel() {
+        sAlarmManager.cancel(this);
+    }
+
+    @Override
+    public void onAlarm() {
+        Message msg = mHandler.obtainMessage(mCmd, mArg1, mArg2);
+        mHandler.handleMessage(msg);
+        msg.recycle();
+    }
+}
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index ba0876d..defb88a 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -39,23 +39,6 @@
 int ifc_enable(const char *ifname);
 int ifc_disable(const char *ifname);
 int ifc_reset_connections(const char *ifname, int reset_mask);
-
-int dhcp_start(const char * const ifname);
-int dhcp_start_renew(const char * const ifname);
-int dhcp_get_results(const char * const ifname,
-                     const char *ipaddr,
-                     const char *gateway,
-                     uint32_t *prefixLength,
-                     const char *dns[],
-                     const char *server,
-                     uint32_t *lease,
-                     const char *vendorInfo,
-                     const char *domains,
-                     const char *mtu);
-
-int dhcp_stop(const char *ifname);
-int dhcp_release_lease(const char *ifname);
-char *dhcp_get_errmsg();
 }
 
 #define NETUTILS_PKG_NAME "android/net/NetworkUtils"
@@ -64,22 +47,6 @@
 
 static const uint16_t kDhcpClientPort = 68;
 
-/*
- * The following remembers the jfieldID's of the fields
- * of the DhcpInfo Java object, so that we don't have
- * to look them up every time.
- */
-static struct fieldIds {
-    jmethodID clear;
-    jmethodID setIpAddress;
-    jmethodID setGateway;
-    jmethodID addDns;
-    jmethodID setDomains;
-    jmethodID setServerAddress;
-    jmethodID setLeaseDuration;
-    jmethodID setVendorInfo;
-} dhcpResultsFieldIds;
-
 static jint android_net_utils_resetConnections(JNIEnv* env, jobject clazz,
       jstring ifname, jint mask)
 {
@@ -95,137 +62,6 @@
     return (jint)result;
 }
 
-static jboolean android_net_utils_getDhcpResults(JNIEnv* env, jobject clazz, jstring ifname,
-        jobject dhcpResults)
-{
-    int result;
-    char  ipaddr[PROPERTY_VALUE_MAX];
-    uint32_t prefixLength;
-    char gateway[PROPERTY_VALUE_MAX];
-    char    dns1[PROPERTY_VALUE_MAX];
-    char    dns2[PROPERTY_VALUE_MAX];
-    char    dns3[PROPERTY_VALUE_MAX];
-    char    dns4[PROPERTY_VALUE_MAX];
-    const char *dns[5] = {dns1, dns2, dns3, dns4, NULL};
-    char  server[PROPERTY_VALUE_MAX];
-    uint32_t lease;
-    char vendorInfo[PROPERTY_VALUE_MAX];
-    char domains[PROPERTY_VALUE_MAX];
-    char mtu[PROPERTY_VALUE_MAX];
-
-    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
-    if (nameStr == NULL) return (jboolean)false;
-
-    result = ::dhcp_get_results(nameStr, ipaddr, gateway, &prefixLength,
-            dns, server, &lease, vendorInfo, domains, mtu);
-    if (result != 0) {
-        ALOGD("dhcp_get_results failed : %s (%s)", nameStr, ::dhcp_get_errmsg());
-    }
-
-    env->ReleaseStringUTFChars(ifname, nameStr);
-    if (result == 0) {
-        env->CallVoidMethod(dhcpResults, dhcpResultsFieldIds.clear);
-
-        // set the linkAddress
-        // dhcpResults->addLinkAddress(inetAddress, prefixLength)
-        result = env->CallBooleanMethod(dhcpResults, dhcpResultsFieldIds.setIpAddress,
-                env->NewStringUTF(ipaddr), prefixLength);
-    }
-
-    if (result == 0) {
-        // set the gateway
-        result = env->CallBooleanMethod(dhcpResults,
-                dhcpResultsFieldIds.setGateway, env->NewStringUTF(gateway));
-    }
-
-    if (result == 0) {
-        // dhcpResults->addDns(new InetAddress(dns1))
-        result = env->CallBooleanMethod(dhcpResults,
-                dhcpResultsFieldIds.addDns, env->NewStringUTF(dns1));
-    }
-
-    if (result == 0) {
-        env->CallVoidMethod(dhcpResults, dhcpResultsFieldIds.setDomains,
-                env->NewStringUTF(domains));
-
-        result = env->CallBooleanMethod(dhcpResults,
-                dhcpResultsFieldIds.addDns, env->NewStringUTF(dns2));
-
-        if (result == 0) {
-            result = env->CallBooleanMethod(dhcpResults,
-                    dhcpResultsFieldIds.addDns, env->NewStringUTF(dns3));
-            if (result == 0) {
-                result = env->CallBooleanMethod(dhcpResults,
-                        dhcpResultsFieldIds.addDns, env->NewStringUTF(dns4));
-            }
-        }
-    }
-
-    if (result == 0) {
-        // dhcpResults->setServerAddress(new InetAddress(server))
-        result = env->CallBooleanMethod(dhcpResults, dhcpResultsFieldIds.setServerAddress,
-                env->NewStringUTF(server));
-    }
-
-    if (result == 0) {
-        // dhcpResults->setLeaseDuration(lease)
-        env->CallVoidMethod(dhcpResults,
-                dhcpResultsFieldIds.setLeaseDuration, lease);
-
-        // dhcpResults->setVendorInfo(vendorInfo)
-        env->CallVoidMethod(dhcpResults, dhcpResultsFieldIds.setVendorInfo,
-                env->NewStringUTF(vendorInfo));
-    }
-    return (jboolean)(result == 0);
-}
-
-static jboolean android_net_utils_startDhcp(JNIEnv* env, jobject clazz, jstring ifname)
-{
-    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
-    if (nameStr == NULL) return (jboolean)false;
-    if (::dhcp_start(nameStr) != 0) {
-        ALOGD("dhcp_start failed : %s", nameStr);
-        return (jboolean)false;
-    }
-    return (jboolean)true;
-}
-
-static jboolean android_net_utils_startDhcpRenew(JNIEnv* env, jobject clazz, jstring ifname)
-{
-    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
-    if (nameStr == NULL) return (jboolean)false;
-    if (::dhcp_start_renew(nameStr) != 0) {
-        ALOGD("dhcp_start_renew failed : %s", nameStr);
-        return (jboolean)false;
-    }
-    return (jboolean)true;
-}
-
-static jboolean android_net_utils_stopDhcp(JNIEnv* env, jobject clazz, jstring ifname)
-{
-    int result;
-
-    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
-    result = ::dhcp_stop(nameStr);
-    env->ReleaseStringUTFChars(ifname, nameStr);
-    return (jboolean)(result == 0);
-}
-
-static jboolean android_net_utils_releaseDhcpLease(JNIEnv* env, jobject clazz, jstring ifname)
-{
-    int result;
-
-    const char *nameStr = env->GetStringUTFChars(ifname, NULL);
-    result = ::dhcp_release_lease(nameStr);
-    env->ReleaseStringUTFChars(ifname, nameStr);
-    return (jboolean)(result == 0);
-}
-
-static jstring android_net_utils_getDhcpError(JNIEnv* env, jobject clazz)
-{
-    return env->NewStringUTF(::dhcp_get_errmsg());
-}
-
 static void android_net_utils_attachDhcpFilter(JNIEnv *env, jobject clazz, jobject javaFd)
 {
     int fd = jniGetFDFromFileDescriptor(env, javaFd);
@@ -305,12 +141,6 @@
 static const JNINativeMethod gNetworkUtilMethods[] = {
     /* name, signature, funcPtr */
     { "resetConnections", "(Ljava/lang/String;I)I",  (void *)android_net_utils_resetConnections },
-    { "startDhcp", "(Ljava/lang/String;)Z",  (void *)android_net_utils_startDhcp },
-    { "startDhcpRenew", "(Ljava/lang/String;)Z",  (void *)android_net_utils_startDhcpRenew },
-    { "getDhcpResults", "(Ljava/lang/String;Landroid/net/DhcpResults;)Z",  (void *)android_net_utils_getDhcpResults },
-    { "stopDhcp", "(Ljava/lang/String;)Z",  (void *)android_net_utils_stopDhcp },
-    { "releaseDhcpLease", "(Ljava/lang/String;)Z",  (void *)android_net_utils_releaseDhcpLease },
-    { "getDhcpError", "()Ljava/lang/String;", (void*) android_net_utils_getDhcpError },
     { "bindProcessToNetwork", "(I)Z", (void*) android_net_utils_bindProcessToNetwork },
     { "getBoundNetworkForProcess", "()I", (void*) android_net_utils_getBoundNetworkForProcess },
     { "bindProcessToNetworkForHostResolution", "(I)Z", (void*) android_net_utils_bindProcessToNetworkForHostResolution },
@@ -322,24 +152,6 @@
 
 int register_android_net_NetworkUtils(JNIEnv* env)
 {
-    jclass dhcpResultsClass = FindClassOrDie(env, "android/net/DhcpResults");
-
-    dhcpResultsFieldIds.clear = GetMethodIDOrDie(env, dhcpResultsClass, "clear", "()V");
-    dhcpResultsFieldIds.setIpAddress =GetMethodIDOrDie(env, dhcpResultsClass, "setIpAddress",
-            "(Ljava/lang/String;I)Z");
-    dhcpResultsFieldIds.setGateway = GetMethodIDOrDie(env, dhcpResultsClass, "setGateway",
-            "(Ljava/lang/String;)Z");
-    dhcpResultsFieldIds.addDns = GetMethodIDOrDie(env, dhcpResultsClass, "addDns",
-            "(Ljava/lang/String;)Z");
-    dhcpResultsFieldIds.setDomains = GetMethodIDOrDie(env, dhcpResultsClass, "setDomains",
-            "(Ljava/lang/String;)V");
-    dhcpResultsFieldIds.setServerAddress = GetMethodIDOrDie(env, dhcpResultsClass,
-            "setServerAddress", "(Ljava/lang/String;)Z");
-    dhcpResultsFieldIds.setLeaseDuration = GetMethodIDOrDie(env, dhcpResultsClass,
-            "setLeaseDuration", "(I)V");
-    dhcpResultsFieldIds.setVendorInfo = GetMethodIDOrDie(env, dhcpResultsClass, "setVendorInfo",
-            "(Ljava/lang/String;)V");
-
     return RegisterMethodsOrDie(env, NETUTILS_PKG_NAME, gNetworkUtilMethods,
                                 NELEM(gNetworkUtilMethods));
 }
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index b081568..8a77f82 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -610,6 +610,10 @@
         // Grant CAP_WAKE_ALARM and CAP_BLOCK_SUSPEND to the Bluetooth process.
         capabilities |= (1LL << CAP_WAKE_ALARM);
         capabilities |= (1LL << CAP_BLOCK_SUSPEND);
+        // Allow bluetooth to open packet sockets so it can start the DHCP client.
+        // TODO: consider making such functionality an RPC to netd.
+        capabilities |= (1LL << CAP_NET_RAW);
+        capabilities |= (1LL << CAP_NET_BIND_SERVICE);
 
         // Add the Bluetooth process to the system group.
         jsize length = env->GetArrayLength(reinterpret_cast<jarray>(gids));
diff --git a/core/tests/coretests/src/com/android/internal/util/AsyncChannelTest.java b/core/tests/coretests/src/com/android/internal/util/AsyncChannelTest.java
deleted file mode 100644
index 7088650..0000000
--- a/core/tests/coretests/src/com/android/internal/util/AsyncChannelTest.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- * Copyright (C) 2010 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;
-
-import android.os.Debug;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
-
-import junit.framework.TestCase;
-
-/**
- * Test for AsyncChannel.
- */
-public class AsyncChannelTest extends TestCase {
-    private static final boolean DBG = true;
-    private static final boolean WAIT_FOR_DEBUGGER = false;
-    private static final String TAG = "AsyncChannelTest";
-
-    @SmallTest
-    public void test1() throws Exception {
-        if (DBG) log("test1");
-        if (WAIT_FOR_DEBUGGER) Debug.waitForDebugger();
-        assertTrue(1 == 1);
-    }
-
-    protected void log(String s) {
-        Log.d(TAG, s);
-    }
-}
diff --git a/core/tests/utiltests/Android.mk b/core/tests/utiltests/Android.mk
new file mode 100644
index 0000000..f949e1a
--- /dev/null
+++ b/core/tests/utiltests/Android.mk
@@ -0,0 +1,23 @@
+#########################################################################
+# Build FrameworksUtilTests package
+#########################################################################
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# We only want this apk build for tests.
+LOCAL_MODULE_TAGS := tests
+
+# Include all test java files.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_PACKAGE_NAME := FrameworksUtilTests
+
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
diff --git a/core/tests/utiltests/AndroidManifest.xml b/core/tests/utiltests/AndroidManifest.xml
new file mode 100644
index 0000000..fecaf8e
--- /dev/null
+++ b/core/tests/utiltests/AndroidManifest.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.frameworks.utiltests">
+
+    <uses-permission android:name="android.permission.READ_LOGS" />
+    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.BROADCAST_STICKY" />
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
+    <uses-permission android:name="android.permission.MANAGE_APP_TOKENS" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+
+    <uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
+    <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
+    <uses-permission android:name="android.permission.MODIFY_NETWORK_ACCOUNTING" />
+    <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+    <uses-permission android:name="android.permission.MANAGE_USERS" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <uses-permission android:name="android.permission.MANAGE_DEVICE_ADMINS" />
+    <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.frameworks.utiltests"
+        android:label="Frameworks Utility Tests" />
+
+</manifest>
diff --git a/core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
similarity index 100%
rename from core/tests/coretests/src/com/android/internal/util/ArrayUtilsTest.java
rename to core/tests/utiltests/src/com/android/internal/util/ArrayUtilsTest.java
diff --git a/core/tests/coretests/src/com/android/internal/util/BitwiseStreamsTest.java b/core/tests/utiltests/src/com/android/internal/util/BitwiseStreamsTest.java
similarity index 100%
rename from core/tests/coretests/src/com/android/internal/util/BitwiseStreamsTest.java
rename to core/tests/utiltests/src/com/android/internal/util/BitwiseStreamsTest.java
diff --git a/core/tests/coretests/src/com/android/internal/util/CallbackRegistryTest.java b/core/tests/utiltests/src/com/android/internal/util/CallbackRegistryTest.java
similarity index 100%
rename from core/tests/coretests/src/com/android/internal/util/CallbackRegistryTest.java
rename to core/tests/utiltests/src/com/android/internal/util/CallbackRegistryTest.java
diff --git a/core/tests/coretests/src/com/android/internal/util/CharSequencesTest.java b/core/tests/utiltests/src/com/android/internal/util/CharSequencesTest.java
similarity index 100%
rename from core/tests/coretests/src/com/android/internal/util/CharSequencesTest.java
rename to core/tests/utiltests/src/com/android/internal/util/CharSequencesTest.java
diff --git a/core/tests/coretests/src/com/android/internal/util/FastXmlSerializerTest.java b/core/tests/utiltests/src/com/android/internal/util/FastXmlSerializerTest.java
similarity index 94%
rename from core/tests/coretests/src/com/android/internal/util/FastXmlSerializerTest.java
rename to core/tests/utiltests/src/com/android/internal/util/FastXmlSerializerTest.java
index be7116d..5f36c2d 100644
--- a/core/tests/coretests/src/com/android/internal/util/FastXmlSerializerTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/FastXmlSerializerTest.java
@@ -42,6 +42,6 @@
         out.endDocument();
 
         assertEquals("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
-                + "<string name=\"meow\"></string>", stream.toString());
+                + "<string name=\"meow\"></string>\n", stream.toString());
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/util/FileRotatorTest.java b/core/tests/utiltests/src/com/android/internal/util/FileRotatorTest.java
similarity index 100%
rename from core/tests/coretests/src/com/android/internal/util/FileRotatorTest.java
rename to core/tests/utiltests/src/com/android/internal/util/FileRotatorTest.java
diff --git a/core/tests/coretests/src/com/android/internal/util/IndentingPrintWriterTest.java b/core/tests/utiltests/src/com/android/internal/util/IndentingPrintWriterTest.java
similarity index 100%
rename from core/tests/coretests/src/com/android/internal/util/IndentingPrintWriterTest.java
rename to core/tests/utiltests/src/com/android/internal/util/IndentingPrintWriterTest.java
diff --git a/core/tests/utiltests/src/com/android/internal/util/MessageUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/MessageUtilsTest.java
new file mode 100644
index 0000000..32b969a
--- /dev/null
+++ b/core/tests/utiltests/src/com/android/internal/util/MessageUtilsTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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;
+
+import static org.junit.Assert.*;
+
+import com.android.internal.util.MessageUtils;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.SparseArray;
+
+import org.junit.Test;
+
+
+class A {
+    // Should not see these.
+    private int mMember;
+    public final int CMD_NOT_STATIC = 7;
+    private static final String CMD_NOT_INT = "not an integer";
+    public static int CMD_NOT_FINAL = 34;
+    public static final int kWrongPrefix = 99;
+
+    // Should see these.
+    private static final int CMD_DO_SOMETHING = 12;
+    public static final int EVENT_SOMETHING_HAPPENED = 45;
+}
+
+class B {
+    public static final int CMD_FOO = 56;
+    public static final int EVENT_BAR = 55;
+    public static final int NOTIFICATION_BAZ = 12;
+}
+
+/**
+ * Unit tests for {@link com.android.util.MessageUtils}.
+ */
+@SmallTest
+public class MessageUtilsTest {
+
+    private static final Class[] CLASSES = { A.class, B.class };
+
+    private SparseArray<String> makeSparseArray(int[] keys, String[] values) throws Exception {
+        assertEquals("Must specify same number of keys and values", keys.length, values.length);
+        SparseArray<String> out = new SparseArray<>();
+        for (int i = 0; i < keys.length; i++) {
+            out.put(keys[i], values[i]);
+        }
+        return out;
+    }
+
+    private void assertSparseArrayEquals(
+            SparseArray<String> a1, SparseArray<String> a2) throws Exception {
+        String msg = String.format("%s != %s", a1.toString(), a2.toString());
+        assertEquals(msg, a1.size(), a2.size());
+        int size = a1.size();
+        for (int i = 0; i < size; i++) {
+            assertEquals(msg, a1.keyAt(i), a2.keyAt(i));
+            assertEquals(msg, a1.valueAt(i), a2.valueAt(i));
+        }
+    }
+
+    @Test
+    public void basicOperation() throws Exception {
+        SparseArray<String> expected = makeSparseArray(
+            new int[]{12, 45, 55, 56},
+            new String[]{"CMD_DO_SOMETHING", "EVENT_SOMETHING_HAPPENED", "EVENT_BAR", "CMD_FOO"});
+        assertSparseArrayEquals(expected, MessageUtils.findMessageNames(CLASSES));
+    }
+
+    @Test
+    public void withPrefixes() throws Exception {
+        SparseArray<String> expected = makeSparseArray(
+            new int[]{45, 55},
+            new String[]{"EVENT_SOMETHING_HAPPENED", "EVENT_BAR"});
+        assertSparseArrayEquals(expected, MessageUtils.findMessageNames(CLASSES,
+                new String[]{"EVENT_"}));
+    }
+
+    @Test(expected=MessageUtils.DuplicateConstantError.class)
+    public void duplicateConstants() {
+        MessageUtils.findMessageNames(CLASSES, new String[]{"CMD_", "NOTIFICATION_"});
+    }
+
+}
diff --git a/core/tests/coretests/src/com/android/internal/util/PredicatesTest.java b/core/tests/utiltests/src/com/android/internal/util/PredicatesTest.java
similarity index 100%
rename from core/tests/coretests/src/com/android/internal/util/PredicatesTest.java
rename to core/tests/utiltests/src/com/android/internal/util/PredicatesTest.java
diff --git a/core/tests/coretests/src/com/android/internal/util/ProcFileReaderTest.java b/core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java
similarity index 100%
rename from core/tests/coretests/src/com/android/internal/util/ProcFileReaderTest.java
rename to core/tests/utiltests/src/com/android/internal/util/ProcFileReaderTest.java
diff --git a/core/tests/coretests/src/com/android/internal/util/StateMachineTest.java b/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java
similarity index 99%
rename from core/tests/coretests/src/com/android/internal/util/StateMachineTest.java
rename to core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java
index 2a2c24e..302aa87 100644
--- a/core/tests/coretests/src/com/android/internal/util/StateMachineTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java
@@ -25,6 +25,7 @@
 import android.os.Message;
 import android.os.SystemClock;
 
+import android.test.suitebuilder.annotation.Suppress;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.internal.util.StateMachine.LogRec;
@@ -38,6 +39,7 @@
 /**
  * Test for StateMachine.
  */
+@Suppress // Failing
 public class StateMachineTest extends TestCase {
     private static final String ENTER = "enter";
     private static final String EXIT = "exit";
diff --git a/core/tests/coretests/src/com/android/internal/util/XmlUtilsTest.java b/core/tests/utiltests/src/com/android/internal/util/XmlUtilsTest.java
similarity index 100%
rename from core/tests/coretests/src/com/android/internal/util/XmlUtilsTest.java
rename to core/tests/utiltests/src/com/android/internal/util/XmlUtilsTest.java
diff --git a/services/core/java/com/android/server/net/IpConfigStore.java b/services/core/java/com/android/server/net/IpConfigStore.java
index 90e26d8..9f1435a 100644
--- a/services/core/java/com/android/server/net/IpConfigStore.java
+++ b/services/core/java/com/android/server/net/IpConfigStore.java
@@ -59,8 +59,12 @@
 
     protected static final int IPCONFIG_FILE_VERSION = 2;
 
+    public IpConfigStore(DelayedDiskWrite writer) {
+        mWriter = writer;
+    }
+
     public IpConfigStore() {
-        mWriter = new DelayedDiskWrite();
+        this(new DelayedDiskWrite());
     }
 
     private boolean writeConfig(DataOutputStream out, int configKey,
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
index 5c7a78a..7fc6c5b 100644
--- a/services/net/java/android/net/dhcp/DhcpClient.java
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -19,6 +19,7 @@
 import com.android.internal.util.HexDump;
 import com.android.internal.util.Protocol;
 import com.android.internal.util.State;
+import com.android.internal.util.MessageUtils;
 import com.android.internal.util.StateMachine;
 
 import android.app.AlarmManager;
@@ -34,7 +35,6 @@
 import android.os.IBinder;
 import android.os.INetworkManagementService;
 import android.os.Message;
-import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
@@ -42,16 +42,15 @@
 import android.system.Os;
 import android.system.PacketSocketAddress;
 import android.util.Log;
+import android.util.SparseArray;
 import android.util.TimeUtils;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.lang.Thread;
 import java.net.Inet4Address;
-import java.net.InetSocketAddress;
 import java.net.NetworkInterface;
 import java.net.SocketException;
-import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.Random;
@@ -133,6 +132,11 @@
     private static final int CMD_TIMEOUT          = PRIVATE_BASE + 3;
     private static final int CMD_ONESHOT_TIMEOUT  = PRIVATE_BASE + 4;
 
+    // For message logging.
+    private static final Class[] sMessageClasses = { DhcpClient.class };
+    private static final SparseArray<String> sMessageNames =
+            MessageUtils.findMessageNames(sMessageClasses);
+
     // DHCP parameters that we request.
     private static final byte[] REQUESTED_PARAMS = new byte[] {
         DHCP_SUBNET_MASK,
@@ -497,30 +501,7 @@
         }
 
         private String messageName(int what) {
-            switch (what) {
-                case CMD_START_DHCP:
-                    return "CMD_START_DHCP";
-                case CMD_STOP_DHCP:
-                    return "CMD_STOP_DHCP";
-                case CMD_RENEW_DHCP:
-                    return "CMD_RENEW_DHCP";
-                case CMD_PRE_DHCP_ACTION:
-                    return "CMD_PRE_DHCP_ACTION";
-                case CMD_PRE_DHCP_ACTION_COMPLETE:
-                    return "CMD_PRE_DHCP_ACTION_COMPLETE";
-                case CMD_POST_DHCP_ACTION:
-                    return "CMD_POST_DHCP_ACTION";
-                case CMD_KICK:
-                    return "CMD_KICK";
-                case CMD_RECEIVED_PACKET:
-                    return "CMD_RECEIVED_PACKET";
-                case CMD_TIMEOUT:
-                    return "CMD_TIMEOUT";
-                case CMD_ONESHOT_TIMEOUT:
-                    return "CMD_ONESHOT_TIMEOUT";
-                default:
-                    return Integer.toString(what);
-            }
+            return sMessageNames.get(what, Integer.toString(what));
         }
 
         private String messageToString(Message message) {
diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java
index d0d87b1..2a90c60 100644
--- a/services/net/java/android/net/ip/IpManager.java
+++ b/services/net/java/android/net/ip/IpManager.java
@@ -16,12 +16,15 @@
 
 package android.net.ip;
 
+import com.android.internal.util.MessageUtils;
+
 import android.content.Context;
 import android.net.DhcpResults;
 import android.net.InterfaceConfiguration;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.LinkProperties.ProvisioningChange;
+import android.net.ProxyInfo;
 import android.net.RouteInfo;
 import android.net.StaticIpConfiguration;
 import android.net.dhcp.DhcpClient;
@@ -29,7 +32,9 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.text.TextUtils;
 import android.util.Log;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -61,6 +66,14 @@
     private static final boolean DBG = true;
     private static final boolean VDBG = false;
 
+    private static final boolean NO_CALLBACKS = false;
+    private static final boolean SEND_CALLBACKS = true;
+
+    // For message logging.
+    private static final Class[] sMessageClasses = { IpManager.class, DhcpClient.class };
+    private static final SparseArray<String> sWhatToString =
+            MessageUtils.findMessageNames(sMessageClasses);
+
     /**
      * Callbacks for handling IpManager events.
      */
@@ -96,6 +109,35 @@
         public void onQuit() {}
     }
 
+    public static class WaitForProvisioningCallback extends Callback {
+        private LinkProperties mCallbackLinkProperties;
+
+        public LinkProperties waitForProvisioning() {
+            synchronized (this) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {}
+                return mCallbackLinkProperties;
+            }
+        }
+
+        @Override
+        public void onProvisioningSuccess(LinkProperties newLp) {
+            synchronized (this) {
+                mCallbackLinkProperties = newLp;
+                notify();
+            }
+        }
+
+        @Override
+        public void onProvisioningFailure(LinkProperties newLp) {
+            synchronized (this) {
+                mCallbackLinkProperties = null;
+                notify();
+            }
+        }
+    }
+
     /**
      * This class encapsulates parameters to be passed to
      * IpManager#startProvisioning(). A defensive copy is made by IpManager
@@ -160,6 +202,8 @@
     private static final int EVENT_PRE_DHCP_ACTION_COMPLETE = 4;
     // Sent by NetlinkTracker to communicate netlink events.
     private static final int EVENT_NETLINK_LINKPROPERTIES_CHANGED = 5;
+    private static final int CMD_UPDATE_TCP_BUFFER_SIZES = 6;
+    private static final int CMD_UPDATE_HTTP_PROXY = 7;
 
     private static final int MAX_LOG_RECORDS = 1000;
 
@@ -181,10 +225,12 @@
     /**
      * Non-final member variables accessed only from within our StateMachine.
      */
+    private ProvisioningConfiguration mConfiguration;
     private IpReachabilityMonitor mIpReachabilityMonitor;
     private DhcpClient mDhcpClient;
     private DhcpResults mDhcpResults;
-    private ProvisioningConfiguration mConfiguration;
+    private String mTcpBufferSizes;
+    private ProxyInfo mHttpProxy;
 
     /**
      * Member variables accessed both from within the StateMachine thread
@@ -293,6 +339,26 @@
         sendMessage(EVENT_PRE_DHCP_ACTION_COMPLETE);
     }
 
+    /**
+     * Set the TCP buffer sizes to use.
+     *
+     * This may be called, repeatedly, at any time before or after a call to
+     * #startProvisioning(). The setting is cleared upon calling #stop().
+     */
+    public void setTcpBufferSizes(String tcpBufferSizes) {
+        sendMessage(CMD_UPDATE_TCP_BUFFER_SIZES, tcpBufferSizes);
+    }
+
+    /**
+     * Set the HTTP Proxy configuration to use.
+     *
+     * This may be called, repeatedly, at any time before or after a call to
+     * #startProvisioning(). The setting is cleared upon calling #stop().
+     */
+    public void setHttpProxy(ProxyInfo proxyInfo) {
+        sendMessage(CMD_UPDATE_HTTP_PROXY, proxyInfo);
+    }
+
     public LinkProperties getLinkProperties() {
         synchronized (mLock) {
             return new LinkProperties(mLinkProperties);
@@ -306,26 +372,7 @@
 
     @Override
     protected String getWhatToString(int what) {
-        // TODO: Investigate switching to reflection.
-        switch (what) {
-            case CMD_STOP:
-                return "CMD_STOP";
-            case CMD_START:
-                return "CMD_START";
-            case CMD_CONFIRM:
-                return "CMD_CONFIRM";
-            case EVENT_PRE_DHCP_ACTION_COMPLETE:
-                return "EVENT_PRE_DHCP_ACTION_COMPLETE";
-            case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
-                return "EVENT_NETLINK_LINKPROPERTIES_CHANGED";
-            case DhcpClient.CMD_PRE_DHCP_ACTION:
-                return "DhcpClient.CMD_PRE_DHCP_ACTION";
-            case DhcpClient.CMD_POST_DHCP_ACTION:
-                return "DhcpClient.CMD_POST_DHCP_ACTION";
-            case DhcpClient.CMD_ON_QUIT:
-                return "DhcpClient.CMD_ON_QUIT";
-        }
-        return "UNKNOWN:" + Integer.toString(what);
+        return sWhatToString.get(what, "UNKNOWN: " + Integer.toString(what));
     }
 
     @Override
@@ -355,8 +402,10 @@
     // assigned to the interface, etc.
     private void resetLinkProperties() {
         mNetlinkTracker.clearLinkProperties();
-        mDhcpResults = null;
         mConfiguration = null;
+        mDhcpResults = null;
+        mTcpBufferSizes = "";
+        mHttpProxy = null;
 
         synchronized (mLock) {
             mLinkProperties = new LinkProperties();
@@ -513,13 +562,33 @@
             newLp.setDomains(mDhcpResults.domains);
         }
 
+        // [4] Add in TCP buffer sizes and HTTP Proxy config, if available.
+        if (!TextUtils.isEmpty(mTcpBufferSizes)) {
+            newLp.setTcpBufferSizes(mTcpBufferSizes);
+        }
+        if (mHttpProxy != null) {
+            newLp.setHttpProxy(mHttpProxy);
+        }
+
         if (VDBG) {
             Log.d(mTag, "newLp{" + newLp + "}");
         }
-
         return newLp;
     }
 
+    // Returns false if we have lost provisioning, true otherwise.
+    private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) {
+        final LinkProperties newLp = assembleLinkProperties();
+        if (linkPropertiesUnchanged(newLp)) {
+            return true;
+        }
+        final ProvisioningChange delta = setLinkProperties(newLp);
+        if (sendCallbacks) {
+            dispatchCallback(delta, newLp);
+        }
+        return (delta != ProvisioningChange.LOST_PROVISIONING);
+    }
+
     private void clearIPv4Address() {
         try {
             final InterfaceConfiguration ifcg = new InterfaceConfiguration();
@@ -598,7 +667,17 @@
                     break;
 
                 case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
-                    setLinkProperties(assembleLinkProperties());
+                    handleLinkPropertiesUpdate(NO_CALLBACKS);
+                    break;
+
+                case CMD_UPDATE_TCP_BUFFER_SIZES:
+                    mTcpBufferSizes = (String) msg.obj;
+                    handleLinkPropertiesUpdate(NO_CALLBACKS);
+                    break;
+
+                case CMD_UPDATE_HTTP_PROXY:
+                    mHttpProxy = (ProxyInfo) msg.obj;
+                    handleLinkPropertiesUpdate(NO_CALLBACKS);
                     break;
 
                 case DhcpClient.CMD_ON_QUIT:
@@ -729,18 +808,23 @@
                     }
                     break;
 
-                case EVENT_NETLINK_LINKPROPERTIES_CHANGED: {
-                    final LinkProperties newLp = assembleLinkProperties();
-                    if (linkPropertiesUnchanged(newLp)) {
-                        break;
-                    }
-                    final ProvisioningChange delta = setLinkProperties(newLp);
-                    dispatchCallback(delta, newLp);
-                    if (delta == ProvisioningChange.LOST_PROVISIONING) {
+                case EVENT_NETLINK_LINKPROPERTIES_CHANGED:
+                    if (!handleLinkPropertiesUpdate(SEND_CALLBACKS)) {
                         transitionTo(mStoppedState);
                     }
                     break;
-                }
+
+                case CMD_UPDATE_TCP_BUFFER_SIZES:
+                    mTcpBufferSizes = (String) msg.obj;
+                    // This cannot possibly change provisioning state.
+                    handleLinkPropertiesUpdate(SEND_CALLBACKS);
+                    break;
+
+                case CMD_UPDATE_HTTP_PROXY:
+                    mHttpProxy = (ProxyInfo) msg.obj;
+                    // This cannot possibly change provisioning state.
+                    handleLinkPropertiesUpdate(SEND_CALLBACKS);
+                    break;
 
                 case DhcpClient.CMD_PRE_DHCP_ACTION:
                     if (VDBG) { Log.d(mTag, "onPreDhcpAction()"); }
diff --git a/wifi/java/android/net/wifi/RttManager.java b/wifi/java/android/net/wifi/RttManager.java
index 9137d9d..249f64f 100644
--- a/wifi/java/android/net/wifi/RttManager.java
+++ b/wifi/java/android/net/wifi/RttManager.java
@@ -324,6 +324,11 @@
         public int requestType;
 
         /**
+         * Whether the secure RTT protocol needs to be used for ranging this peer device.
+         */
+        public boolean secure;
+
+        /**
          * mac address of the device being ranged
          * Default value: null
          */
@@ -478,6 +483,7 @@
                 for (RttParams params : mParams) {
                     dest.writeInt(params.deviceType);
                     dest.writeInt(params.requestType);
+                    dest.writeByte(params.secure ? (byte) 1 : 0);
                     dest.writeString(params.bssid);
                     dest.writeInt(params.channelWidth);
                     dest.writeInt(params.frequency);
@@ -515,6 +521,7 @@
                             params[i] = new RttParams();
                             params[i].deviceType = in.readInt();
                             params[i].requestType = in.readInt();
+                            params[i].secure = (in.readByte() != 0);
                             params[i].bssid = in.readString();
                             params[i].channelWidth = in.readInt();
                             params[i].frequency = in.readInt();
@@ -690,6 +697,11 @@
 
         /** LCR information Element, only available to double side RTT. */
         public WifiInformationElement LCR;
+
+        /**
+         * Whether the secure RTT protocol was used for ranging.
+         */
+        public boolean secure;
     }
 
 
@@ -742,6 +754,7 @@
                         dest.writeInt((byte) result.LCR.data.length);
                         dest.writeByte(result.LCR.id);
                     }
+                    dest.writeByte(result.secure ? (byte) 1 : 0);
                 }
             } else {
                 dest.writeInt(0);
@@ -796,6 +809,7 @@
                                 results[i].LCR.data = new byte[length];
                                 in.readByteArray(results[i].LCR.data);
                             }
+                            results[i].secure = (in.readByte() != 0);
                         }
 
                         ParcelableRttResults parcelableResults = new ParcelableRttResults(results);
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index 31da670..ed12bdf 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -331,6 +331,7 @@
      */
     public static class InformationElement {
         public static final int EID_SSID = 0;
+        public static final int EID_TIM = 5;
         public static final int EID_BSS_LOAD = 11;
         public static final int EID_RSN = 48;
         public static final int EID_HT_OPERATION = 61;
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index a6aad3c..472d1ef 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -402,6 +402,15 @@
 
     /**
      * @hide
+     * The number of beacon intervals between Delivery Traffic Indication Maps (DTIM)
+     * This value is populated from scan results that contain Beacon Frames, which are infrequent.
+     * The value is not guaranteed to be set or current (Although it SHOULDNT change once set)
+     * Valid values are from 1 - 255. Initialized here as 0, use this to check if set.
+     */
+    public int dtimInterval = 0;
+
+    /**
+     * @hide
      * Uid of app creating the configuration
      */
     @SystemApi
@@ -1297,6 +1306,7 @@
         lastUpdateUid = -1;
         creatorUid = -1;
         shared = true;
+        dtimInterval = 0;
     }
 
     /**
@@ -2043,4 +2053,4 @@
         config.allowedKeyManagement.set(in.readInt());
         return config;
     }
-}
\ No newline at end of file
+}
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index a406fd7..964bfa2 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -20,6 +20,7 @@
 import android.os.Parcelable;
 import android.security.Credentials;
 import android.text.TextUtils;
+import android.util.Log;
 
 import java.io.ByteArrayInputStream;
 import java.nio.charset.StandardCharsets;
@@ -122,6 +123,22 @@
     /** {@hide} */
     public static final String DISABLE_TLS_1_2 = "\"tls_disable_tlsv1_2=1\"";
 
+    // Fields to copy verbatim from wpa_supplicant.
+    private static final String[] SUPPLICANT_CONFIG_KEYS = new String[] {
+            IDENTITY_KEY,
+            ANON_IDENTITY_KEY,
+            PASSWORD_KEY,
+            CLIENT_CERT_KEY,
+            CA_CERT_KEY,
+            SUBJECT_MATCH_KEY,
+            ENGINE_KEY,
+            ENGINE_ID_KEY,
+            PRIVATE_KEY_ID_KEY,
+            ALTSUBJECT_MATCH_KEY,
+            DOM_SUFFIX_MATCH_KEY,
+            CA_PATH_KEY
+    };
+
     private HashMap<String, String> mFields = new HashMap<String, String>();
     //By default, we enable TLS1.2. However, due to a known bug on some radius, we may disable it to
     // fall back to TLS 1.1.
@@ -129,6 +146,10 @@
     private X509Certificate[] mCaCerts;
     private PrivateKey mClientPrivateKey;
     private X509Certificate mClientCertificate;
+    private int mEapMethod = Eap.NONE;
+    private int mPhase2Method = Phase2.NONE;
+
+    private static final String TAG = "WifiEnterpriseConfig";
 
     public WifiEnterpriseConfig() {
         // Do not set defaults so that the enterprise fields that are not changed
@@ -143,6 +164,8 @@
         for (String key : source.mFields.keySet()) {
             mFields.put(key, source.mFields.get(key));
         }
+        mEapMethod = source.mEapMethod;
+        mPhase2Method = source.mPhase2Method;
     }
 
     @Override
@@ -158,6 +181,8 @@
             dest.writeString(entry.getValue());
         }
 
+        dest.writeInt(mEapMethod);
+        dest.writeInt(mPhase2Method);
         writeCertificates(dest, mCaCerts);
 
         if (mClientPrivateKey != null) {
@@ -210,6 +235,8 @@
                         enterpriseConfig.mFields.put(key, value);
                     }
 
+                    enterpriseConfig.mEapMethod = in.readInt();
+                    enterpriseConfig.mPhase2Method = in.readInt();
                     enterpriseConfig.mCaCerts = readCertificates(in);
 
                     PrivateKey userKey = null;
@@ -307,7 +334,8 @@
         public static final int MSCHAPV2    = 3;
         /** Generic Token Card */
         public static final int GTC         = 4;
-        private static final String PREFIX = "auth=";
+        private static final String AUTH_PREFIX = "auth=";
+        private static final String AUTHEAP_PREFIX = "autheap=";
         /** @hide */
         public static final String[] strings = {EMPTY_VALUE, "PAP", "MSCHAP",
                 "MSCHAPV2", "GTC" };
@@ -316,11 +344,98 @@
         private Phase2() {}
     }
 
-    /** Internal use only
+    // Loader and saver interfaces for exchanging data with wpa_supplicant.
+    // TODO: Decouple this object (which is just a placeholder of the configuration)
+    // from the implementation that knows what wpa_supplicant wants.
+    /**
+     * Interface used for retrieving supplicant configuration from WifiEnterpriseConfig
      * @hide
      */
-    public HashMap<String, String> getFields() {
-        return mFields;
+    public interface SupplicantSaver {
+        /**
+         * Set a value within wpa_supplicant configuration
+         * @param key index to set within wpa_supplciant
+         * @param value the value for the key
+         * @return true if successful; false otherwise
+         */
+        boolean saveValue(String key, String value);
+    }
+
+    /**
+     * Interface used for populating a WifiEnterpriseConfig from supplicant configuration
+     * @hide
+     */
+    public interface SupplicantLoader {
+        /**
+         * Returns a value within wpa_supplicant configuration
+         * @param key index to set within wpa_supplciant
+         * @return string value if successful; null otherwise
+         */
+        String loadValue(String key);
+    }
+
+    /**
+     * Internal use only; supply field values to wpa_supplicant config.  The configuration
+     * process aborts on the first failed call on {@code saver}.
+     * @param saver proxy for setting configuration in wpa_supplciant
+     * @return whether the save succeeded on all attempts
+     * @hide
+     */
+    public boolean saveToSupplicant(SupplicantSaver saver) {
+        if (!isEapMethodValid()) {
+            return false;
+        }
+
+        for (String key : mFields.keySet()) {
+            if (!saver.saveValue(key, mFields.get(key))) {
+                return false;
+            }
+        }
+
+        if (!saver.saveValue(EAP_KEY, Eap.strings[mEapMethod])) {
+            return false;
+        }
+
+        if (mEapMethod != Eap.TLS && mPhase2Method != Phase2.NONE) {
+            boolean is_autheap = mEapMethod == Eap.TTLS && mPhase2Method == Phase2.GTC;
+            String prefix = is_autheap ? Phase2.AUTHEAP_PREFIX : Phase2.AUTH_PREFIX;
+            String value = convertToQuotedString(prefix + Phase2.strings[mPhase2Method]);
+            return saver.saveValue(PHASE2_KEY, value);
+        } else if (mPhase2Method == Phase2.NONE) {
+            // By default, send a null phase 2 to clear old configuration values.
+            return saver.saveValue(PHASE2_KEY, null);
+        } else {
+            Log.e(TAG, "WiFi enterprise configuration is invalid as it supplies a "
+                    + "phase 2 method but the phase1 method does not support it.");
+            return false;
+        }
+    }
+
+    /**
+     * Internal use only; retrieve configuration from wpa_supplicant config.
+     * @param loader proxy for retrieving configuration keys from wpa_supplicant
+     * @hide
+     */
+    public void loadFromSupplicant(SupplicantLoader loader) {
+        for (String key : SUPPLICANT_CONFIG_KEYS) {
+            String value = loader.loadValue(key);
+            if (value == null) {
+                mFields.put(key, EMPTY_VALUE);
+            } else {
+                mFields.put(key, value);
+            }
+        }
+        String eapMethod  = loader.loadValue(EAP_KEY);
+        mEapMethod = getStringIndex(Eap.strings, eapMethod, Eap.NONE);
+
+        String phase2Method = removeDoubleQuotes(loader.loadValue(PHASE2_KEY));
+        // Remove "auth=" or "autheap=" prefix.
+        if (phase2Method.startsWith(Phase2.AUTH_PREFIX)) {
+            phase2Method = phase2Method.substring(Phase2.AUTH_PREFIX.length());
+        } else if (phase2Method.startsWith(Phase2.AUTHEAP_PREFIX)) {
+            phase2Method = phase2Method.substring(Phase2.AUTHEAP_PREFIX.length());
+        }
+        mPhase2Method = getStringIndex(Phase2.strings, phase2Method, Phase2.NONE);
     }
 
     /**
@@ -341,7 +456,7 @@
             case Eap.SIM:
             case Eap.AKA:
             case Eap.AKA_PRIME:
-                mFields.put(EAP_KEY, Eap.strings[eapMethod]);
+                mEapMethod = eapMethod;
                 mFields.put(OPP_KEY_CACHING, "1");
                 break;
             default:
@@ -374,8 +489,7 @@
      * @return eap method configured
      */
     public int getEapMethod() {
-        String eapMethod  = mFields.get(EAP_KEY);
-        return getStringIndex(Eap.strings, eapMethod, Eap.NONE);
+        return mEapMethod;
     }
 
     /**
@@ -390,15 +504,11 @@
     public void setPhase2Method(int phase2Method) {
         switch (phase2Method) {
             case Phase2.NONE:
-                mFields.put(PHASE2_KEY, EMPTY_VALUE);
-                break;
-            /** Valid methods */
             case Phase2.PAP:
             case Phase2.MSCHAP:
             case Phase2.MSCHAPV2:
             case Phase2.GTC:
-                mFields.put(PHASE2_KEY, convertToQuotedString(
-                        Phase2.PREFIX + Phase2.strings[phase2Method]));
+                mPhase2Method = phase2Method;
                 break;
             default:
                 throw new IllegalArgumentException("Unknown Phase 2 method");
@@ -410,12 +520,7 @@
      * @return a phase 2 method defined at {@link Phase2}
      * */
     public int getPhase2Method() {
-        String phase2Method = removeDoubleQuotes(mFields.get(PHASE2_KEY));
-        // Remove auth= prefix
-        if (phase2Method.startsWith(Phase2.PREFIX)) {
-            phase2Method = phase2Method.substring(Phase2.PREFIX.length());
-        }
-        return getStringIndex(Phase2.strings, phase2Method, Phase2.NONE);
+        return mPhase2Method;
     }
 
     /**
@@ -443,7 +548,8 @@
         setFieldValue(ANON_IDENTITY_KEY, anonymousIdentity, "");
     }
 
-    /** Get the anonymous identity
+    /**
+     * Get the anonymous identity
      * @return anonymous identity
      */
     public String getAnonymousIdentity() {
@@ -870,18 +976,15 @@
     }
 
     /** See {@link WifiConfiguration#getKeyIdForCredentials} @hide */
-    String getKeyId(WifiEnterpriseConfig current) {
-        String eap = mFields.get(EAP_KEY);
-        String phase2 = mFields.get(PHASE2_KEY);
-
-        // If either eap or phase2 are not initialized, use current config details
-        if (TextUtils.isEmpty((eap))) {
-            eap = current.mFields.get(EAP_KEY);
+    public String getKeyId(WifiEnterpriseConfig current) {
+        // If EAP method is not initialized, use current config details
+        if (mEapMethod == Eap.NONE) {
+            return (current != null) ? current.getKeyId(null) : EMPTY_VALUE;
         }
-        if (TextUtils.isEmpty(phase2)) {
-            phase2 = current.mFields.get(PHASE2_KEY);
+        if (!isEapMethodValid()) {
+            return EMPTY_VALUE;
         }
-        return eap + "_" + phase2;
+        return Eap.strings[mEapMethod] + "_" + Phase2.strings[mPhase2Method];
     }
 
     private String removeDoubleQuotes(String string) {
@@ -898,7 +1001,8 @@
         return "\"" + string + "\"";
     }
 
-    /** Returns the index at which the toBeFound string is found in the array.
+    /**
+     * Returns the index at which the toBeFound string is found in the array.
      * @param arr array of strings
      * @param toBeFound string to be found
      * @param defaultIndex default index to be returned when string is not found
@@ -912,13 +1016,16 @@
         return defaultIndex;
     }
 
-    /** Returns the field value for the key.
+    /**
+     * Returns the field value for the key.
      * @param key into the hash
      * @param prefix is the prefix that the value may have
      * @return value
      * @hide
      */
     public String getFieldValue(String key, String prefix) {
+        // TODO: Should raise an exception if |key| is EAP_KEY or PHASE2_KEY since
+        // neither of these keys should be retrieved in this manner.
         String value = mFields.get(key);
         // Uninitialized or known to be empty after reading from supplicant
         if (TextUtils.isEmpty(value) || EMPTY_VALUE.equals(value)) return "";
@@ -931,13 +1038,16 @@
         }
     }
 
-    /** Set a value with an optional prefix at key
+    /**
+     * Set a value with an optional prefix at key
      * @param key into the hash
      * @param value to be set
      * @param prefix an optional value to be prefixed to actual value
      * @hide
      */
     public void setFieldValue(String key, String value, String prefix) {
+        // TODO: Should raise an exception if |key| is EAP_KEY or PHASE2_KEY since
+        // neither of these keys should be set in this manner.
         if (TextUtils.isEmpty(value)) {
             mFields.put(key, EMPTY_VALUE);
         } else {
@@ -946,13 +1056,16 @@
     }
 
 
-    /** Set a value with an optional prefix at key
+    /**
+     * Set a value with an optional prefix at key
      * @param key into the hash
      * @param value to be set
      * @param prefix an optional value to be prefixed to actual value
      * @hide
      */
     public void setFieldValue(String key, String value) {
+        // TODO: Should raise an exception if |key| is EAP_KEY or PHASE2_KEY since
+        // neither of these keys should be set in this manner.
         if (TextUtils.isEmpty(value)) {
            mFields.put(key, EMPTY_VALUE);
         } else {
@@ -968,4 +1081,25 @@
         }
         return sb.toString();
     }
+
+    /**
+     * Returns whether the EAP method data is valid, i.e., whether mEapMethod and mPhase2Method
+     * are valid indices into {@code Eap.strings[]} and {@code Phase2.strings[]} respectively.
+     */
+    private boolean isEapMethodValid() {
+        if (mEapMethod == Eap.NONE) {
+            Log.e(TAG, "WiFi enterprise configuration is invalid as it supplies no EAP method.");
+            return false;
+        }
+        if (mEapMethod < 0 || mEapMethod >= Eap.strings.length) {
+            Log.e(TAG, "mEapMethod is invald for WiFi enterprise configuration: " + mEapMethod);
+            return false;
+        }
+        if (mPhase2Method < 0 || mPhase2Method >= Phase2.strings.length) {
+            Log.e(TAG, "mPhase2Method is invald for WiFi enterprise configuration: "
+                    + mPhase2Method);
+            return false;
+        }
+        return true;
+    }
 }
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 2373754..69e179d 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -30,7 +30,9 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.AsyncChannel;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.Protocol;
 
 import java.util.List;
@@ -78,6 +80,8 @@
     public static final int REASON_INVALID_REQUEST = -3;
     /** Invalid request */
     public static final int REASON_NOT_AUTHORIZED = -4;
+    /** An outstanding request with the same listener hasn't finished yet. */
+    public static final int REASON_DUPLICATE_REQEUST = -5;
 
     /** @hide */
     public static final String GET_AVAILABLE_CHANNELS_EXTRA = "Channels";
@@ -460,8 +464,11 @@
      *                 scans should also not share this object.
      */
     public void startBackgroundScan(ScanSettings settings, ScanListener listener) {
+        Preconditions.checkNotNull(listener, "listener cannot be null");
+        int key = addListener(listener);
+        if (key == INVALID_KEY) return;
         validateChannel();
-        sAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, putListener(listener), settings);
+        sAsyncChannel.sendMessage(CMD_START_BACKGROUND_SCAN, 0, key, settings);
     }
     /**
      * stop an ongoing wifi scan
@@ -469,8 +476,11 @@
      *  #startBackgroundScan}
      */
     public void stopBackgroundScan(ScanListener listener) {
+        Preconditions.checkNotNull(listener, "listener cannot be null");
+        int key = removeListener(listener);
+        if (key == INVALID_KEY) return;
         validateChannel();
-        sAsyncChannel.sendMessage(CMD_STOP_BACKGROUND_SCAN, 0, removeListener(listener));
+        sAsyncChannel.sendMessage(CMD_STOP_BACKGROUND_SCAN, 0, key);
     }
     /**
      * reports currently available scan results on appropriate listeners
@@ -491,8 +501,11 @@
      *                 scans should also not share this object.
      */
     public void startScan(ScanSettings settings, ScanListener listener) {
+        Preconditions.checkNotNull(listener, "listener cannot be null");
+        int key = addListener(listener);
+        if (key == INVALID_KEY) return;
         validateChannel();
-        sAsyncChannel.sendMessage(CMD_START_SINGLE_SCAN, 0, putListener(listener), settings);
+        sAsyncChannel.sendMessage(CMD_START_SINGLE_SCAN, 0, key, settings);
     }
 
     /**
@@ -501,8 +514,11 @@
      * @param listener
      */
     public void stopScan(ScanListener listener) {
+        Preconditions.checkNotNull(listener, "listener cannot be null");
+        int key = removeListener(listener);
+        if (key == INVALID_KEY) return;
         validateChannel();
-        sAsyncChannel.sendMessage(CMD_STOP_SINGLE_SCAN, 0, removeListener(listener));
+        sAsyncChannel.sendMessage(CMD_STOP_SINGLE_SCAN, 0, key);
     }
 
     /** specifies information about an access point of interest */
@@ -634,8 +650,11 @@
      *                 provided on {@link #stopTrackingWifiChange}
      */
     public void startTrackingWifiChange(WifiChangeListener listener) {
+        Preconditions.checkNotNull(listener, "listener cannot be null");
+        int key = addListener(listener);
+        if (key == INVALID_KEY) return;
         validateChannel();
-        sAsyncChannel.sendMessage(CMD_START_TRACKING_CHANGE, 0, putListener(listener));
+        sAsyncChannel.sendMessage(CMD_START_TRACKING_CHANGE, 0, key);
     }
 
     /**
@@ -644,8 +663,10 @@
      * #stopTrackingWifiChange}
      */
     public void stopTrackingWifiChange(WifiChangeListener listener) {
+        int key = removeListener(listener);
+        if (key == INVALID_KEY) return;
         validateChannel();
-        sAsyncChannel.sendMessage(CMD_STOP_TRACKING_CHANGE, 0, removeListener(listener));
+        sAsyncChannel.sendMessage(CMD_STOP_TRACKING_CHANGE, 0, key);
     }
 
     /** @hide */
@@ -730,11 +751,14 @@
      */
     public void startTrackingBssids(BssidInfo[] bssidInfos,
                                     int apLostThreshold, BssidListener listener) {
+        Preconditions.checkNotNull(listener, "listener cannot be null");
+        int key = addListener(listener);
+        if (key == INVALID_KEY) return;
         validateChannel();
         HotlistSettings settings = new HotlistSettings();
         settings.bssidInfos = bssidInfos;
         settings.apLostThreshold = apLostThreshold;
-        sAsyncChannel.sendMessage(CMD_SET_HOTLIST, 0, putListener(listener), settings);
+        sAsyncChannel.sendMessage(CMD_SET_HOTLIST, 0, key, settings);
     }
 
     /**
@@ -742,8 +766,11 @@
      * @param listener same object provided in {@link #startTrackingBssids}
      */
     public void stopTrackingBssids(BssidListener listener) {
+        Preconditions.checkNotNull(listener, "listener cannot be null");
+        int key = removeListener(listener);
+        if (key == INVALID_KEY) return;
         validateChannel();
-        sAsyncChannel.sendMessage(CMD_RESET_HOTLIST, 0, removeListener(listener));
+        sAsyncChannel.sendMessage(CMD_RESET_HOTLIST, 0, key);
     }
 
 
@@ -812,7 +839,7 @@
 
     private static final Object sThreadRefLock = new Object();
     private static int sThreadRefCount;
-    private static HandlerThread sHandlerThread;
+    private static Handler sInternalHandler;
 
     /**
      * Create a new WifiScanner instance.
@@ -824,12 +851,29 @@
      * @hide
      */
     public WifiScanner(Context context, IWifiScanner service) {
-        mContext = context;
-        mService = service;
-        init();
+        this(context, service, null, true);
     }
 
-    private void init() {
+    /**
+     * Create a new WifiScanner instance.
+     *
+     * @param context The application context.
+     * @param service The IWifiScanner Binder interface
+     * @param looper Looper for running WifiScanner operations. If null, a handler thread will be
+     *          created for running WifiScanner operations.
+     * @param waitForConnection If true, this will not return until a connection to Wifi Scanner
+     *          service is established.
+     * @hide
+     */
+    @VisibleForTesting
+    public WifiScanner(Context context, IWifiScanner service, Looper looper,
+            boolean waitForConnection) {
+        mContext = context;
+        mService = service;
+        init(looper, waitForConnection);
+    }
+
+    private void init(Looper looper, boolean waitForConnection) {
         synchronized (sThreadRefLock) {
             if (++sThreadRefCount == 1) {
                 Messenger messenger = null;
@@ -846,17 +890,23 @@
                     return;
                 }
 
-                sHandlerThread = new HandlerThread("WifiScanner");
                 sAsyncChannel = new AsyncChannel();
                 sConnected = new CountDownLatch(1);
 
-                sHandlerThread.start();
-                Handler handler = new ServiceHandler(sHandlerThread.getLooper());
-                sAsyncChannel.connect(mContext, handler, messenger);
-                try {
-                    sConnected.await();
-                } catch (InterruptedException e) {
-                    Log.e(TAG, "interrupted wait at init");
+                if (looper == null) {
+                    HandlerThread thread = new HandlerThread("WifiScanner");
+                    thread.start();
+                    sInternalHandler = new ServiceHandler(thread.getLooper());
+                } else {
+                    sInternalHandler = new ServiceHandler(looper);
+                }
+                sAsyncChannel.connect(mContext, sInternalHandler, messenger);
+                if (waitForConnection) {
+                    try {
+                        sConnected.await();
+                    } catch (InterruptedException e) {
+                        Log.e(TAG, "interrupted wait at init");
+                    }
                 }
             }
         }
@@ -867,6 +917,30 @@
                 "No permission to access and change wifi or a bad initialization");
     }
 
+    // Add a listener into listener map. If the listener already exists, return INVALID_KEY and
+    // send an error message to internal handler; Otherwise add the listener to the listener map and
+    // return the key of the listener.
+    private int addListener(ActionListener listener) {
+        synchronized (sListenerMap) {
+            boolean keyExists = (getListenerKey(listener) != INVALID_KEY);
+            // Note we need to put the listener into listener map even if it's a duplicate as the
+            // internal handler will need the key to find the listener. In case of duplicates,
+            // removing duplicate key logic will be handled in internal handler.
+            int key = putListener(listener);
+            if (keyExists) {
+                if (DBG) Log.d(TAG, "listener key already exists");
+                OperationResult operationResult = new OperationResult(REASON_DUPLICATE_REQEUST,
+                        "Outstanding request with same key not stopped yet");
+                Message message = Message.obtain(sInternalHandler, CMD_OP_FAILED, 0, key,
+                        operationResult);
+                message.sendToTarget();
+                return INVALID_KEY;
+            } else {
+                return key;
+            }
+        }
+    }
+
     private static int putListener(Object listener) {
         if (listener == null) return INVALID_KEY;
         int key;
@@ -910,7 +984,10 @@
 
     private static int removeListener(Object listener) {
         int key = getListenerKey(listener);
-        if (key == INVALID_KEY) return key;
+        if (key == INVALID_KEY) {
+            Log.e(TAG, "listener cannot be found");
+            return key;
+        }
         synchronized (sListenerMapLock) {
             sListenerMap.remove(key);
             return key;