Merge "Let the system server have CONNECTIVITY_USE_RESTRICTED_NETWORKS."
diff --git a/api/current.txt b/api/current.txt
index 97c1049..55d3f9b 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -43064,7 +43064,7 @@
     method public String getNetworkOperator();
     method public String getNetworkOperatorName();
     method public String getNetworkSpecifier();
-    method public int getNetworkType();
+    method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getNetworkType();
     method public int getPhoneCount();
     method public int getPhoneType();
     method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public int getPreferredOpportunisticDataSubscription();
diff --git a/core/java/android/bluetooth/BluetoothMapClient.java b/core/java/android/bluetooth/BluetoothMapClient.java
index ec0180c5..69682c6 100644
--- a/core/java/android/bluetooth/BluetoothMapClient.java
+++ b/core/java/android/bluetooth/BluetoothMapClient.java
@@ -53,6 +53,10 @@
      * NOTE: HANDLE is only valid for a single session with the device. */
     public static final String EXTRA_MESSAGE_HANDLE =
             "android.bluetooth.mapmce.profile.extra.MESSAGE_HANDLE";
+    public static final String EXTRA_MESSAGE_TIMESTAMP =
+            "android.bluetooth.mapmce.profile.extra.MESSAGE_TIMESTAMP";
+    public static final String EXTRA_MESSAGE_READ_STATUS =
+            "android.bluetooth.mapmce.profile.extra.MESSAGE_READ_STATUS";
     public static final String EXTRA_SENDER_CONTACT_URI =
             "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_URI";
     public static final String EXTRA_SENDER_CONTACT_NAME =
diff --git a/core/java/android/net/DnsResolver.java b/core/java/android/net/DnsResolver.java
index 68826cb..4b2b4c3 100644
--- a/core/java/android/net/DnsResolver.java
+++ b/core/java/android/net/DnsResolver.java
@@ -16,16 +16,17 @@
 
 package android.net;
 
+import static android.net.NetworkUtils.getDnsNetId;
 import static android.net.NetworkUtils.resNetworkCancel;
 import static android.net.NetworkUtils.resNetworkQuery;
 import static android.net.NetworkUtils.resNetworkResult;
 import static android.net.NetworkUtils.resNetworkSend;
+import static android.net.util.DnsUtils.haveIpv4;
+import static android.net.util.DnsUtils.haveIpv6;
+import static android.net.util.DnsUtils.rfc6724Sort;
 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
 import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
-import static android.system.OsConstants.AF_INET;
-import static android.system.OsConstants.AF_INET6;
-import static android.system.OsConstants.IPPROTO_UDP;
-import static android.system.OsConstants.SOCK_DGRAM;
+import static android.system.OsConstants.ENONET;
 
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
@@ -34,18 +35,12 @@
 import android.os.CancellationSignal;
 import android.os.Looper;
 import android.system.ErrnoException;
-import android.system.Os;
 import android.util.Log;
 
-import libcore.io.IoUtils;
-
 import java.io.FileDescriptor;
-import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.SocketAddress;
 import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.List;
