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);