@@ -196,8 +191,8 @@
         final Object lock = new Object();
         final FileDescriptor queryfd;
         try {
-            queryfd = resNetworkSend((network != null
-                ? network.getNetIdForResolv() : NETID_UNSET), query, query.length, flags);
+            queryfd = resNetworkSend((network != null)
+                    ? network.getNetIdForResolv() : NETID_UNSET, query, query.length, flags);
         } catch (ErrnoException e) {
             executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
             return;
@@ -237,8 +232,8 @@
         final Object lock = new Object();
         final FileDescriptor queryfd;
         try {
-            queryfd = resNetworkQuery((network != null
-                    ? network.getNetIdForResolv() : NETID_UNSET), domain, nsClass, nsType, flags);
+            queryfd = resNetworkQuery((network != null)
+                    ? network.getNetIdForResolv() : NETID_UNSET, domain, nsClass, nsType, flags);
         } catch (ErrnoException e) {
             executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
             return;
@@ -252,14 +247,16 @@
 
     private class InetAddressAnswerAccumulator implements Callback<byte[]> {
         private final List<InetAddress> mAllAnswers;
+        private final Network mNetwork;
         private int mRcode;
         private DnsException mDnsException;
         private final Callback<? super List<InetAddress>> mUserCallback;
         private final int mTargetAnswerCount;
         private int mReceivedAnswerCount = 0;
 
-        InetAddressAnswerAccumulator(int size,
+        InetAddressAnswerAccumulator(@NonNull Network network, int size,
                 @NonNull Callback<? super List<InetAddress>> callback) {
+            mNetwork = network;
             mTargetAnswerCount = size;
             mAllAnswers = new ArrayList<>();
             mUserCallback = callback;
@@ -280,8 +277,7 @@
         private void maybeReportAnswer() {
             if (++mReceivedAnswerCount != mTargetAnswerCount) return;
             if (mAllAnswers.isEmpty() && maybeReportError()) return;
-            // TODO: Do RFC6724 sort.
-            mUserCallback.onAnswer(mAllAnswers, mRcode);
+            mUserCallback.onAnswer(rfc6724Sort(mNetwork, mAllAnswers), mRcode);
         }
 
         @Override
@@ -308,7 +304,7 @@
 
     /**
      * Send a DNS query with the specified name on a network with both IPv4 and IPv6,
-     * get back a set of InetAddresses asynchronously.
+     * get back a set of InetAddresses with rfc6724 sorting style asynchronously.
      *
      * This method will examine the connection ability on given network, and query IPv4
      * and IPv6 if connection is available.
@@ -335,8 +331,23 @@
             return;
         }
         final Object lock = new Object();
-        final boolean queryIpv6 = haveIpv6(network);
-        final boolean queryIpv4 = haveIpv4(network);
+        final Network queryNetwork;
+        try {
+            queryNetwork = (network != null) ? network : new Network(getDnsNetId());
+        } catch (ErrnoException e) {
+            executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
+            return;
+        }
+        final boolean queryIpv6 = haveIpv6(queryNetwork);
+        final boolean queryIpv4 = haveIpv4(queryNetwork);
+
+        // This can only happen if queryIpv4 and queryIpv6 are both false.
+        // This almost certainly means that queryNetwork does not exist or no longer exists.
+        if (!queryIpv6 && !queryIpv4) {
+            executor.execute(() -> callback.onError(
+                    new DnsException(ERROR_SYSTEM, new ErrnoException("resNetworkQuery", ENONET))));
+            return;
+        }
 
         final FileDescriptor v4fd;
         final FileDescriptor v6fd;
@@ -345,9 +356,8 @@
 
         if (queryIpv6) {
             try {
-                v6fd = resNetworkQuery((network != null
-                        ? network.getNetIdForResolv() : NETID_UNSET),
-                        domain, CLASS_IN, TYPE_AAAA, flags);
+                v6fd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN,
+                        TYPE_AAAA, flags);
             } catch (ErrnoException e) {
                 executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
                 return;
@@ -355,7 +365,6 @@
             queryCount++;
         } else v6fd = null;
 
-        // TODO: Use device flag to control the sleep time.
         // Avoiding gateways drop packets if queries are sent too close together
         try {
             Thread.sleep(SLEEP_TIME_MS);
@@ -365,9 +374,8 @@
 
         if (queryIpv4) {
             try {
-                v4fd = resNetworkQuery((network != null
-                        ? network.getNetIdForResolv() : NETID_UNSET),
-                        domain, CLASS_IN, TYPE_A, flags);
+                v4fd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN, TYPE_A,
+                        flags);
             } catch (ErrnoException e) {
                 if (queryIpv6) resNetworkCancel(v6fd);  // Closes fd, marks it invalid.
                 executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
@@ -377,7 +385,7 @@
         } else v4fd = null;
 
         final InetAddressAnswerAccumulator accumulator =
-                new InetAddressAnswerAccumulator(queryCount, callback);
+                new InetAddressAnswerAccumulator(queryNetwork, queryCount, callback);
 
         synchronized (lock)  {
             if (queryIpv6) {
@@ -398,7 +406,7 @@
 
     /**
      * Send a DNS query with the specified name and query type, get back a set of
-     * InetAddresses asynchronously.
+     * InetAddresses with rfc6724 sorting style asynchronously.
      *
      * The answer will be provided asynchronously through the provided {@link Callback}.
      *
@@ -423,15 +431,17 @@
         }
         final Object lock = new Object();
         final FileDescriptor queryfd;
+        final Network queryNetwork;
         try {
-            queryfd = resNetworkQuery((network != null
-                    ? network.getNetIdForResolv() : NETID_UNSET), domain, CLASS_IN, nsType, flags);
+            queryNetwork = (network != null) ? network : new Network(getDnsNetId());
+            queryfd = resNetworkQuery(queryNetwork.getNetIdForResolv(), domain, CLASS_IN, nsType,
+                    flags);
         } catch (ErrnoException e) {
             executor.execute(() -> callback.onError(new DnsException(ERROR_SYSTEM, e)));
             return;
         }
         final InetAddressAnswerAccumulator accumulator =
-                new InetAddressAnswerAccumulator(1, callback);
+                new InetAddressAnswerAccumulator(queryNetwork, 1, callback);
         synchronized (lock)  {
             registerFDListener(executor, queryfd, accumulator, cancellationSignal, lock);
             if (cancellationSignal == null) return;
@@ -500,38 +510,6 @@
         });
     }
 
-    // These two functions match the behaviour of have_ipv4 and have_ipv6 in the native resolver.
-    private boolean haveIpv4(@Nullable Network network) {
-        final SocketAddress addrIpv4 =
-                new InetSocketAddress(InetAddresses.parseNumericAddress("8.8.8.8"), 0);
-        return checkConnectivity(network, AF_INET, addrIpv4);
-    }
-
-    private boolean haveIpv6(@Nullable Network network) {
-        final SocketAddress addrIpv6 =
-                new InetSocketAddress(InetAddresses.parseNumericAddress("2000::"), 0);
-        return checkConnectivity(network, AF_INET6, addrIpv6);
-    }
-
-    private boolean checkConnectivity(@Nullable Network network,
-            int domain, @NonNull SocketAddress addr) {
-        final FileDescriptor socket;
-        try {
-            socket = Os.socket(domain, SOCK_DGRAM, IPPROTO_UDP);
-        } catch (ErrnoException e) {
-            return false;
-        }
-        try {
-            if (network != null) network.bindSocket(socket);
-            Os.connect(socket, addr);
-        } catch (IOException | ErrnoException e) {
-            return false;
-        } finally {
-            IoUtils.closeQuietly(socket);
-        }
-        return true;
-    }
-
     private static class DnsAddressAnswer extends DnsPacket {
         private static final String TAG = "DnsResolver.DnsAddressAnswer";
         private static final boolean DBG = false;
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index d07ff13..1728d96 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -152,6 +152,13 @@
     public static native void resNetworkCancel(FileDescriptor fd);
 
     /**
+     * DNS resolver series jni method.
+     * Attempts to get netid of network which resolver will
+     * use if no network is explicitly selected.
+     */
+    public static native int getDnsNetId() throws ErrnoException;
+
+    /**
      * Get the tcp repair window associated with the {@code fd}.
      *
      * @param fd the tcp socket's {@link FileDescriptor}.
diff --git a/core/java/android/net/StaticIpConfiguration.java b/core/java/android/net/StaticIpConfiguration.java
index baf7ae0..0600036 100644
--- a/core/java/android/net/StaticIpConfiguration.java
+++ b/core/java/android/net/StaticIpConfiguration.java
@@ -22,7 +22,6 @@
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.net.shared.InetAddressUtils;
-import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -54,7 +53,7 @@
 @TestApi
 public final class StaticIpConfiguration implements Parcelable {
     /** @hide */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+    @UnsupportedAppUsage
     @Nullable
     public LinkAddress ipAddress;
     /** @hide */
diff --git a/core/java/android/net/util/DnsUtils.java b/core/java/android/net/util/DnsUtils.java
new file mode 100644
index 0000000..e6abd50
--- /dev/null
+++ b/core/java/android/net/util/DnsUtils.java
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2019 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.util;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+import static android.system.OsConstants.IPPROTO_UDP;
+import static android.system.OsConstants.SOCK_DGRAM;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.InetAddresses;
+import android.net.Network;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
+import com.android.internal.util.BitUtils;
+
+import libcore.io.IoUtils;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * @hide
+ */
+public class DnsUtils {
+    private static final String TAG = "DnsUtils";
+    private static final int CHAR_BIT = 8;
+    public static final int IPV6_ADDR_SCOPE_NODELOCAL = 0x01;
+    public static final int IPV6_ADDR_SCOPE_LINKLOCAL = 0x02;
+    public static final int IPV6_ADDR_SCOPE_SITELOCAL = 0x05;
+    public static final int IPV6_ADDR_SCOPE_GLOBAL = 0x0e;
+    private static final Comparator<SortableAddress> sRfc6724Comparator = new Rfc6724Comparator();
+
+    /**
+     * Comparator to sort SortableAddress in Rfc6724 style.
+     */
+    public static class Rfc6724Comparator implements Comparator<SortableAddress> {
+        // This function matches the behaviour of _rfc6724_compare in the native resolver.
+        @Override
+        public int compare(SortableAddress span1, SortableAddress span2) {
+            // Rule 1: Avoid unusable destinations.
+            if (span1.hasSrcAddr != span2.hasSrcAddr) {
+                return span2.hasSrcAddr - span1.hasSrcAddr;
+            }
+
+            // Rule 2: Prefer matching scope.
+            if (span1.scopeMatch != span2.scopeMatch) {
+                return span2.scopeMatch - span1.scopeMatch;
+            }
+
+            // TODO: Implement rule 3: Avoid deprecated addresses.
+            // TODO: Implement rule 4: Prefer home addresses.
+
+            // Rule 5: Prefer matching label.
+            if (span1.labelMatch != span2.labelMatch) {
+                return span2.labelMatch - span1.labelMatch;
+            }
+
+            // Rule 6: Prefer higher precedence.
+            if (span1.precedence != span2.precedence) {
+                return span2.precedence - span1.precedence;
+            }
+
+            // TODO: Implement rule 7: Prefer native transport.
+
+            // Rule 8: Prefer smaller scope.
+            if (span1.scope != span2.scope) {
+                return span1.scope - span2.scope;
+            }
+
+            // Rule 9: Use longest matching prefix. IPv6 only.
+            if (span1.prefixMatchLen != span2.prefixMatchLen) {
+                return span2.prefixMatchLen - span1.prefixMatchLen;
+            }
+
+            // Rule 10: Leave the order unchanged. Collections.sort is a stable sort.
+            return 0;
+        }
+    }
+
+    /**
+     * Class used to sort with RFC 6724
+     */
+    public static class SortableAddress {
+        public final int label;
+        public final int labelMatch;
+        public final int scope;
+        public final int scopeMatch;
+        public final int precedence;
+        public final int prefixMatchLen;
+        public final int hasSrcAddr;
+        public final InetAddress address;
+
+        public SortableAddress(@NonNull InetAddress addr, @Nullable InetAddress srcAddr) {
+            address = addr;
+            hasSrcAddr = (srcAddr != null) ? 1 : 0;
+            label = findLabel(addr);
+            scope = findScope(addr);
+            precedence = findPrecedence(addr);
+            labelMatch = ((srcAddr != null) && (label == findLabel(srcAddr))) ? 1 : 0;
+            scopeMatch = ((srcAddr != null) && (scope == findScope(srcAddr))) ? 1 : 0;
+            if (isIpv6Address(addr) && isIpv6Address(srcAddr)) {
+                prefixMatchLen = compareIpv6PrefixMatchLen(srcAddr, addr);
+            } else {
+                prefixMatchLen = 0;
+            }
+        }
+    }
+
+    /**
+     * Sort the given address list in RFC6724 order.
+     * Will leave the list unchanged if an error occurs.
+     *
+     * This function matches the behaviour of _rfc6724_sort in the native resolver.
+     */
+    public static @NonNull List<InetAddress> rfc6724Sort(@Nullable Network network,
+            @NonNull List<InetAddress> answers) {
+        List<SortableAddress> sortableAnswerList = new ArrayList<>();
+        answers.forEach(addr -> sortableAnswerList.add(
+                new SortableAddress(addr, findSrcAddress(network, addr))));
+
+        Collections.sort(sortableAnswerList, sRfc6724Comparator);
+
+        final List<InetAddress> sortedAnswers = new ArrayList<>();
+        sortableAnswerList.forEach(ans -> sortedAnswers.add(ans.address));
+
+        return sortedAnswers;
+    }
+
+    private static @Nullable InetAddress findSrcAddress(@Nullable Network network,
+            @NonNull InetAddress addr) {
+        final int domain;
+        if (isIpv4Address(addr)) {
+            domain = AF_INET;
+        } else if (isIpv6Address(addr)) {
+            domain = AF_INET6;
+        } else {
+            return null;
+        }
+        final FileDescriptor socket;
+        try {
+            socket = Os.socket(domain, SOCK_DGRAM, IPPROTO_UDP);
+        } catch (ErrnoException e) {
+            Log.e(TAG, "findSrcAddress:" + e.toString());
+            return null;
+        }
+        try {
+            if (network != null) network.bindSocket(socket);
+            Os.connect(socket, new InetSocketAddress(addr, 0));
+            return ((InetSocketAddress) Os.getsockname(socket)).getAddress();
+        } catch (IOException | ErrnoException e) {
+            return null;
+        } finally {
+            IoUtils.closeQuietly(socket);
+        }
+    }
+
+    /**
+     * Get the label for a given IPv4/IPv6 address.
+     * RFC 6724, section 2.1.
+     *
+     * Note that Java will return an IPv4-mapped address as an IPv4 address.
+     */
+    private static int findLabel(@NonNull InetAddress addr) {
+        if (isIpv4Address(addr)) {
+            return 4;
+        } else if (isIpv6Address(addr)) {
+            if (addr.isLoopbackAddress()) {
+                return 0;
+            } else if (isIpv6Address6To4(addr)) {
+                return 2;
+            } else if (isIpv6AddressTeredo(addr)) {
+                return 5;
+            } else if (isIpv6AddressULA(addr)) {
+                return 13;
+            } else if (((Inet6Address) addr).isIPv4CompatibleAddress()) {
+                return 3;
+            } else if (addr.isSiteLocalAddress()) {
+                return 11;
+            } else if (isIpv6Address6Bone(addr)) {
+                return 12;
+            } else {
+                // All other IPv6 addresses, including global unicast addresses.
+                return 1;
+            }
+        } else {
+            // This should never happen.
+            return 1;
+        }
+    }
+
+    private static boolean isIpv6Address(@Nullable InetAddress addr) {
+        return addr instanceof Inet6Address;
+    }
+
+    private static boolean isIpv4Address(@Nullable InetAddress addr) {
+        return addr instanceof Inet4Address;
+    }
+
+    private static boolean isIpv6Address6To4(@NonNull InetAddress addr) {
+        if (!isIpv6Address(addr)) return false;
+        final byte[] byteAddr = addr.getAddress();
+        return byteAddr[0] == 0x20 && byteAddr[1] == 0x02;
+    }
+
+    private static boolean isIpv6AddressTeredo(@NonNull InetAddress addr) {
+        if (!isIpv6Address(addr)) return false;
+        final byte[] byteAddr = addr.getAddress();
+        return byteAddr[0] == 0x20 && byteAddr[1] == 0x01 && byteAddr[2] == 0x00
+                && byteAddr[3] == 0x00;
+    }
+
+    private static boolean isIpv6AddressULA(@NonNull InetAddress addr) {
+        return isIpv6Address(addr) && (addr.getAddress()[0] & 0xfe) == 0xfc;
+    }
+
+    private static boolean isIpv6Address6Bone(@NonNull InetAddress addr) {
+        if (!isIpv6Address(addr)) return false;
+        final byte[] byteAddr = addr.getAddress();
+        return byteAddr[0] == 0x3f && byteAddr[1] == (byte) 0xfe;
+    }
+
+    private static int getIpv6MulticastScope(@NonNull InetAddress addr) {
+        return !isIpv6Address(addr) ? 0 : (addr.getAddress()[1] & 0x0f);
+    }
+
+    private static int findScope(@NonNull InetAddress addr) {
+        if (isIpv6Address(addr)) {
+            if (addr.isMulticastAddress()) {
+                return getIpv6MulticastScope(addr);
+            } else if (addr.isLoopbackAddress() || addr.isLinkLocalAddress()) {
+                /**
+                 * RFC 4291 section 2.5.3 says loopback is to be treated as having
+                 * link-local scope.
+                 */
+                return IPV6_ADDR_SCOPE_LINKLOCAL;
+            } else if (addr.isSiteLocalAddress()) {
+                return IPV6_ADDR_SCOPE_SITELOCAL;
+            } else {
+                return IPV6_ADDR_SCOPE_GLOBAL;
+            }
+        } else if (isIpv4Address(addr)) {
+            if (addr.isLoopbackAddress() || addr.isLinkLocalAddress()) {
+                return IPV6_ADDR_SCOPE_LINKLOCAL;
+            } else {
+                /**
+                 * RFC 6724 section 3.2. Other IPv4 addresses, including private addresses
+                 * and shared addresses (100.64.0.0/10), are assigned global scope.
+                 */
+                return IPV6_ADDR_SCOPE_GLOBAL;
+            }
+        } else {
+            /**
+             * This should never happen.
+             * Return a scope with low priority as a last resort.
+             */
+            return IPV6_ADDR_SCOPE_NODELOCAL;
+        }
+    }
+
+    /**
+     * Get the precedence for a given IPv4/IPv6 address.
+     * RFC 6724, section 2.1.
+     *
+     * Note that Java will return an IPv4-mapped address as an IPv4 address.
+     */
+    private static int findPrecedence(@NonNull InetAddress addr) {
+        if (isIpv4Address(addr)) {
+            return 35;
+        } else if (isIpv6Address(addr)) {
+            if (addr.isLoopbackAddress()) {
+                return 50;
+            } else if (isIpv6Address6To4(addr)) {
+                return 30;
+            } else if (isIpv6AddressTeredo(addr)) {
+                return 5;
+            } else if (isIpv6AddressULA(addr)) {
+                return 3;
+            } else if (((Inet6Address) addr).isIPv4CompatibleAddress() || addr.isSiteLocalAddress()
+                    || isIpv6Address6Bone(addr)) {
+                return 1;
+            } else {
+                // All other IPv6 addresses, including global unicast addresses.
+                return 40;
+            }
+        } else {
+            return 1;
+        }
+    }
+
+    /**
+     * Find number of matching initial bits between the two addresses.
+     */
+    private static int compareIpv6PrefixMatchLen(@NonNull InetAddress srcAddr,
+            @NonNull InetAddress dstAddr) {
+        final byte[] srcByte = srcAddr.getAddress();
+        final byte[] dstByte = dstAddr.getAddress();
+
+        // This should never happen.
+        if (srcByte.length != dstByte.length) return 0;
+
+        for (int i = 0; i < dstByte.length; ++i) {
+            if (srcByte[i] == dstByte[i]) {
+                continue;
+            }
+            int x = BitUtils.uint8(srcByte[i]) ^ BitUtils.uint8(dstByte[i]);
+            return i * CHAR_BIT + (Integer.numberOfLeadingZeros(x) - 24);  // Java ints are 32 bits
+        }
+        return dstByte.length * CHAR_BIT;
+    }
+
+    /**
+     * Check if given network has Ipv4 capability
+     * This function matches the behaviour of have_ipv4 in the native resolver.
+     */
+    public static boolean haveIpv4(@Nullable Network network) {
+        final SocketAddress addrIpv4 =
+                new InetSocketAddress(InetAddresses.parseNumericAddress("8.8.8.8"), 0);
+        return checkConnectivity(network, AF_INET, addrIpv4);
+    }
+
+    /**
+     * Check if given network has Ipv6 capability
+     * This function matches the behaviour of have_ipv6 in the native resolver.
+     */
+    public static boolean haveIpv6(@Nullable Network network) {
+        final SocketAddress addrIpv6 =
+                new InetSocketAddress(InetAddresses.parseNumericAddress("2000::"), 0);
+        return checkConnectivity(network, AF_INET6, addrIpv6);
+    }
+
+    private static boolean checkConnectivity(@Nullable Network network,
+            int domain, @NonNull SocketAddress addr) {
+        final FileDescriptor socket;
+        try {
+            socket = Os.socket(domain, SOCK_DGRAM, IPPROTO_UDP);
+        } catch (ErrnoException e) {
+            return false;
+        }
+        try {
+            if (network != null) network.bindSocket(socket);
+            Os.connect(socket, addr);
+        } catch (IOException | ErrnoException e) {
+            return false;
+        } finally {
+            IoUtils.closeQuietly(socket);
+        }
+        return true;
+    }
+}
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index 929496f..350094e 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -106,6 +106,8 @@
     // Lock to synchronize between the UI thread and the thread that handles pixel copy results.
     // Only sync mWindow writes from UI thread with mWindow reads from sPixelCopyHandlerThread.
     private final Object mLock = new Object();
+    // The lock used to synchronize the UI and render threads when a #dismiss is performed.
+    private final Object mDestroyLock = new Object();
 
     /**
      * Initializes a magnifier.
@@ -173,7 +175,7 @@
                             mParentSurface.mSurface,
                             mWindowWidth, mWindowHeight, mWindowElevation, mWindowCornerRadius,
                             Handler.getMain() /* draw the magnifier on the UI thread */, mLock,
-                            mCallback);
+                            mDestroyLock, mCallback);
                 }
             }
             performPixelCopy(startX, startY, true /* update window position */);
@@ -187,9 +189,11 @@
      */
     public void dismiss() {
         if (mWindow != null) {
-            synchronized (mLock) {
-                mWindow.destroy();
-                mWindow = null;
+            synchronized (mDestroyLock) {
+                synchronized (mLock) {
+                    mWindow.destroy();
+                    mWindow = null;
+                }
             }
             mPrevPosInView.x = NONEXISTENT_PREVIOUS_CONFIG_VALUE;
             mPrevPosInView.y = NONEXISTENT_PREVIOUS_CONFIG_VALUE;
@@ -478,14 +482,16 @@
         // is performed on the UI thread and a frame callback on the render thread.
         // When both mLock and mDestroyLock need to be held at the same time,
         // mDestroyLock should be acquired before mLock in order to avoid deadlocks.
-        private final Object mDestroyLock = new Object();
+        private final Object mDestroyLock;
 
         InternalPopupWindow(final Context context, final Display display,
                 final Surface parentSurface,
                 final int width, final int height, final float elevation, final float cornerRadius,
-                final Handler handler, final Object lock, final Callback callback) {
+                final Handler handler, final Object lock, final Object destroyLock,
+                final Callback callback) {
             mDisplay = display;
             mLock = lock;
+            mDestroyLock = destroyLock;
             mCallback = callback;
 
             mContentWidth = width;
diff --git a/core/java/com/android/internal/policy/PhoneLayoutInflater.java b/core/java/com/android/internal/policy/PhoneLayoutInflater.java
index 0781f1a..991b6bb 100644
--- a/core/java/com/android/internal/policy/PhoneLayoutInflater.java
+++ b/core/java/com/android/internal/policy/PhoneLayoutInflater.java
@@ -53,11 +53,6 @@
         call through to our super class.
     */
     @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
-        View fastView = fastCreateView(name, getContext(), attrs);
-        if (fastView != null) {
-            return fastView;
-        }
-
         for (String prefix : sClassPrefixList) {
             try {
                 View view = createView(name, prefix, attrs);
@@ -73,67 +68,6 @@
         return super.onCreateView(name, attrs);
     }
 
-    private static final View fastCreateView(String name, Context context, AttributeSet attrs) {
-        switch (name) {
-            case "ActionMenuView":
-                return new android.widget.ActionMenuView(context, attrs);
-            case "AutoCompleteTextView":
-                return new android.widget.AutoCompleteTextView(context, attrs);
-            case "Button":
-                return new android.widget.Button(context, attrs);
-            case "CheckBox":
-                return new android.widget.CheckBox(context, attrs);
-            case "CheckedTextView":
-                return new android.widget.CheckedTextView(context, attrs);
-            case "EditText":
-                return new android.widget.EditText(context, attrs);
-            case "FrameLayout":
-                return new android.widget.FrameLayout(context, attrs);
-            case "ImageButton":
-                return new android.widget.ImageButton(context, attrs);
-            case "ImageView":
-                return new android.widget.ImageView(context, attrs);
-            case "LinearLayout":
-                return new android.widget.LinearLayout(context, attrs);
-            case "ListView":
-                return new android.widget.ListView(context, attrs);
-            case "MultiAutoCompleteTextView":
-                return new android.widget.AutoCompleteTextView(context, attrs);
-            case "ProgressBar":
-                return new android.widget.ProgressBar(context, attrs);
-            case "RadioButton":
-                return new android.widget.RadioButton(context, attrs);
-            case "RatingBar":
-                return new android.widget.RatingBar(context, attrs);
-            case "RelativeLayout":
-                return new android.widget.RelativeLayout(context, attrs);
-            case "ScrollView":
-                return new android.widget.ScrollView(context, attrs);
-            case "SeekBar":
-                return new android.widget.SeekBar(context, attrs);
-            case "Space":
-                return new android.widget.Space(context, attrs);
-            case "Spinner":
-                return new android.widget.Spinner(context, attrs);
-            case "TextureView":
-                return new android.view.TextureView(context, attrs);
-            case "TextView":
-                return new android.widget.TextView(context, attrs);
-            case "ToggleButton":
-                return new android.widget.ToggleButton(context, attrs);
-            case "Toolbar":
-                return new android.widget.Toolbar(context, attrs);
-            case "View":
-                return new android.view.View(context, attrs);
-            case "ViewFlipper":
-                return new android.widget.ViewFlipper(context, attrs);
-            case "ViewStub":
-                return new android.view.ViewStub(context, attrs);
-            default:
-                return null;
-        }
-    }
-
     public LayoutInflater cloneInContext(Context newContext) {
         return new PhoneLayoutInflater(this, newContext);
     }
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index c5fc9b3..00e0e3a 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -18,26 +18,27 @@
 
 #include <vector>
 
-#include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include <nativehelper/ScopedLocalRef.h>
-#include "NetdClient.h"
-#include <utils/misc.h>
-#include <android_runtime/AndroidRuntime.h>
-#include <utils/Log.h>
 #include <arpa/inet.h>
-#include <net/if.h>
 #include <linux/filter.h>
 #include <linux/if_arp.h>
 #include <linux/tcp.h>
+#include <net/if.h>
 #include <netinet/ether.h>
 #include <netinet/icmp6.h>
 #include <netinet/ip.h>
 #include <netinet/ip6.h>
 #include <netinet/udp.h>
-#include <cutils/properties.h>
 
+#include <android_runtime/AndroidRuntime.h>
+#include <cutils/properties.h>
+#include <utils/misc.h>
+#include <utils/Log.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+
+#include "NetdClient.h"
 #include "core_jni_helpers.h"
+#include "jni.h"
 
 extern "C" {
 int ifc_enable(const char *ifname);
@@ -303,6 +304,15 @@
     jniSetFileDescriptorOfFD(env, javaFd, -1);
 }
 
+static jint android_net_utils_getDnsNetId(JNIEnv *env, jobject thiz) {
+    int dnsNetId = getNetworkForDns();
+    if (dnsNetId < 0) {
+        throwErrnoException(env, "getDnsNetId", -dnsNetId);
+    }
+
+    return dnsNetId;
+}
+
 static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, jobject javaFd) {
     if (javaFd == NULL) {
         jniThrowNullPointerException(env, NULL);
@@ -359,6 +369,7 @@
     { "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery },
     { "resNetworkResult", "(Ljava/io/FileDescriptor;)Landroid/net/DnsResolver$DnsResponse;", (void*) android_net_utils_resNetworkResult },
     { "resNetworkCancel", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_resNetworkCancel },
+    { "getDnsNetId", "()I", (void*) android_net_utils_getDnsNetId },
 };
 
 int register_android_net_NetworkUtils(JNIEnv* env)
diff --git a/core/jni/android_util_jar_StrictJarFile.cpp b/core/jni/android_util_jar_StrictJarFile.cpp
index e33da91..57688c4 100644
--- a/core/jni/android_util_jar_StrictJarFile.cpp
+++ b/core/jni/android_util_jar_StrictJarFile.cpp
@@ -112,7 +112,7 @@
 
 jobject StrictJarFile_nativeNextEntry(JNIEnv* env, jobject, jlong iterationHandle) {
   ZipEntry data;
-  ZipString entryName;
+  std::string entryName;
 
   IterationHandle* handle = reinterpret_cast<IterationHandle*>(iterationHandle);
   const int32_t error = Next(*handle->CookieAddress(), &data, &entryName);
@@ -121,10 +121,7 @@
     return NULL;
   }
 
-  std::unique_ptr<char[]> entryNameCString(new char[entryName.name_length + 1]);
-  memcpy(entryNameCString.get(), entryName.name, entryName.name_length);
-  entryNameCString[entryName.name_length] = '\0';
-  ScopedLocalRef<jstring> entryNameString(env, env->NewStringUTF(entryNameCString.get()));
+  ScopedLocalRef<jstring> entryNameString(env, env->NewStringUTF(entryName.c_str()));
 
   return newZipEntry(env, data, entryNameString.get());
 }
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index afe7913..d6ffa8a 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -147,6 +147,9 @@
     <assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="media" />
     <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="media" />
     <assign-permission name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" uid="media" />
+    <assign-permission name="android.permission.INTERNET" uid="media" />
+
+    <assign-permission name="android.permission.INTERNET" uid="shell" />
 
     <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="audioserver" />
     <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="audioserver" />
diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml
index de45784..bb838a2 100644
--- a/packages/NetworkStack/AndroidManifest.xml
+++ b/packages/NetworkStack/AndroidManifest.xml
@@ -45,5 +45,8 @@
                 <action android:name="android.net.INetworkStackConnector"/>
             </intent-filter>
         </service>
+        <service android:name="com.android.server.connectivity.ipmemorystore.RegularMaintenanceJobService"
+                 android:permission="android.permission.BIND_JOB_SERVICE" >
+        </service>
     </application>
 </manifest>
diff --git a/packages/NetworkStack/AndroidManifestBase.xml b/packages/NetworkStack/AndroidManifestBase.xml
index d00a551..69a4da4 100644
--- a/packages/NetworkStack/AndroidManifestBase.xml
+++ b/packages/NetworkStack/AndroidManifestBase.xml
@@ -25,9 +25,5 @@
         android:defaultToDeviceProtectedStorage="true"
         android:directBootAware="true"
         android:usesCleartextTraffic="true">
-
-        <service android:name="com.android.server.connectivity.ipmemorystore.RegularMaintenanceJobService"
-                 android:permission="android.permission.BIND_JOB_SERVICE" >
-        </service>
     </application>
 </manifest>
diff --git a/packages/NetworkStack/AndroidManifest_InProcess.xml b/packages/NetworkStack/AndroidManifest_InProcess.xml
index 275cd02..2778a2a 100644
--- a/packages/NetworkStack/AndroidManifest_InProcess.xml
+++ b/packages/NetworkStack/AndroidManifest_InProcess.xml
@@ -27,5 +27,9 @@
                 <action android:name="android.net.INetworkStackConnector.InProcess"/>
             </intent-filter>
         </service>
+        <service android:name="com.android.server.connectivity.ipmemorystore.RegularMaintenanceJobService"
+                 android:process="system"
+                 android:permission="android.permission.BIND_JOB_SERVICE" >
+        </service>
     </application>
 </manifest>
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
index 8e9350d..4e40ba4 100644
--- a/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/NetworkMonitor.java
@@ -24,8 +24,13 @@
 import static android.net.ConnectivityManager.TYPE_MOBILE;
 import static android.net.ConnectivityManager.TYPE_WIFI;
 import static android.net.DnsResolver.FLAG_EMPTY;
-import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
-import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_FALLBACK;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
@@ -69,7 +74,6 @@
 import android.content.res.Resources;
 import android.net.ConnectivityManager;
 import android.net.DnsResolver;
-import android.net.INetworkMonitor;
 import android.net.INetworkMonitorCallbacks;
 import android.net.LinkProperties;
 import android.net.Network;
@@ -277,7 +281,7 @@
     private static final int BLAME_FOR_EVALUATION_ATTEMPTS = 5;
     // Delay between reevaluations once a captive portal has been found.
     private static final int CAPTIVE_PORTAL_REEVALUATE_DELAY_MS = 10 * 60 * 1000;
-
+    private static final int NETWORK_VALIDATION_RESULT_INVALID = 0;
     private String mPrivateDnsProviderHostname = "";
 
     private final Context mContext;
@@ -348,7 +352,8 @@
     private long mLastProbeTime;
     // Set to true if data stall is suspected and reset to false after metrics are sent to statsd.
     private boolean mCollectDataStallMetrics;
-    private boolean mAcceptPartialConnectivity;
+    private boolean mAcceptPartialConnectivity = false;
+    private final EvaluationState mEvaluationState = new EvaluationState();
 
     public NetworkMonitor(Context context, INetworkMonitorCallbacks cb, Network network,
             SharedLog validationLog) {
@@ -601,7 +606,8 @@
                         case APP_RETURN_UNWANTED:
                             mDontDisplaySigninNotification = true;
                             mUserDoesNotWant = true;
-                            notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null);
+                            mEvaluationState.reportEvaluationResult(
+                                    NETWORK_VALIDATION_RESULT_INVALID, null);
                             // TODO: Should teardown network.
                             mUidResponsibleForReeval = 0;
                             transitionTo(mEvaluatingState);
@@ -653,7 +659,7 @@
                 // re-evaluating and get the result of partial connectivity, ProbingState will
                 // disable HTTPS probe and transition to EvaluatingPrivateDnsState.
                 case EVENT_ACCEPT_PARTIAL_CONNECTIVITY:
-                    mAcceptPartialConnectivity = true;
+                    maybeDisableHttpsProbing(true /* acceptPartial */);
                     break;
                 case EVENT_LINK_PROPERTIES_CHANGED:
                     mLinkProperties = (LinkProperties) message.obj;
@@ -677,7 +683,14 @@
         public void enter() {
             maybeLogEvaluationResult(
                     networkEventType(validationStage(), EvaluationResult.VALIDATED));
-            notifyNetworkTested(INetworkMonitor.NETWORK_TEST_RESULT_VALID, null);
+            // If the user has accepted partial connectivity and HTTPS probing is disabled, then
+            // mark the network as validated and partial so that settings can keep informing the
+            // user that the connection is limited.
+            int result = NETWORK_VALIDATION_RESULT_VALID;
+            if (!mUseHttps && mAcceptPartialConnectivity) {
+                result |= NETWORK_VALIDATION_RESULT_PARTIAL;
+            }
+            mEvaluationState.reportEvaluationResult(result, null /* redirectUrl */);
             mValidations++;
         }
 
@@ -820,6 +833,9 @@
             }
             mReevaluateDelayMs = INITIAL_REEVALUATE_DELAY_MS;
             mEvaluateAttempts = 0;
+            // Reset all current probe results to zero, but retain current validation state until
+            // validation succeeds or fails.
+            mEvaluationState.clearProbeResults();
         }
 
         @Override
@@ -875,8 +891,7 @@
                 // 1. Network is connected and finish the network validation.
                 // 2. NetworkMonitor detects network is partial connectivity and user accepts it.
                 case EVENT_ACCEPT_PARTIAL_CONNECTIVITY:
-                    mAcceptPartialConnectivity = true;
-                    mUseHttps = false;
+                    maybeDisableHttpsProbing(true /* acceptPartial */);
                     transitionTo(mEvaluatingPrivateDnsState);
                     return HANDLED;
                 default:
@@ -1019,6 +1034,8 @@
                 mPrivateDnsConfig = null;
                 validationLog("Strict mode hostname resolution failed: " + uhe.getMessage());
             }
+            mEvaluationState.reportProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS,
+                    (mPrivateDnsConfig != null) /* succeeded */);
         }
 
         private void notifyPrivateDnsConfigResolved() {
@@ -1030,13 +1047,18 @@
         }
 
         private void handlePrivateDnsEvaluationFailure() {
-            notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, null);
-
+            mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID,
+                    null /* redirectUrl */);
             // Queue up a re-evaluation with backoff.
             //
             // TODO: Consider abandoning this state after a few attempts and
             // transitioning back to EvaluatingState, to perhaps give ourselves
             // the opportunity to (re)detect a captive portal or something.
+            //
+            // TODO: distinguish between CMD_EVALUATE_PRIVATE_DNS messages that are caused by server
+            // lookup failures (which should continue to do exponential backoff) and
+            // CMD_EVALUATE_PRIVATE_DNS messages that are caused by user reconfiguration (which
+            // should be processed immediately.
             sendMessageDelayed(CMD_EVALUATE_PRIVATE_DNS, mPrivateDnsReevalDelayMs);
             mPrivateDnsReevalDelayMs *= 2;
             if (mPrivateDnsReevalDelayMs > MAX_REEVALUATE_DELAY_MS) {
@@ -1050,21 +1072,22 @@
             final String host = UUID.randomUUID().toString().substring(0, 8)
                     + oneTimeHostnameSuffix;
             final Stopwatch watch = new Stopwatch().start();
+            boolean success = false;
+            long time;
             try {
                 final InetAddress[] ips = mNetwork.getAllByName(host);
-                final long time = watch.stop();
+                time = watch.stop();
                 final String strIps = Arrays.toString(ips);
-                final boolean success = (ips != null && ips.length > 0);
+                success = (ips != null && ips.length > 0);
                 validationLog(PROBE_PRIVDNS, host, String.format("%dms: %s", time, strIps));
-                logValidationProbe(time, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE);
-                return success;
             } catch (UnknownHostException uhe) {
-                final long time = watch.stop();
+                time = watch.stop();
                 validationLog(PROBE_PRIVDNS, host,
                         String.format("%dms - Error: %s", time, uhe.getMessage()));
-                logValidationProbe(time, PROBE_PRIVDNS, DNS_FAILURE);
             }
-            return false;
+            logValidationProbe(time, PROBE_PRIVDNS, success ? DNS_SUCCESS : DNS_FAILURE);
+            mEvaluationState.reportProbeResult(NETWORK_VALIDATION_PROBE_PRIVDNS, success);
+            return success;
         }
     }
 
@@ -1106,22 +1129,24 @@
                         // state (even if no Private DNS validation required).
                         transitionTo(mEvaluatingPrivateDnsState);
                     } else if (probeResult.isPortal()) {
-                        notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, probeResult.redirectUrl);
+                        mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID,
+                                probeResult.redirectUrl);
                         mLastPortalProbeResult = probeResult;
                         transitionTo(mCaptivePortalState);
                     } else if (probeResult.isPartialConnectivity()) {
-                        logNetworkEvent(NetworkEvent.NETWORK_PARTIAL_CONNECTIVITY);
-                        notifyNetworkTested(NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY,
-                                probeResult.redirectUrl);
+                        mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_PARTIAL,
+                                null /* redirectUrl */);
+                        // Check if disable https probing needed.
+                        maybeDisableHttpsProbing(mAcceptPartialConnectivity);
                         if (mAcceptPartialConnectivity) {
-                            mUseHttps = false;
                             transitionTo(mEvaluatingPrivateDnsState);
                         } else {
                             transitionTo(mWaitingForNextProbeState);
                         }
                     } else {
                         logNetworkEvent(NetworkEvent.NETWORK_VALIDATION_FAILED);
-                        notifyNetworkTested(NETWORK_TEST_RESULT_INVALID, probeResult.redirectUrl);
+                        mEvaluationState.reportEvaluationResult(NETWORK_VALIDATION_RESULT_INVALID,
+                                null /* redirectUrl */);
                         transitionTo(mWaitingForNextProbeState);
                     }
                     return HANDLED;
@@ -1469,10 +1494,13 @@
         final CaptivePortalProbeResult result;
         if (pacUrl != null) {
             result = sendDnsAndHttpProbes(null, pacUrl, ValidationProbeEvent.PROBE_PAC);
+            reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, result);
         } else if (mUseHttps) {
+            // Probe results are reported inside sendParallelHttpProbes.
             result = sendParallelHttpProbes(proxyInfo, httpsUrl, httpUrl);
         } else {
             result = sendDnsAndHttpProbes(proxyInfo, httpUrl, ValidationProbeEvent.PROBE_HTTP);
+            reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, result);
         }
 
         long endTime = SystemClock.elapsedRealtime();
@@ -1484,6 +1512,7 @@
         log("isCaptivePortal: isSuccessful()=" + result.isSuccessful()
                 + " isPortal()=" + result.isPortal()
                 + " RedirectUrl=" + result.redirectUrl
+                + " isPartialConnectivity()=" + result.isPartialConnectivity()
                 + " Time=" + (endTime - startTime) + "ms");
 
         return result;
@@ -1498,6 +1527,10 @@
         // Only do this if HttpURLConnection is about to, to avoid any potentially
         // unnecessary resolution.
         final String host = (proxy != null) ? proxy.getHost() : url.getHost();
+        // This method cannot safely report probe results because it might not be running on the
+        // state machine thread. Reporting results here would cause races and potentially send
+        // information to callers that does not make sense because the state machine has already
+        // changed state.
         sendDnsProbe(host);
         return sendHttpProbe(url, probeType, null);
     }
@@ -1682,10 +1715,12 @@
 
         // Look for a conclusive probe result first.
         if (httpResult.isPortal()) {
+            reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, httpResult);
             return httpResult;
         }
         // httpsResult.isPortal() is not expected, but check it nonetheless.
         if (httpsResult.isPortal() || httpsResult.isSuccessful()) {
+            reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTPS, httpsResult);
             return httpsResult;
         }
         // If a fallback method exists, use it to retry portal detection.
@@ -1695,6 +1730,7 @@
         CaptivePortalProbeResult fallbackProbeResult = null;
         if (fallbackUrl != null) {
             fallbackProbeResult = sendHttpProbe(fallbackUrl, PROBE_FALLBACK, probeSpec);
+            reportHttpProbeResult(NETWORK_VALIDATION_PROBE_FALLBACK, fallbackProbeResult);
             if (fallbackProbeResult.isPortal()) {
                 return fallbackProbeResult;
             }
@@ -1702,10 +1738,15 @@
         // Otherwise wait until http and https probes completes and use their results.
         try {
             httpProbe.join();
+            reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, httpProbe.result());
+
             if (httpProbe.result().isPortal()) {
                 return httpProbe.result();
             }
+
             httpsProbe.join();
+            reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTPS, httpsProbe.result());
+
             final boolean isHttpSuccessful =
                     (httpProbe.result().isSuccessful()
                     || (fallbackProbeResult != null && fallbackProbeResult.isSuccessful()));
@@ -2024,4 +2065,79 @@
 
         return result;
     }
+
+    // Class to keep state of evaluation results and probe results.
+    // The main purpose is to ensure NetworkMonitor can notify ConnectivityService of probe results
+    // as soon as they happen, without triggering any other changes. This requires keeping state on
+    // the most recent evaluation result. Calling reportProbeResult will ensure that the results
+    // reported to ConnectivityService contain the previous evaluation result, and thus won't
+    // trigger a validation or partial connectivity state change.
+    @VisibleForTesting
+    protected class EvaluationState {
+        // The latest validation result for this network. This is a bitmask of
+        // INetworkMonitor.NETWORK_VALIDATION_RESULT_* constants.
+        private int mEvaluationResult = NETWORK_VALIDATION_RESULT_INVALID;
+        // Indicates which probes have completed since clearProbeResults was called.
+        // This is a bitmask of INetworkMonitor.NETWORK_VALIDATION_PROBE_* constants.
+        private int mProbeResults = 0;
+        // The latest redirect URL.
+        private String mRedirectUrl;
+
+        protected void clearProbeResults() {
+            mProbeResults = 0;
+        }
+
+        // Probe result for http probe should be updated from reportHttpProbeResult().
+        protected void reportProbeResult(int probeResult, boolean succeeded) {
+            if (succeeded) {
+                mProbeResults |= probeResult;
+            } else {
+                mProbeResults &= ~probeResult;
+            }
+            notifyNetworkTested(getNetworkTestResult(), mRedirectUrl);
+        }
+
+        protected void reportEvaluationResult(int result, @Nullable String redirectUrl) {
+            mEvaluationResult = result;
+            mRedirectUrl = redirectUrl;
+            notifyNetworkTested(getNetworkTestResult(), mRedirectUrl);
+        }
+
+        protected int getNetworkTestResult() {
+            return mEvaluationResult | mProbeResults;
+        }
+    }
+
+    @VisibleForTesting
+    protected EvaluationState getEvaluationState() {
+        return mEvaluationState;
+    }
+
+    private void maybeDisableHttpsProbing(boolean acceptPartial) {
+        mAcceptPartialConnectivity = acceptPartial;
+        // Ignore https probe in next validation if user accept partial connectivity on a partial
+        // connectivity network.
+        if (((mEvaluationState.getNetworkTestResult() & NETWORK_VALIDATION_RESULT_PARTIAL) != 0)
+                && mAcceptPartialConnectivity) {
+            mUseHttps = false;
+        }
+    }
+
+    // Report HTTP, HTTP or FALLBACK probe result.
+    @VisibleForTesting
+    protected void reportHttpProbeResult(int probeResult,
+                @NonNull final CaptivePortalProbeResult result) {
+        boolean succeeded = result.isSuccessful();
+        // The success of a HTTP probe does not tell us whether the DNS probe succeeded.
+        // The DNS and HTTP probes run one after the other in sendDnsAndHttpProbes, and that
+        // method cannot report the result of the DNS probe because that it could be running
+        // on a different thread which is racing with the main state machine thread. So, if
+        // an HTTP or HTTPS probe succeeded, assume that the DNS probe succeeded. But if an
+        // HTTP or HTTPS probe failed, don't assume that DNS is not working.
+        // TODO: fix this.
+        if (succeeded) {
+            probeResult |= NETWORK_VALIDATION_PROBE_DNS;
+        }
+        mEvaluationState.reportProbeResult(probeResult, succeeded);
+    }
 }
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
index 764e2d0..a538a5b 100644
--- a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreDatabase.java
@@ -410,6 +410,7 @@
     private static final String[] DATA_COLUMN = new String[] {
             PrivateDataContract.COLNAME_DATA
     };
+
     @Nullable
     static byte[] retrieveBlob(@NonNull final SQLiteDatabase db, @NonNull final String key,
             @NonNull final String clientId, @NonNull final String name) {
@@ -432,6 +433,57 @@
     }
 
     /**
+     * Wipe all data in tables when network factory reset occurs.
+     */
+    static void wipeDataUponNetworkReset(@NonNull final SQLiteDatabase db) {
+        for (int remainingRetries = 3; remainingRetries > 0; --remainingRetries) {
+            db.beginTransaction();
+            try {
+                db.delete(NetworkAttributesContract.TABLENAME, null, null);
+                db.delete(PrivateDataContract.TABLENAME, null, null);
+                final Cursor cursorNetworkAttributes = db.query(
+                        // table name
+                        NetworkAttributesContract.TABLENAME,
+                        // column name
+                        new String[] { NetworkAttributesContract.COLNAME_L2KEY },
+                        null, // selection
+                        null, // selectionArgs
+                        null, // groupBy
+                        null, // having
+                        null, // orderBy
+                        "1"); // limit
+                if (0 != cursorNetworkAttributes.getCount()) {
+                    cursorNetworkAttributes.close();
+                    continue;
+                }
+                cursorNetworkAttributes.close();
+                final Cursor cursorPrivateData = db.query(
+                        // table name
+                        PrivateDataContract.TABLENAME,
+                        // column name
+                        new String[] { PrivateDataContract.COLNAME_L2KEY },
+                        null, // selection
+                        null, // selectionArgs
+                        null, // groupBy
+                        null, // having
+                        null, // orderBy
+                        "1"); // limit
+                if (0 != cursorPrivateData.getCount()) {
+                    cursorPrivateData.close();
+                    continue;
+                }
+                cursorPrivateData.close();
+                db.setTransactionSuccessful();
+                return;
+            } catch (SQLiteException e) {
+                Log.e(TAG, "Could not wipe the data in database", e);
+            } finally {
+                db.endTransaction();
+            }
+        }
+    }
+
+    /**
      * The following is a horrible hack that is necessary because the Android SQLite API does not
      * have a way to query a binary blob. This, almost certainly, is an overlook.
      *
diff --git a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
index 8312dfe..55ab8d4 100644
--- a/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
+++ b/packages/NetworkStack/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreService.java
@@ -60,7 +60,6 @@
  */
 public class IpMemoryStoreService extends IIpMemoryStore.Stub {
     private static final String TAG = IpMemoryStoreService.class.getSimpleName();
-    private static final int MAX_CONCURRENT_THREADS = 4;
     private static final int DATABASE_SIZE_THRESHOLD = 10 * 1024 * 1024; //10MB
     private static final int MAX_DROP_RECORD_TIMES = 500;
     private static final int MIN_DELETE_NUM = 5;
@@ -107,23 +106,17 @@
             db = null;
         }
         mDb = db;
-        // The work-stealing thread pool executor will spawn threads as needed up to
-        // the max only when there is no free thread available. This generally behaves
-        // exactly like one would expect it intuitively :
-        // - When work arrives, it will spawn a new thread iff there are no available threads
-        // - When there is no work to do it will shutdown threads after a while (the while
-        //   being equal to 2 seconds (not configurable) when max threads are spun up and
-        //   twice as much for every one less thread)
-        // - When all threads are busy the work is enqueued and waits for any worker
-        //   to become available.
-        // Because the stealing pool is made for very heavily parallel execution of
-        // small tasks that spawn others, it creates a queue per thread that in this
-        // case is overhead. However, the three behaviors above make it a superior
-        // choice to cached or fixedThreadPoolExecutor, neither of which can actually
-        // enqueue a task waiting for a thread to be free. This can probably be solved
-        // with judicious subclassing of ThreadPoolExecutor, but that's a lot of dangerous
-        // complexity for little benefit in this case.
-        mExecutor = Executors.newWorkStealingPool(MAX_CONCURRENT_THREADS);
+        // The single thread executor guarantees that all work is executed sequentially on the
+        // same thread, and no two tasks can be active at the same time. This is required to
+        // ensure operations from multiple clients don't interfere with each other (in particular,
+        // operations involving a transaction must not run concurrently with other operations
+        // as the other operations might be taken as part of the transaction). By default, the
+        // single thread executor runs off an unbounded queue.
+        // TODO : investigate replacing this scheme with a scheme where each thread has its own
+        // instance of the database, as it may be faster. It is likely however that IpMemoryStore
+        // operations are mostly IO-bound anyway, and additional contention is unlikely to bring
+        // benefits. Alternatively, a read-write lock might increase throughput.
+        mExecutor = Executors.newSingleThreadExecutor();
         RegularMaintenanceJobService.schedule(mContext, this);
     }
 
@@ -410,8 +403,12 @@
         });
     }
 
+    /**
+     * Wipe the data in IpMemoryStore database upon network factory reset.
+     */
     @Override
     public void factoryReset() {
+        mExecutor.execute(() -> IpMemoryStoreDatabase.wipeDataUponNetworkReset(mDb));
     }
 
     /** Get db size threshold. */
diff --git a/packages/NetworkStack/tests/unit/Android.bp b/packages/NetworkStack/tests/unit/Android.bp
index 6cc8054..85951eb 100644
--- a/packages/NetworkStack/tests/unit/Android.bp
+++ b/packages/NetworkStack/tests/unit/Android.bp
@@ -36,7 +36,6 @@
         "libdexmakerjvmtiagent",
         "libstaticjvmtiagent",
         // For ApfTest
-        "libartbase",
         "libbacktrace",
         "libbase",
         "libbinder",
@@ -45,7 +44,6 @@
         "libcgrouprc",
         "libcrypto",
         "libcutils",
-        "libdexfile",
         "ld-android",
         "libdl_android",
         "libhidl-gen-utils",
diff --git a/packages/NetworkStack/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java b/packages/NetworkStack/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
index 832b712..262641d 100644
--- a/packages/NetworkStack/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
+++ b/packages/NetworkStack/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java
@@ -17,9 +17,13 @@
 package com.android.server.connectivity;
 
 import static android.net.CaptivePortal.APP_RETURN_DISMISSED;
-import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
-import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
-import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_FALLBACK;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_PRIVDNS;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_CONSECUTIVE_DNS_TIMEOUT_THRESHOLD;
 import static android.net.util.DataStallUtils.CONFIG_DATA_STALL_EVALUATION_TYPE;
@@ -98,6 +102,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
+import org.mockito.verification.VerificationWithTimeout;
 
 import java.io.IOException;
 import java.net.HttpURLConnection;
@@ -149,6 +154,19 @@
     private static final String TEST_OTHER_FALLBACK_URL = "http://otherfallback.google.com/gen_204";
     private static final String TEST_MCCMNC = "123456";
 
+    private static final int VALIDATION_RESULT_INVALID = 0;
+    private static final int VALIDATION_RESULT_PORTAL = 0;
+    private static final String TEST_REDIRECT_URL = "android.com";
+    private static final int VALIDATION_RESULT_PARTIAL = NETWORK_VALIDATION_PROBE_DNS
+            | NETWORK_VALIDATION_PROBE_HTTP
+            | NETWORK_VALIDATION_RESULT_PARTIAL;
+    private static final int VALIDATION_RESULT_FALLBACK_PARTIAL = NETWORK_VALIDATION_PROBE_DNS
+            | NETWORK_VALIDATION_PROBE_FALLBACK
+            | NETWORK_VALIDATION_RESULT_PARTIAL;
+    private static final int VALIDATION_RESULT_VALID = NETWORK_VALIDATION_PROBE_DNS
+            | NETWORK_VALIDATION_PROBE_HTTPS
+            | NETWORK_VALIDATION_RESULT_VALID;
+
     private static final int RETURN_CODE_DNS_SUCCESS = 0;
     private static final int RETURN_CODE_DNS_TIMEOUT = 255;
     private static final int DEFAULT_DNS_TIMEOUT_THRESHOLD = 5;
@@ -472,8 +490,7 @@
     public void testIsCaptivePortal_HttpProbeIsPortal() throws IOException {
         setSslException(mHttpsConnection);
         setPortal302(mHttpConnection);
-
-        runPortalNetworkTest();
+        runPortalNetworkTest(VALIDATION_RESULT_PORTAL);
     }
 
     @Test
@@ -489,8 +506,7 @@
         setSslException(mHttpsConnection);
         setStatus(mHttpConnection, 500);
         setPortal302(mFallbackConnection);
-
-        runPortalNetworkTest();
+        runPortalNetworkTest(VALIDATION_RESULT_INVALID);
     }
 
     @Test
@@ -518,7 +534,7 @@
         when(mRandom.nextInt()).thenReturn(2);
 
         // First check always uses the first fallback URL: inconclusive
-        final NetworkMonitor monitor = runNetworkTest(NETWORK_TEST_RESULT_INVALID);
+        final NetworkMonitor monitor = runNetworkTest(VALIDATION_RESULT_INVALID);
         assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
         verify(mFallbackConnection, times(1)).getResponseCode();
         verify(mOtherFallbackConnection, never()).getResponseCode();
@@ -548,8 +564,7 @@
         setSslException(mHttpsConnection);
         setStatus(mHttpConnection, 500);
         setPortal302(mOtherFallbackConnection);
-
-        runPortalNetworkTest();
+        runPortalNetworkTest(VALIDATION_RESULT_INVALID);
         verify(mOtherFallbackConnection, times(1)).getResponseCode();
         verify(mFallbackConnection, never()).getResponseCode();
     }
@@ -572,7 +587,7 @@
         set302(mOtherFallbackConnection, "https://www.google.com/test?q=3");
 
         // HTTPS failed, fallback spec went through -> partial connectivity
-        runPartialConnectivityNetworkTest();
+        runPartialConnectivityNetworkTest(VALIDATION_RESULT_FALLBACK_PARTIAL);
         verify(mOtherFallbackConnection, times(1)).getResponseCode();
         verify(mFallbackConnection, never()).getResponseCode();
     }
@@ -581,8 +596,7 @@
     public void testIsCaptivePortal_FallbackSpecIsPortal() throws IOException {
         setupFallbackSpec();
         set302(mOtherFallbackConnection, "http://login.portal.example.com");
-
-        runPortalNetworkTest();
+        runPortalNetworkTest(VALIDATION_RESULT_INVALID);
     }
 
     @Test
@@ -591,7 +605,7 @@
         setSslException(mHttpsConnection);
         setPortal302(mHttpConnection);
 
-        runNotPortalNetworkTest();
+        runNoValidationNetworkTest();
     }
 
     @Test
@@ -677,7 +691,8 @@
 
     @Test
     public void testNoInternetCapabilityValidated() throws Exception {
-        runNetworkTest(NO_INTERNET_CAPABILITIES, NETWORK_TEST_RESULT_VALID);
+        runNetworkTest(NO_INTERNET_CAPABILITIES, NETWORK_VALIDATION_RESULT_VALID,
+                getGeneralVerification());
         verify(mCleartextDnsNetwork, never()).openConnection(any());
     }
 
@@ -713,10 +728,11 @@
         setStatus(mHttpsConnection, 204);
         setStatus(mHttpConnection, 204);
 
+        reset(mCallbacks);
         nm.notifyCaptivePortalAppFinished(APP_RETURN_DISMISSED);
-        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
-                .notifyNetworkTested(NETWORK_TEST_RESULT_VALID, null);
-
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeastOnce())
+                .notifyNetworkTested(eq(NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP
+                        | NETWORK_VALIDATION_RESULT_VALID), any());
         assertEquals(0, mRegisteredReceivers.size());
     }
 
@@ -730,7 +746,8 @@
         wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
         wnm.notifyNetworkConnected(TEST_LINK_PROPERTIES, NOT_METERED_CAPABILITIES);
         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
-                .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null));
+                .notifyNetworkTested(eq(VALIDATION_RESULT_VALID | NETWORK_VALIDATION_PROBE_PRIVDNS),
+                        eq(null));
     }
 
     @Test
@@ -743,38 +760,47 @@
         WrappedNetworkMonitor wnm = makeNotMeteredNetworkMonitor();
         wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
         wnm.notifyNetworkConnected(TEST_LINK_PROPERTIES, NOT_METERED_CAPABILITIES);
-        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
-                .notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null));
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeastOnce())
+                .notifyNetworkTested(
+                        eq(NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS),
+                        eq(null));
 
         // Fix DNS and retry, expect validation to succeed.
         reset(mCallbacks);
         mFakeDns.setAnswer("dns.google", new String[]{"2001:db8::1"});
 
         wnm.forceReevaluation(Process.myUid());
-        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
-                .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null));
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeastOnce())
+                .notifyNetworkTested(eq(VALIDATION_RESULT_VALID | NETWORK_VALIDATION_PROBE_PRIVDNS),
+                        eq(null));
 
         // Change configuration to an invalid DNS name, expect validation to fail.
         reset(mCallbacks);
         mFakeDns.setAnswer("dns.bad", new String[0]);
         wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.bad", new InetAddress[0]));
+        // Strict mode hostname resolve fail. Expect only notification for evaluation fail. No probe
+        // notification.
         verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
-                .notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null));
+                .notifyNetworkTested(eq(VALIDATION_RESULT_VALID), eq(null));
 
         // Change configuration back to working again, but make private DNS not work.
         // Expect validation to fail.
         reset(mCallbacks);
         mFakeDns.setNonBypassPrivateDnsWorking(false);
-        wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google", new InetAddress[0]));
-        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
-                .notifyNetworkTested(eq(NETWORK_TEST_RESULT_INVALID), eq(null));
+        wnm.notifyPrivateDnsSettingsChanged(new PrivateDnsConfig("dns.google",
+                new InetAddress[0]));
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeastOnce())
+                .notifyNetworkTested(
+                        eq(NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS),
+                        eq(null));
 
         // Make private DNS work again. Expect validation to succeed.
         reset(mCallbacks);
         mFakeDns.setNonBypassPrivateDnsWorking(true);
         wnm.forceReevaluation(Process.myUid());
-        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
-                .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), eq(null));
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).atLeastOnce())
+                .notifyNetworkTested(
+                        eq(VALIDATION_RESULT_VALID | NETWORK_VALIDATION_PROBE_PRIVDNS), eq(null));
     }
 
     @Test
@@ -834,12 +860,14 @@
     public void testIgnoreHttpsProbe() throws Exception {
         setSslException(mHttpsConnection);
         setStatus(mHttpConnection, 204);
+        // Expect to send HTTP, HTTPS, FALLBACK probe and evaluation result notifications to CS.
+        final NetworkMonitor nm = runNetworkTest(VALIDATION_RESULT_PARTIAL);
 
-        final NetworkMonitor nm = runNetworkTest(NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY);
-
+        reset(mCallbacks);
         nm.setAcceptPartialConnectivity();
-        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
-                .notifyNetworkTested(eq(NETWORK_TEST_RESULT_VALID), any());
+        // Expect to update evaluation result notifications to CS.
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyNetworkTested(
+                eq(VALIDATION_RESULT_PARTIAL | NETWORK_VALIDATION_RESULT_VALID), eq(null));
     }
 
     @Test
@@ -847,12 +875,13 @@
         setStatus(mHttpsConnection, 500);
         setStatus(mHttpConnection, 204);
         setStatus(mFallbackConnection, 500);
-        runPartialConnectivityNetworkTest();
+        runPartialConnectivityNetworkTest(VALIDATION_RESULT_PARTIAL);
 
+        reset(mCallbacks);
         setStatus(mHttpsConnection, 500);
         setStatus(mHttpConnection, 500);
         setStatus(mFallbackConnection, 204);
-        runPartialConnectivityNetworkTest();
+        runPartialConnectivityNetworkTest(VALIDATION_RESULT_FALLBACK_PARTIAL);
     }
 
     private void assertIpAddressArrayEquals(String[] expected, InetAddress[] actual) {
@@ -896,6 +925,86 @@
         }
     }
 
+    @Test
+    public void testNotifyNetwork_WithforceReevaluation() throws Exception {
+        final NetworkMonitor nm = runValidatedNetworkTest();
+        // Verify forceReevalution will not reset the validation result but only probe result until
+        // getting the validation result.
+        reset(mCallbacks);
+        setSslException(mHttpsConnection);
+        setStatus(mHttpConnection, 500);
+        setStatus(mFallbackConnection, 204);
+        nm.forceReevaluation(Process.myUid());
+        final ArgumentCaptor<Integer> intCaptor = ArgumentCaptor.forClass(Integer.class);
+        // Expect to send HTTP, HTTPs, FALLBACK and evaluation results.
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(4))
+            .notifyNetworkTested(intCaptor.capture(), any());
+        List<Integer> intArgs = intCaptor.getAllValues();
+
+        // None of these exact values can be known in advance except for intArgs.get(0) because the
+        // HTTP and HTTPS probes race and the order in which they complete is non-deterministic.
+        // Thus, check only exact value for intArgs.get(0) and only check the validation result for
+        // the rest ones.
+        assertEquals(Integer.valueOf(NETWORK_VALIDATION_PROBE_DNS
+                | NETWORK_VALIDATION_PROBE_FALLBACK | NETWORK_VALIDATION_RESULT_VALID),
+                intArgs.get(0));
+        assertTrue((intArgs.get(1) & NETWORK_VALIDATION_RESULT_VALID) != 0);
+        assertTrue((intArgs.get(2) & NETWORK_VALIDATION_RESULT_VALID) != 0);
+        assertTrue((intArgs.get(3) & NETWORK_VALIDATION_RESULT_PARTIAL) != 0);
+        assertTrue((intArgs.get(3) & NETWORK_VALIDATION_RESULT_VALID) == 0);
+    }
+
+    @Test
+    public void testEvaluationState_clearProbeResults() throws Exception {
+        final NetworkMonitor nm = runValidatedNetworkTest();
+        nm.getEvaluationState().clearProbeResults();
+        // Verify probe results are all reset and only evaluation result left.
+        assertEquals(NETWORK_VALIDATION_RESULT_VALID,
+                nm.getEvaluationState().getNetworkTestResult());
+    }
+
+    @Test
+    public void testEvaluationState_reportProbeResult() throws Exception {
+        final NetworkMonitor nm = runValidatedNetworkTest();
+
+        reset(mCallbacks);
+
+        nm.reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, CaptivePortalProbeResult.SUCCESS);
+        // Verify result should be appended and notifyNetworkTested callback is triggered once.
+        assertEquals(nm.getEvaluationState().getNetworkTestResult(),
+                VALIDATION_RESULT_VALID | NETWORK_VALIDATION_PROBE_HTTP);
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyNetworkTested(
+                eq(VALIDATION_RESULT_VALID | NETWORK_VALIDATION_PROBE_HTTP), any());
+
+        nm.reportHttpProbeResult(NETWORK_VALIDATION_PROBE_HTTP, CaptivePortalProbeResult.FAILED);
+        // Verify DNS probe result should not be cleared.
+        assertTrue((nm.getEvaluationState().getNetworkTestResult() & NETWORK_VALIDATION_PROBE_DNS)
+                == NETWORK_VALIDATION_PROBE_DNS);
+    }
+
+    @Test
+    public void testEvaluationState_reportEvaluationResult() throws Exception {
+        final NetworkMonitor nm = runValidatedNetworkTest();
+
+        nm.getEvaluationState().reportEvaluationResult(NETWORK_VALIDATION_RESULT_PARTIAL,
+                null /* redirectUrl */);
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyNetworkTested(
+                eq(NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS
+                | NETWORK_VALIDATION_RESULT_PARTIAL), eq(null));
+
+        nm.getEvaluationState().reportEvaluationResult(
+                NETWORK_VALIDATION_RESULT_VALID | NETWORK_VALIDATION_RESULT_PARTIAL,
+                null /* redirectUrl */);
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyNetworkTested(
+                eq(VALIDATION_RESULT_VALID | NETWORK_VALIDATION_RESULT_PARTIAL), eq(null));
+
+        nm.getEvaluationState().reportEvaluationResult(VALIDATION_RESULT_INVALID,
+                TEST_REDIRECT_URL);
+        verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)).notifyNetworkTested(
+                eq(NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS),
+                eq(TEST_REDIRECT_URL));
+    }
+
     private void makeDnsTimeoutEvent(WrappedNetworkMonitor wrappedMonitor, int count) {
         for (int i = 0; i < count; i++) {
             wrappedMonitor.getDnsStallDetector().accumulateConsecutiveDnsTimeoutCount(
@@ -954,39 +1063,64 @@
                 eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt())).thenReturn(mode);
     }
 
-    private void runPortalNetworkTest() {
-        runNetworkTest(NETWORK_TEST_RESULT_INVALID);
+    private void runPortalNetworkTest(int result) {
+        // The network test event will be triggered twice with the same result. Expect to capture
+        // the second one with direct url.
+        runPortalNetworkTest(result,
+                (VerificationWithTimeout) timeout(HANDLER_TIMEOUT_MS).times(2));
+    }
+
+    private void runPortalNetworkTest(int result, VerificationWithTimeout mode) {
+        runNetworkTest(result, mode);
         assertEquals(1, mRegisteredReceivers.size());
         assertNotNull(mNetworkTestedRedirectUrlCaptor.getValue());
     }
 
     private void runNotPortalNetworkTest() {
-        runNetworkTest(NETWORK_TEST_RESULT_VALID);
+        runNetworkTest(VALIDATION_RESULT_VALID);
+        assertEquals(0, mRegisteredReceivers.size());
+        assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
+    }
+
+    private void runNoValidationNetworkTest() {
+        runNetworkTest(NETWORK_VALIDATION_RESULT_VALID);
         assertEquals(0, mRegisteredReceivers.size());
         assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
     }
 
     private void runFailedNetworkTest() {
-        runNetworkTest(NETWORK_TEST_RESULT_INVALID);
+        runNetworkTest(VALIDATION_RESULT_INVALID);
         assertEquals(0, mRegisteredReceivers.size());
         assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
     }
 
-    private void runPartialConnectivityNetworkTest() {
-        runNetworkTest(NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY);
+    private void runPartialConnectivityNetworkTest(int result) {
+        runNetworkTest(result);
         assertEquals(0, mRegisteredReceivers.size());
         assertNull(mNetworkTestedRedirectUrlCaptor.getValue());
     }
 
+    private NetworkMonitor runValidatedNetworkTest() throws Exception {
+        setStatus(mHttpsConnection, 204);
+        setStatus(mHttpConnection, 204);
+        // Expect to send HTTPs and evaluation results.
+        return runNetworkTest(VALIDATION_RESULT_VALID);
+    }
+
     private NetworkMonitor runNetworkTest(int testResult) {
-        return runNetworkTest(METERED_CAPABILITIES, testResult);
+        return runNetworkTest(METERED_CAPABILITIES, testResult, getGeneralVerification());
     }
 
-    private NetworkMonitor runNetworkTest(NetworkCapabilities nc, int testResult) {
+    private NetworkMonitor runNetworkTest(int testResult, VerificationWithTimeout mode) {
+        return runNetworkTest(METERED_CAPABILITIES, testResult, mode);
+    }
+
+    private NetworkMonitor runNetworkTest(NetworkCapabilities nc, int testResult,
+            VerificationWithTimeout mode) {
         final NetworkMonitor monitor = makeMonitor(nc);
         monitor.notifyNetworkConnected(TEST_LINK_PROPERTIES, nc);
         try {
-            verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1))
+            verify(mCallbacks, mode)
                     .notifyNetworkTested(eq(testResult), mNetworkTestedRedirectUrlCaptor.capture());
         } catch (RemoteException e) {
             fail("Unexpected exception: " + e);
@@ -1018,5 +1152,10 @@
             stats.addDnsEvent(RETURN_CODE_DNS_TIMEOUT, 123456789 /* timeMs */);
         }
     }
+
+    private VerificationWithTimeout getGeneralVerification() {
+        return (VerificationWithTimeout) timeout(HANDLER_TIMEOUT_MS).atLeastOnce();
+    }
+
 }
 
diff --git a/packages/NetworkStack/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java b/packages/NetworkStack/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
index 87346e5..64fe3a6 100644
--- a/packages/NetworkStack/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
+++ b/packages/NetworkStack/tests/unit/src/com/android/server/connectivity/ipmemorystore/IpMemoryStoreServiceTest.java
@@ -62,6 +62,7 @@
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
@@ -77,7 +78,11 @@
     private static final int DEFAULT_TIMEOUT_MS = 5000;
     private static final int LONG_TIMEOUT_MS = 30000;
     private static final int FAKE_KEY_COUNT = 20;
+    private static final long LEASE_EXPIRY_NULL = -1L;
+    private static final int MTU_NULL = -1;
     private static final String[] FAKE_KEYS;
+    private static final byte[] TEST_BLOB_DATA = new byte[] { -3, 6, 8, -9, 12,
+            -128, 0, 89, 112, 91, -34 };
     static {
         FAKE_KEYS = new String[FAKE_KEY_COUNT];
         for (int i = 0; i < FAKE_KEYS.length; ++i) {
@@ -124,6 +129,29 @@
         mDbFile.delete();
     }
 
+    /** Helper method to build test network attributes */
+    private static NetworkAttributes.Builder buildTestNetworkAttributes(
+            final Inet4Address ipAddress, final long expiry, final String hint,
+            final List<InetAddress> dnsServers, final int mtu) {
+        final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
+        if (null != ipAddress) {
+            na.setAssignedV4Address(ipAddress);
+        }
+        if (LEASE_EXPIRY_NULL != expiry) {
+            na.setAssignedV4AddressExpiry(expiry);
+        }
+        if (null != hint) {
+            na.setGroupHint(hint);
+        }
+        if (null != dnsServers) {
+            na.setDnsAddresses(dnsServers);
+        }
+        if (MTU_NULL != mtu) {
+            na.setMtu(mtu);
+        }
+        return na;
+    }
+
     /** Helper method to make a vanilla IOnStatusListener */
     private IOnStatusListener onStatus(Consumer<Status> functor) {
         return new IOnStatusListener() {
@@ -265,7 +293,7 @@
         }
     }
 
-    // Helper methods to factorize more boilerplate
+    // Helper method to store network attributes to database
     private void storeAttributes(final String l2Key, final NetworkAttributes na) {
         storeAttributes("Did not complete storing attributes", l2Key, na);
     }
@@ -278,15 +306,28 @@
                 })));
     }
 
+    // Helper method to store blob data to database
+    private void storeBlobOrFail(final String l2Key, final Blob b, final byte[] data) {
+        storeBlobOrFail("Did not complete storing private data", l2Key, b, data);
+    }
+    private void storeBlobOrFail(final String timeoutMessage, final String l2Key, final Blob b,
+            final byte[] data) {
+        b.data = data;
+        doLatched(timeoutMessage, latch -> mService.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME,
+                b, onStatus(status -> {
+                    assertTrue("Store status not successful : " + status.resultCode,
+                            status.isSuccess());
+                    latch.countDown();
+                })));
+    }
+
     /** Insert large data that db size will be over threshold for maintenance test usage. */
     private void insertFakeDataAndOverThreshold() {
         try {
-            final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
-            na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
-            na.setGroupHint("hint1");
-            na.setMtu(219);
-            na.setDnsAddresses(Arrays.asList(Inet6Address.getByName("0A1C:2E40:480A::1CA6")));
-            final byte[] data = new byte[]{-3, 6, 8, -9, 12, -128, 0, 89, 112, 91, -34};
+            final NetworkAttributes.Builder na = buildTestNetworkAttributes(
+                    (Inet4Address) Inet4Address.getByName("1.2.3.4"), LEASE_EXPIRY_NULL,
+                    "hint1", Arrays.asList(Inet6Address.getByName("0A1C:2E40:480A::1CA6")),
+                    219);
             final long time = System.currentTimeMillis() - 1;
             for (int i = 0; i < 1000; i++) {
                 int errorCode = IpMemoryStoreDatabase.storeNetworkAttributes(
@@ -298,7 +339,8 @@
                 assertEquals(errorCode, Status.SUCCESS);
 
                 errorCode = IpMemoryStoreDatabase.storeBlob(
-                        mService.mDb, "fakeKey" + i, TEST_CLIENT_ID, TEST_DATA_NAME, data);
+                        mService.mDb, "fakeKey" + i, TEST_CLIENT_ID, TEST_DATA_NAME,
+                        TEST_BLOB_DATA);
                 assertEquals(errorCode, Status.SUCCESS);
             }
 
@@ -320,12 +362,10 @@
 
     @Test
     public void testNetworkAttributes() throws UnknownHostException {
-        final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
-        na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
-        na.setAssignedV4AddressExpiry(System.currentTimeMillis() + 7_200_000);
-        na.setGroupHint("hint1");
-        na.setMtu(219);
         final String l2Key = FAKE_KEYS[0];
+        final NetworkAttributes.Builder na = buildTestNetworkAttributes(
+                (Inet4Address) Inet4Address.getByName("1.2.3.4"),
+                System.currentTimeMillis() + 7_200_000, "hint1", null, 219);
         NetworkAttributes attributes = na.build();
         storeAttributes(l2Key, attributes);
 
@@ -420,16 +460,9 @@
 
     @Test
     public void testPrivateData() {
-        final Blob b = new Blob();
-        b.data = new byte[] { -3, 6, 8, -9, 12, -128, 0, 89, 112, 91, -34 };
         final String l2Key = FAKE_KEYS[0];
-        doLatched("Did not complete storing private data", latch ->
-                mService.storeBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, b,
-                        onStatus(status -> {
-                            assertTrue("Store status not successful : " + status.resultCode,
-                                    status.isSuccess());
-                            latch.countDown();
-                        })));
+        final Blob b = new Blob();
+        storeBlobOrFail(l2Key, b, TEST_BLOB_DATA);
 
         doLatched("Did not complete retrieving private data", latch ->
                 mService.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, onBlobRetrieved(
@@ -564,11 +597,10 @@
 
     @Test
     public void testIsSameNetwork() throws UnknownHostException {
-        final NetworkAttributes.Builder na = new NetworkAttributes.Builder();
-        na.setAssignedV4Address((Inet4Address) Inet4Address.getByName("1.2.3.4"));
-        na.setGroupHint("hint1");
-        na.setMtu(219);
-        na.setDnsAddresses(Arrays.asList(Inet6Address.getByName("0A1C:2E40:480A::1CA6")));
+        final NetworkAttributes.Builder na = buildTestNetworkAttributes(
+                (Inet4Address) Inet4Address.getByName("1.2.3.4"), LEASE_EXPIRY_NULL,
+                "hint1", Arrays.asList(Inet6Address.getByName("0A1C:2E40:480A::1CA6")),
+                219);
 
         storeAttributes(FAKE_KEYS[0], na.build());
         // 0 and 1 have identical attributes
@@ -601,7 +633,6 @@
                 })));
     }
 
-
     @Test
     public void testFullMaintenance() {
         insertFakeDataAndOverThreshold();
@@ -660,4 +691,66 @@
         // still be over the threshold.
         assertTrue(mService.isDbSizeOverThreshold());
     }
+
+    @Test
+    public void testFactoryReset() throws UnknownHostException {
+        final String l2Key = FAKE_KEYS[0];
+
+        // store network attributes
+        final NetworkAttributes.Builder na = buildTestNetworkAttributes(
+                (Inet4Address) Inet4Address.getByName("1.2.3.4"),
+                System.currentTimeMillis() + 7_200_000, "hint1", null, 219);
+        storeAttributes(l2Key, na.build());
+
+        // store private data blob
+        final Blob b = new Blob();
+        storeBlobOrFail(l2Key, b, TEST_BLOB_DATA);
+
+        // wipe all data in Database
+        mService.factoryReset();
+
+        // retrieved network attributes should be null
+        doLatched("Did not complete retrieving attributes", latch ->
+                mService.retrieveNetworkAttributes(l2Key, onNetworkAttributesRetrieved(
+                        (status, key, attr) -> {
+                            assertTrue("Retrieve network attributes not successful : "
+                                    + status.resultCode, status.isSuccess());
+                            assertEquals(l2Key, key);
+                            assertNull(attr);
+                            latch.countDown();
+                        })));
+
+        // retrieved private data blob should be null
+        doLatched("Did not complete retrieving private data", latch ->
+                mService.retrieveBlob(l2Key, TEST_CLIENT_ID, TEST_DATA_NAME, onBlobRetrieved(
+                        (status, key, name, data) -> {
+                            assertTrue("Retrieve blob status not successful : " + status.resultCode,
+                                    status.isSuccess());
+                            assertEquals(l2Key, key);
+                            assertEquals(name, TEST_DATA_NAME);
+                            assertNull(data);
+                            latch.countDown();
+                        })));
+    }
+
+    public void testTasksAreSerial() {
+        final long sleepTimeMs = 1000;
+        final long startTime = System.currentTimeMillis();
+        mService.retrieveNetworkAttributes("somekey", onNetworkAttributesRetrieved(
+                (status, key, attr) -> {
+                    assertTrue("Unexpected status : " + status.resultCode, status.isSuccess());
+                    try {
+                        Thread.sleep(sleepTimeMs);
+                    } catch (InterruptedException e) {
+                        fail("InterruptedException");
+                    }
+                }));
+        doLatched("Serial tasks timing out", latch ->
+                mService.retrieveNetworkAttributes("somekey", onNetworkAttributesRetrieved(
+                        (status, key, attr) -> {
+                            assertTrue("Unexpected status : " + status.resultCode,
+                                    status.isSuccess());
+                            assertTrue(System.currentTimeMillis() >= startTime + sleepTimeMs);
+                        })), DEFAULT_TIMEOUT_MS);
+    }
 }
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 99c48cb..151ea6a 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -12,6 +12,7 @@
 dupin@google.com
 ethibodeau@google.com
 evanlaird@google.com
+hyunyoungs@google.com
 jmonk@google.com
 jaggies@google.com
 jjaggi@google.com
@@ -23,6 +24,8 @@
 lynhan@google.com
 madym@google.com
 mankoff@google.com
+mrcasey@google.com
+mrenouf@google.com
 nbenbernou@google.com
 nesciosquid@google.com
 ngmatthew@google.com
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index b3b5e45..4b2d481 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -25,8 +25,8 @@
 import static android.net.ConnectivityManager.TYPE_VPN;
 import static android.net.ConnectivityManager.getNetworkTypeName;
 import static android.net.ConnectivityManager.isNetworkTypeValid;
-import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
-import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
@@ -77,6 +77,7 @@
 import android.net.ISocketKeepaliveCallback;
 import android.net.ITetheringEventCallback;
 import android.net.InetAddresses;
+import android.net.IpMemoryStore;
 import android.net.IpPrefix;
 import android.net.LinkProperties;
 import android.net.LinkProperties.CompareResult;
@@ -2603,21 +2604,12 @@
                     final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
                     if (nai == null) break;
 
-                    final boolean partialConnectivity =
-                            (msg.arg1 == NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY)
-                                    || (nai.networkMisc.acceptPartialConnectivity
-                                            && nai.partialConnectivity);
-                    // Once a network is determined to have partial connectivity, it cannot
-                    // go back to full connectivity without a disconnect. This is because
-                    // NetworkMonitor can only communicate either PARTIAL_CONNECTIVITY or VALID,
-                    // but not both.
-                    // TODO: Provide multi-testResult to improve the communication between
-                    // ConnectivityService and NetworkMonitor, so that ConnectivityService could
-                    // know the real status of network.
+                    final boolean wasPartial = nai.partialConnectivity;
+                    nai.partialConnectivity = ((msg.arg1 & NETWORK_VALIDATION_RESULT_PARTIAL) != 0);
                     final boolean partialConnectivityChanged =
-                            (partialConnectivity && !nai.partialConnectivity);
+                            (wasPartial != nai.partialConnectivity);
 
-                    final boolean valid = (msg.arg1 == NETWORK_TEST_RESULT_VALID);
+                    final boolean valid = ((msg.arg1 & NETWORK_VALIDATION_RESULT_VALID) != 0);
                     final boolean wasValidated = nai.lastValidated;
                     final boolean wasDefault = isDefaultNetwork(nai);
                     if (nai.everCaptivePortalDetected && !nai.captivePortalLoginNotified
@@ -2647,21 +2639,23 @@
                         if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
                         if (valid) {
                             handleFreshlyValidatedNetwork(nai);
-                            // Clear NO_INTERNET and LOST_INTERNET notifications if network becomes
-                            // valid.
+                            // Clear NO_INTERNET, PARTIAL_CONNECTIVITY and LOST_INTERNET
+                            // notifications if network becomes valid.
                             mNotifier.clearNotification(nai.network.netId,
                                     NotificationType.NO_INTERNET);
                             mNotifier.clearNotification(nai.network.netId,
                                     NotificationType.LOST_INTERNET);
+                            mNotifier.clearNotification(nai.network.netId,
+                                    NotificationType.PARTIAL_CONNECTIVITY);
                         }
                     } else if (partialConnectivityChanged) {
-                        nai.partialConnectivity = partialConnectivity;
                         updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities);
                     }
                     updateInetCondition(nai);
                     // Let the NetworkAgent know the state of its network
                     Bundle redirectUrlBundle = new Bundle();
                     redirectUrlBundle.putString(NetworkAgent.REDIRECT_URL_KEY, redirectUrl);
+                    // TODO: Evaluate to update partial connectivity to status to NetworkAgent.
                     nai.asyncChannel.sendMessage(
                             NetworkAgent.CMD_REPORT_NETWORK_STATUS,
                             (valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK),
@@ -3441,6 +3435,9 @@
             // Inform NetworkMonitor that partial connectivity is acceptable. This will likely
             // result in a partial connectivity result which will be processed by
             // maybeHandleNetworkMonitorMessage.
+            //
+            // TODO: NetworkMonitor does not refer to the "never ask again" bit. The bit is stored
+            // per network. Therefore, NetworkMonitor may still do https probe.
             try {
                 nai.networkMonitor().setAcceptPartialConnectivity();
             } catch (RemoteException e) {
@@ -6897,6 +6894,9 @@
 
         final int userId = UserHandle.getCallingUserId();
 
+        final IpMemoryStore ipMemoryStore = IpMemoryStore.getMemoryStore(mContext);
+        ipMemoryStore.factoryReset();
+
         // Turn airplane mode off
         setAirplaneMode(false);
 
diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
index d05369e9..fbe2589 100644
--- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java
+++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
@@ -199,15 +199,13 @@
             ArraySet<String> perms = systemPermission.valueAt(i);
             int uid = systemPermission.keyAt(i);
             int netdPermission = 0;
-            // Get the uids of native services that have UPDATE_DEVICE_STATS permission.
+            // Get the uids of native services that have UPDATE_DEVICE_STATS or INTERNET permission.
             if (perms != null) {
                 netdPermission |= perms.contains(UPDATE_DEVICE_STATS)
                         ? INetd.PERMISSION_UPDATE_DEVICE_STATS : 0;
+                netdPermission |= perms.contains(INTERNET)
+                        ? INetd.PERMISSION_INTERNET : 0;
             }
-            // For internet permission, the native services have their own selinux domains and
-            // sepolicy will control the socket creation during run time. netd cannot block the
-            // socket creation based on the permission information here.
-            netdPermission |= INetd.PERMISSION_INTERNET;
             netdPermsUids.put(uid, netdPermsUids.get(uid) | netdPermission);
         }
         log("Users: " + mUsers.size() + ", Apps: " + mApps.size());
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 1275302..f5de2ad 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -1850,10 +1850,11 @@
         if (!profile.searchDomains.isEmpty()) {
             config.searchDomains = Arrays.asList(profile.searchDomains.split(" +"));
         }
-        startLegacyVpn(config, racoon, mtpd);
+        startLegacyVpn(config, racoon, mtpd, profile);
     }
 
-    private synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd) {
+    private synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd,
+            VpnProfile profile) {
         stopLegacyVpnPrivileged();
 
         // Prepare for the new request.
@@ -1861,7 +1862,7 @@
         updateState(DetailedState.CONNECTING, "startLegacyVpn");
 
         // Start a new LegacyVpnRunner and we are done!
-        mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd);
+        mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd, profile);
         mLegacyVpnRunner.start();
     }
 
@@ -1927,6 +1928,7 @@
         private final String mOuterInterface;
         private final AtomicInteger mOuterConnection =
                 new AtomicInteger(ConnectivityManager.TYPE_NONE);
+        private final VpnProfile mProfile;
 
         private long mBringupStartTime = -1;
 
@@ -1953,7 +1955,7 @@
             }
         };
 
-        public LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd) {
+        LegacyVpnRunner(VpnConfig config, String[] racoon, String[] mtpd, VpnProfile profile) {
             super(TAG);
             mConfig = config;
             mDaemons = new String[] {"racoon", "mtpd"};
@@ -1969,6 +1971,8 @@
             // registering
             mOuterInterface = mConfig.interfaze;
 
+            mProfile = profile;
+
             if (!TextUtils.isEmpty(mOuterInterface)) {
                 final ConnectivityManager cm = ConnectivityManager.from(mContext);
                 for (Network network : cm.getAllNetworks()) {
@@ -2181,7 +2185,7 @@
                 }
 
                 // Add a throw route for the VPN server endpoint, if one was specified.
-                String endpoint = parameters[5];
+                String endpoint = parameters[5].isEmpty() ? mProfile.server : parameters[5];
                 if (!endpoint.isEmpty()) {
                     try {
                         InetAddress addr = InetAddress.parseNumericAddress(endpoint);
diff --git a/services/net/java/android/net/IpMemoryStoreClient.java b/services/net/java/android/net/IpMemoryStoreClient.java
index 3d56202..014b528 100644
--- a/services/net/java/android/net/IpMemoryStoreClient.java
+++ b/services/net/java/android/net/IpMemoryStoreClient.java
@@ -212,4 +212,16 @@
                             null, null, null));
         }
     }
+
+    /**
+     * Wipe the data in the database upon network factory reset.
+     */
+    public void factoryReset() {
+        try {
+            runWhenServiceReady(service -> ignoringRemoteException(
+                    () -> service.factoryReset()));
+        } catch (ExecutionException m) {
+            Log.e(TAG, "Error executing factory reset", m);
+        }
+    }
 }
diff --git a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
index 6a3469c5..e615428 100644
--- a/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
+++ b/services/usb/java/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -177,15 +177,11 @@
              * Audio Class Specific
              */
             case UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE:
-                if (mDeviceDescriptor.getDevClass() == UsbDescriptor.CLASSID_AUDIO) {
-                    descriptor = UsbACInterface.allocDescriptor(this, stream, length, type);
-                }
+                descriptor = UsbACInterface.allocDescriptor(this, stream, length, type);
                 break;
 
             case UsbDescriptor.DESCRIPTORTYPE_AUDIO_ENDPOINT:
-                if (mDeviceDescriptor.getDevClass() == UsbDescriptor.CLASSID_AUDIO) {
-                    descriptor = UsbACEndpoint.allocDescriptor(this, length, type);
-                }
+                descriptor = UsbACEndpoint.allocDescriptor(this, length, type);
                 break;
 
             default:
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 5731646..e5b111e 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -2209,7 +2209,7 @@
     @UnsupportedAppUsage
     public String getNetworkOperatorForPhone(int phoneId) {
         return getTelephonyProperty(phoneId, TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, "");
-     }
+    }
 
 
     /**
@@ -2409,24 +2409,15 @@
     public @interface NetworkType{}
 
     /**
+     * Return the current data network type.
+     *
+     * @deprecated use {@link #getDataNetworkType()}
      * @return the NETWORK_TYPE_xxxx for current data connection.
      */
+    @Deprecated
+    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
     public @NetworkType int getNetworkType() {
-       try {
-           ITelephony telephony = getITelephony();
-           if (telephony != null) {
-               return telephony.getNetworkType();
-            } else {
-                // This can happen when the ITelephony interface is not up yet.
-                return NETWORK_TYPE_UNKNOWN;
-            }
-        } catch(RemoteException ex) {
-            // This shouldn't happen in the normal case
-            return NETWORK_TYPE_UNKNOWN;
-        } catch (NullPointerException ex) {
-            // This could happen before phone restarts due to crashing
-            return NETWORK_TYPE_UNKNOWN;
-        }
+        return getNetworkType(getSubId(SubscriptionManager.getDefaultDataSubscriptionId()));
     }
 
     /**
@@ -2457,7 +2448,7 @@
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
     public int getNetworkType(int subId) {
         try {
             ITelephony telephony = getITelephony();
@@ -8245,9 +8236,8 @@
             ITelephony telephony = getITelephony();
             if (telephony != null)
                 retVal = telephony.isUserDataEnabled(subId);
-        } catch (RemoteException e) {
+        } catch (RemoteException | NullPointerException e) {
             Log.e(TAG, "Error calling ITelephony#isUserDataEnabled", e);
-        } catch (NullPointerException e) {
         }
         return retVal;
     }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 6b927bb..d71ad09 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -458,13 +458,6 @@
     void sendDialerSpecialCode(String callingPackageName, String inputCode);
 
     /**
-     * Returns the network type for data transmission
-     * Legacy call, permission-free
-     */
-    @UnsupportedAppUsage
-    int getNetworkType();
-
-    /**
      * Returns the network type of a subId.
      * @param subId user preferred subId.
      * @param callingPackage package making the call.
diff --git a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
index 7574a6e..73d49dd 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyPermissions.java
@@ -75,6 +75,16 @@
                 callingPackage, message);
     }
 
+    /** Identical to checkCallingOrSelfReadPhoneState but never throws SecurityException */
+    public static boolean checkCallingOrSelfReadPhoneStateNoThrow(
+            Context context, int subId, String callingPackage, String message) {
+        try {
+            return checkCallingOrSelfReadPhoneState(context, subId, callingPackage, message);
+        } catch (SecurityException se) {
+            return false;
+        }
+    }
+
     /**
      * Check whether the app with the given pid/uid can read phone state.
      *
diff --git a/tests/net/java/android/net/IpMemoryStoreTest.java b/tests/net/java/android/net/IpMemoryStoreTest.java
index 6e69b34..b81ca36 100644
--- a/tests/net/java/android/net/IpMemoryStoreTest.java
+++ b/tests/net/java/android/net/IpMemoryStoreTest.java
@@ -321,4 +321,11 @@
                 eq(TEST_OTHER_DATA_NAME), any());
         assertEquals(TEST_NETWORK_ATTRIBUTES, new NetworkAttributes(mNapCaptor.getValue()));
     }
+
+    @Test
+    public void testFactoryReset() throws RemoteException {
+        startIpMemoryStore(true /* supplyService */);
+        mStore.factoryReset();
+        verify(mMockService, times(1)).factoryReset();
+    }
 }
diff --git a/tests/net/java/android/net/util/DnsUtilsTest.java b/tests/net/java/android/net/util/DnsUtilsTest.java
new file mode 100644
index 0000000..42e340b
--- /dev/null
+++ b/tests/net/java/android/net/util/DnsUtilsTest.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2019 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.util;
+
+import static android.net.util.DnsUtils.IPV6_ADDR_SCOPE_GLOBAL;
+import static android.net.util.DnsUtils.IPV6_ADDR_SCOPE_LINKLOCAL;
+import static android.net.util.DnsUtils.IPV6_ADDR_SCOPE_SITELOCAL;
+
+import static org.junit.Assert.assertEquals;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.InetAddresses;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.InetAddress;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DnsUtilsTest {
+    private InetAddress stringToAddress(@NonNull String addr) {
+        return InetAddresses.parseNumericAddress(addr);
+    }
+
+    private DnsUtils.SortableAddress makeSortableAddress(@NonNull String addr) {
+        return makeSortableAddress(addr, null);
+    }
+
+    private DnsUtils.SortableAddress makeSortableAddress(@NonNull String addr,
+            @Nullable String srcAddr) {
+        return new DnsUtils.SortableAddress(stringToAddress(addr),
+                srcAddr != null ? stringToAddress(srcAddr) : null);
+    }
+
+    @Test
+    public void testRfc6724Comparator() {
+        final List<DnsUtils.SortableAddress> test = Arrays.asList(
+                makeSortableAddress("216.58.200.36"),             // Ipv4
+                makeSortableAddress("2404:6800:4008:801::2004"),  // global
+                makeSortableAddress("::1"),                       // loop back
+                makeSortableAddress("fe80::c46f:1cff:fe04:39b4"), // link local
+                makeSortableAddress("::ffff:192.168.95.3"),       // IPv4-mapped IPv6
+                makeSortableAddress("2001::47c1"),                // teredo tunneling
+                makeSortableAddress("::216.58.200.36"),           // IPv4-compatible
+                makeSortableAddress("3ffe::1234:5678"));          // 6bone
+
+        final List<InetAddress> expected = Arrays.asList(
+                stringToAddress("::1"),                       // loop back
+                stringToAddress("fe80::c46f:1cff:fe04:39b4"), // link local
+                stringToAddress("2404:6800:4008:801::2004"),  // global
+                stringToAddress("216.58.200.36"),             // Ipv4
+                stringToAddress("::ffff:192.168.95.3"),       // IPv4-mapped IPv6
+                stringToAddress("2001::47c1"),                // teredo tunneling
+                stringToAddress("::216.58.200.36"),            // IPv4-compatible
+                stringToAddress("3ffe::1234:5678"));          // 6bone
+
+        Collections.sort(test, new DnsUtils.Rfc6724Comparator());
+
+        for (int i = 0; i < test.size(); ++i) {
+            assertEquals(test.get(i).address, expected.get(i));
+        }
+
+        // TODO: add more combinations
+    }
+
+    @Test
+    public void testV4SortableAddress() {
+        // Test V4 address
+        DnsUtils.SortableAddress test = makeSortableAddress("216.58.200.36");
+        assertEquals(test.hasSrcAddr, 0);
+        assertEquals(test.prefixMatchLen, 0);
+        assertEquals(test.address, stringToAddress("216.58.200.36"));
+        assertEquals(test.labelMatch, 0);
+        assertEquals(test.scopeMatch, 0);
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+        assertEquals(test.label, 4);
+        assertEquals(test.precedence, 35);
+
+        // Test V4 loopback address with the same source address
+        test = makeSortableAddress("127.1.2.3", "127.1.2.3");
+        assertEquals(test.hasSrcAddr, 1);
+        assertEquals(test.prefixMatchLen, 0);
+        assertEquals(test.address, stringToAddress("127.1.2.3"));
+        assertEquals(test.labelMatch, 1);
+        assertEquals(test.scopeMatch, 1);
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL);
+        assertEquals(test.label, 4);
+        assertEquals(test.precedence, 35);
+    }
+
+    @Test
+    public void testV6SortableAddress() {
+        // Test global address
+        DnsUtils.SortableAddress test = makeSortableAddress("2404:6800:4008:801::2004");
+        assertEquals(test.address, stringToAddress("2404:6800:4008:801::2004"));
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+        assertEquals(test.label, 1);
+        assertEquals(test.precedence, 40);
+
+        // Test global address with global source address
+        test = makeSortableAddress("2404:6800:4008:801::2004",
+                "2401:fa00:fc:fd00:6d6c:7199:b8e7:41d6");
+        assertEquals(test.address, stringToAddress("2404:6800:4008:801::2004"));
+        assertEquals(test.hasSrcAddr, 1);
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+        assertEquals(test.labelMatch, 1);
+        assertEquals(test.scopeMatch, 1);
+        assertEquals(test.label, 1);
+        assertEquals(test.precedence, 40);
+        assertEquals(test.prefixMatchLen, 13);
+
+        // Test global address with linklocal source address
+        test = makeSortableAddress("2404:6800:4008:801::2004", "fe80::c46f:1cff:fe04:39b4");
+        assertEquals(test.hasSrcAddr, 1);
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+        assertEquals(test.labelMatch, 1);
+        assertEquals(test.scopeMatch, 0);
+        assertEquals(test.label, 1);
+        assertEquals(test.precedence, 40);
+        assertEquals(test.prefixMatchLen, 0);
+
+        // Test loopback address with the same source address
+        test = makeSortableAddress("::1", "::1");
+        assertEquals(test.hasSrcAddr, 1);
+        assertEquals(test.prefixMatchLen, 16 * 8);
+        assertEquals(test.labelMatch, 1);
+        assertEquals(test.scopeMatch, 1);
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL);
+        assertEquals(test.label, 0);
+        assertEquals(test.precedence, 50);
+
+        // Test linklocal address
+        test = makeSortableAddress("fe80::c46f:1cff:fe04:39b4");
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL);
+        assertEquals(test.label, 1);
+        assertEquals(test.precedence, 40);
+
+        // Test linklocal address
+        test = makeSortableAddress("fe80::");
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_LINKLOCAL);
+        assertEquals(test.label, 1);
+        assertEquals(test.precedence, 40);
+
+        // Test 6to4 address
+        test = makeSortableAddress("2002:c000:0204::");
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+        assertEquals(test.label, 2);
+        assertEquals(test.precedence, 30);
+
+        // Test unique local address
+        test = makeSortableAddress("fc00::c000:13ab");
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+        assertEquals(test.label, 13);
+        assertEquals(test.precedence, 3);
+
+        // Test teredo tunneling address
+        test = makeSortableAddress("2001::47c1");
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+        assertEquals(test.label, 5);
+        assertEquals(test.precedence, 5);
+
+        // Test IPv4-compatible addresses
+        test = makeSortableAddress("::216.58.200.36");
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+        assertEquals(test.label, 3);
+        assertEquals(test.precedence, 1);
+
+        // Test site-local address
+        test = makeSortableAddress("fec0::cafe:3ab2");
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_SITELOCAL);
+        assertEquals(test.label, 11);
+        assertEquals(test.precedence, 1);
+
+        // Test 6bone address
+        test = makeSortableAddress("3ffe::1234:5678");
+        assertEquals(test.scope, IPV6_ADDR_SCOPE_GLOBAL);
+        assertEquals(test.label, 12);
+        assertEquals(test.precedence, 1);
+    }
+}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 1f32bd4..cb774ba 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -30,9 +30,12 @@
 import static android.net.ConnectivityManager.TYPE_NONE;
 import static android.net.ConnectivityManager.TYPE_VPN;
 import static android.net.ConnectivityManager.TYPE_WIFI;
-import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_INVALID;
-import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
-import static android.net.INetworkMonitor.NETWORK_TEST_RESULT_VALID;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_DNS;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_FALLBACK;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTP;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_PROBE_HTTPS;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_PARTIAL;
+import static android.net.INetworkMonitor.NETWORK_VALIDATION_RESULT_VALID;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
@@ -443,6 +446,16 @@
     }
 
     private class MockNetworkAgent {
+        private static final int VALIDATION_RESULT_BASE = NETWORK_VALIDATION_PROBE_DNS
+                | NETWORK_VALIDATION_PROBE_HTTP
+                | NETWORK_VALIDATION_PROBE_HTTPS;
+        private static final int VALIDATION_RESULT_VALID = VALIDATION_RESULT_BASE
+                | NETWORK_VALIDATION_RESULT_VALID;
+        private static final int VALIDATION_RESULT_PARTIAL = VALIDATION_RESULT_BASE
+                | NETWORK_VALIDATION_PROBE_FALLBACK
+                | NETWORK_VALIDATION_RESULT_PARTIAL;
+        private static final int VALIDATION_RESULT_INVALID = 0;
+
         private final INetworkMonitor mNetworkMonitor;
         private final NetworkInfo mNetworkInfo;
         private final NetworkCapabilities mNetworkCapabilities;
@@ -460,17 +473,17 @@
         private String mRedirectUrl;
 
         private INetworkMonitorCallbacks mNmCallbacks;
-        private int mNmValidationResult = NETWORK_TEST_RESULT_INVALID;
+        private int mNmValidationResult = VALIDATION_RESULT_BASE;
         private String mNmValidationRedirectUrl = null;
         private boolean mNmProvNotificationRequested = false;
 
         void setNetworkValid() {
-            mNmValidationResult = NETWORK_TEST_RESULT_VALID;
+            mNmValidationResult = VALIDATION_RESULT_VALID;
             mNmValidationRedirectUrl = null;
         }
 
         void setNetworkInvalid() {
-            mNmValidationResult = NETWORK_TEST_RESULT_INVALID;
+            mNmValidationResult = VALIDATION_RESULT_INVALID;
             mNmValidationRedirectUrl = null;
         }
 
@@ -480,7 +493,12 @@
         }
 
         void setNetworkPartial() {
-            mNmValidationResult = NETWORK_TEST_RESULT_PARTIAL_CONNECTIVITY;
+            mNmValidationResult = VALIDATION_RESULT_PARTIAL;
+            mNmValidationRedirectUrl = null;
+        }
+
+        void setNetworkPartialValid() {
+            mNmValidationResult = VALIDATION_RESULT_PARTIAL | VALIDATION_RESULT_VALID;
             mNmValidationRedirectUrl = null;
         }
 
@@ -597,7 +615,7 @@
         private void onValidationRequested() {
             try {
                 if (mNmProvNotificationRequested
-                        && mNmValidationResult == NETWORK_TEST_RESULT_VALID) {
+                        && ((mNmValidationResult & NETWORK_VALIDATION_RESULT_VALID) != 0)) {
                     mNmCallbacks.hideProvisioningNotification();
                     mNmProvNotificationRequested = false;
                 }
@@ -2651,7 +2669,7 @@
 
         // With HTTPS probe disabled, NetworkMonitor should pass the network validation with http
         // probe.
-        mWiFiNetworkAgent.setNetworkValid();
+        mWiFiNetworkAgent.setNetworkPartialValid();
         // If the user chooses yes to use this partial connectivity wifi, switch the default
         // network to wifi and check if wifi becomes valid or not.
         mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), true /* accept */,
@@ -2749,6 +2767,55 @@
     }
 
     @Test
+    public void testCaptivePortalOnPartialConnectivity() throws RemoteException {
+        final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
+        final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build();
+        mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback);
+
+        final TestNetworkCallback validatedCallback = new TestNetworkCallback();
+        final NetworkRequest validatedRequest = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_VALIDATED).build();
+        mCm.registerNetworkCallback(validatedRequest, validatedCallback);
+
+        // Bring up a network with a captive portal.
+        // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        String redirectUrl = "http://android.com/path";
+        mWiFiNetworkAgent.connectWithCaptivePortal(redirectUrl);
+        captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+        assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), redirectUrl);
+
+        // Check that startCaptivePortalApp sends the expected command to NetworkMonitor.
+        mCm.startCaptivePortalApp(mWiFiNetworkAgent.getNetwork());
+        verify(mWiFiNetworkAgent.mNetworkMonitor, timeout(TIMEOUT_MS).times(1))
+                .launchCaptivePortalApp();
+
+        // Report that the captive portal is dismissed with partial connectivity, and check that
+        // callbacks are fired.
+        mWiFiNetworkAgent.setNetworkPartial();
+        mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
+        waitForIdle();
+        captivePortalCallback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY,
+                mWiFiNetworkAgent);
+
+        // Report partial connectivity is accepted.
+        mWiFiNetworkAgent.setNetworkPartialValid();
+        mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), true /* accept */,
+                false /* always */);
+        waitForIdle();
+        mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
+        captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+        validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
+        NetworkCapabilities nc =
+                validatedCallback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY,
+                mWiFiNetworkAgent);
+
+        mCm.unregisterNetworkCallback(captivePortalCallback);
+        mCm.unregisterNetworkCallback(validatedCallback);
+    }
+
+    @Test
     public void testCaptivePortal() {
         final TestNetworkCallback captivePortalCallback = new TestNetworkCallback();
         final NetworkRequest captivePortalRequest = new NetworkRequest.Builder()
@@ -3864,6 +3931,7 @@
             }
         }
 
+        testFactory.expectRemoveRequests(1);
         if (preUnregister) {
             mCm.unregisterNetworkCallback(networkCallback);
 
@@ -3873,7 +3941,6 @@
             testFactory.triggerUnfulfillable(requests.get(newRequestId));
         } else {
             // Simulate the factory releasing the request as unfulfillable and expect onUnavailable!
-            testFactory.expectRemoveRequests(1);
             testFactory.triggerUnfulfillable(requests.get(newRequestId));
 
             networkCallback.expectCallback(CallbackState.UNAVAILABLE, null);