Better notification to user when DNS-over-TLS is broken
Provide a specifc notification to let users know that device
has no internet is because it really doesn't have internet access
or it's caused by private DNS resolution failed.
Bug: 113242081
Test: atest FrameworksNetTests
Change-Id: I710c88a4742f5fd56c39fc797d7fa3ad36dba553
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 3e325b7..e3259ff 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -57,6 +57,9 @@
private static final String TAG = "NetworkCapabilities";
private static final int INVALID_UID = -1;
+ // Set to true when private DNS is broken.
+ private boolean mPrivateDnsBroken;
+
/**
* @hide
*/
@@ -86,6 +89,7 @@
mUids = null;
mEstablishingVpnAppUid = INVALID_UID;
mSSID = null;
+ mPrivateDnsBroken = false;
}
/**
@@ -104,6 +108,7 @@
mEstablishingVpnAppUid = nc.mEstablishingVpnAppUid;
mUnwantedNetworkCapabilities = nc.mUnwantedNetworkCapabilities;
mSSID = nc.mSSID;
+ mPrivateDnsBroken = nc.mPrivateDnsBroken;
}
/**
@@ -557,6 +562,9 @@
}
if (mLinkUpBandwidthKbps != 0 || mLinkDownBandwidthKbps != 0) return "link bandwidth";
if (hasSignalStrength()) return "signalStrength";
+ if (isPrivateDnsBroken()) {
+ return "privateDnsBroken";
+ }
return null;
}
@@ -1443,7 +1451,8 @@
&& equalsSpecifier(that)
&& equalsTransportInfo(that)
&& equalsUids(that)
- && equalsSSID(that));
+ && equalsSSID(that)
+ && equalsPrivateDnsBroken(that));
}
@Override
@@ -1460,7 +1469,8 @@
+ (mSignalStrength * 29)
+ Objects.hashCode(mUids) * 31
+ Objects.hashCode(mSSID) * 37
- + Objects.hashCode(mTransportInfo) * 41;
+ + Objects.hashCode(mTransportInfo) * 41
+ + Objects.hashCode(mPrivateDnsBroken) * 43;
}
@Override
@@ -1479,6 +1489,7 @@
dest.writeInt(mSignalStrength);
dest.writeArraySet(mUids);
dest.writeString(mSSID);
+ dest.writeBoolean(mPrivateDnsBroken);
}
public static final @android.annotation.NonNull Creator<NetworkCapabilities> CREATOR =
@@ -1498,6 +1509,7 @@
netCap.mUids = (ArraySet<UidRange>) in.readArraySet(
null /* ClassLoader, null for default */);
netCap.mSSID = in.readString();
+ netCap.mPrivateDnsBroken = in.readBoolean();
return netCap;
}
@Override
@@ -1555,6 +1567,10 @@
sb.append(" SSID: ").append(mSSID);
}
+ if (mPrivateDnsBroken) {
+ sb.append(" Private DNS is broken");
+ }
+
sb.append("]");
return sb.toString();
}
@@ -1706,4 +1722,28 @@
public boolean isMetered() {
return !hasCapability(NET_CAPABILITY_NOT_METERED);
}
+
+ /**
+ * Check if private dns is broken.
+ *
+ * @return {@code true} if {@code mPrivateDnsBroken} is set when private DNS is broken.
+ * @hide
+ */
+ public boolean isPrivateDnsBroken() {
+ return mPrivateDnsBroken;
+ }
+
+ /**
+ * Set mPrivateDnsBroken to true when private dns is broken.
+ *
+ * @param broken the status of private DNS to be set.
+ * @hide
+ */
+ public void setPrivateDnsBroken(boolean broken) {
+ mPrivateDnsBroken = broken;
+ }
+
+ private boolean equalsPrivateDnsBroken(NetworkCapabilities nc) {
+ return mPrivateDnsBroken == nc.mPrivateDnsBroken;
+ }
}
diff --git a/core/java/android/net/NetworkMisc.java b/core/java/android/net/NetworkMisc.java
index 9ba3bd9..4ad52d5 100644
--- a/core/java/android/net/NetworkMisc.java
+++ b/core/java/android/net/NetworkMisc.java
@@ -77,6 +77,12 @@
*/
public boolean skip464xlat;
+ /**
+ * Set to true if the PRIVATE_DNS_BROKEN notification has shown for this network.
+ * Reset this bit when private DNS mode is changed from strict mode to opportunistic/off mode.
+ */
+ public boolean hasShownBroken;
+
public NetworkMisc() {
}
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 152b131..f423c5f 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3421,6 +3421,15 @@
<!-- A notification is shown when the user connects to a Wi-Fi network and the system detects that that network has no Internet access. This is the notification's message. -->
<string name="wifi_no_internet_detailed">Tap for options</string>
+ <!-- A notification is shown when the user connects to a mobile network without internet access. This is the notification's title. -->
+ <string name="mobile_no_internet">Mobile network has no internet access</string>
+
+ <!-- A notification is shown when the user connects to a non-mobile and non-wifi network without internet access. This is the notification's title. -->
+ <string name="other_networks_no_internet">Network has no internet access</string>
+
+ <!-- A notification is shown when connected network without internet due to private dns validation failed. This is the notification's message. [CHAR LIMIT=NONE] -->
+ <string name="private_dns_broken_detailed">Private DNS server cannot be accessed</string>
+
<!-- A notification is shown after the user logs in to a captive portal network, to indicate that the network should now have internet connectivity. This is the message of notification. [CHAR LIMIT=50] -->
<string name="captive_portal_logged_in_detailed">Connected</string>
<!-- A notification is shown when the user connects to a network that doesn't have access to some services (e.g. Push notifications may not work). This is the notification's title. [CHAR LIMIT=50] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0bd5e43..0e647fe 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -590,9 +590,12 @@
<java-symbol type="string" name="menu_space_shortcut_label" />
<java-symbol type="string" name="menu_shift_shortcut_label" />
<java-symbol type="string" name="menu_sym_shortcut_label" />
+ <java-symbol type="string" name="mobile_no_internet" />
<java-symbol type="string" name="notification_title" />
+ <java-symbol type="string" name="other_networks_no_internet" />
<java-symbol type="string" name="permission_request_notification_with_subtitle" />
<java-symbol type="string" name="prepend_shortcut_label" />
+ <java-symbol type="string" name="private_dns_broken_detailed" />
<java-symbol type="string" name="paste_as_plain_text" />
<java-symbol type="string" name="replace" />
<java-symbol type="string" name="undo" />
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 0e91839..bdfa217 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -127,6 +127,9 @@
<!-- Summary for Connected wifi network without internet -->
<string name="wifi_connected_no_internet">Connected, no internet</string>
+ <!-- Summary for connected network without internet due to private dns validation failed [CHAR LIMIT=NONE] -->
+ <string name="private_dns_broken">Private DNS server cannot be accessed</string>
+
<!-- Summary for connected wifi network with partial internet connectivity [CHAR LIMIT=50] -->
<string name="wifi_limited_connection">Limited connection</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index f16fb1c..9e71189 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -50,6 +50,7 @@
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
@@ -1569,7 +1570,13 @@
NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY)) {
return context.getString(R.string.wifi_limited_connection);
} else if (!nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
- return context.getString(R.string.wifi_connected_no_internet);
+ final String mode = Settings.Global.getString(context.getContentResolver(),
+ Settings.Global.PRIVATE_DNS_MODE);
+ if (nc.isPrivateDnsBroken()) {
+ return context.getString(R.string.private_dns_broken);
+ } else {
+ return context.getString(R.string.wifi_connected_no_internet);
+ }
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 5352936..b11585a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -31,6 +31,7 @@
import android.net.wifi.WifiSsid;
import android.os.Handler;
import android.os.Looper;
+import android.provider.Settings;
import com.android.settingslib.R;
@@ -163,7 +164,13 @@
statusLabel = mContext.getString(R.string.wifi_limited_connection);
return;
} else if (!networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) {
- statusLabel = mContext.getString(R.string.wifi_status_no_internet);
+ final String mode = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.PRIVATE_DNS_MODE);
+ if (networkCapabilities.isPrivateDnsBroken()) {
+ statusLabel = mContext.getString(R.string.private_dns_broken);
+ } else {
+ statusLabel = mContext.getString(R.string.wifi_status_no_internet);
+ }
return;
}
}
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index ffbf1ae..ef071a4 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -249,6 +249,8 @@
NOTE_NETWORK_LOGGED_IN = 744;
// A partial connectivity network was detected during network validation
NOTE_NETWORK_PARTIAL_CONNECTIVITY = 745;
+ // Private DNS is broken in strict mode
+ NOTE_NETWORK_PRIVATE_DNS_BROKEN = 746;
// Notify the user that their work profile has been deleted
// Package: android
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 0bb72cb..ce0e9e7 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -26,6 +26,7 @@
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_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_CAPTIVE_PORTAL;
@@ -528,6 +529,15 @@
private static final int EVENT_SET_ACCEPT_PARTIAL_CONNECTIVITY = 45;
/**
+ * Event for NetworkMonitor to inform ConnectivityService that the probe status has changed.
+ * Both of the arguments are bitmasks, and the value of bits come from
+ * INetworkMonitor.NETWORK_VALIDATION_PROBE_*.
+ * arg1 = A bitmask to describe which probes are completed.
+ * arg2 = A bitmask to describe which probes are successful.
+ */
+ public static final int EVENT_PROBE_STATUS_CHANGED = 46;
+
+ /**
* Argument for {@link #EVENT_PROVISIONING_NOTIFICATION} to indicate that the notification
* should be shown.
*/
@@ -2663,6 +2673,41 @@
switch (msg.what) {
default:
return false;
+ case EVENT_PROBE_STATUS_CHANGED: {
+ final Integer netId = (Integer) msg.obj;
+ final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(netId);
+ if (nai == null) {
+ break;
+ }
+ final boolean probePrivateDnsCompleted =
+ ((msg.arg1 & NETWORK_VALIDATION_PROBE_PRIVDNS) != 0);
+ final boolean privateDnsBroken =
+ ((msg.arg2 & NETWORK_VALIDATION_PROBE_PRIVDNS) == 0);
+ if (probePrivateDnsCompleted) {
+ if (nai.networkCapabilities.isPrivateDnsBroken() != privateDnsBroken) {
+ nai.networkCapabilities.setPrivateDnsBroken(privateDnsBroken);
+ final int oldScore = nai.getCurrentScore();
+ updateCapabilities(oldScore, nai, nai.networkCapabilities);
+ }
+ // Only show the notification when the private DNS is broken and the
+ // PRIVATE_DNS_BROKEN notification hasn't shown since last valid.
+ if (privateDnsBroken && !nai.networkMisc.hasShownBroken) {
+ showNetworkNotification(nai, NotificationType.PRIVATE_DNS_BROKEN);
+ }
+ nai.networkMisc.hasShownBroken = privateDnsBroken;
+ } else if (nai.networkCapabilities.isPrivateDnsBroken()) {
+ // If probePrivateDnsCompleted is false but nai.networkCapabilities says
+ // private DNS is broken, it means this network is being reevaluated.
+ // Either probing private DNS is not necessary any more or it hasn't been
+ // done yet. In either case, the networkCapabilities should be updated to
+ // reflect the new status.
+ nai.networkCapabilities.setPrivateDnsBroken(false);
+ final int oldScore = nai.getCurrentScore();
+ updateCapabilities(oldScore, nai, nai.networkCapabilities);
+ nai.networkMisc.hasShownBroken = false;
+ }
+ break;
+ }
case EVENT_NETWORK_TESTED: {
final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
if (nai == null) break;
@@ -2705,14 +2750,20 @@
if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
if (valid) {
handleFreshlyValidatedNetwork(nai);
- // Clear NO_INTERNET, PARTIAL_CONNECTIVITY and LOST_INTERNET
- // notifications if network becomes valid.
+ // Clear NO_INTERNET, PRIVATE_DNS_BROKEN, 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);
+ mNotifier.clearNotification(nai.network.netId,
+ NotificationType.PRIVATE_DNS_BROKEN);
+ // If network becomes valid, the hasShownBroken should be reset for
+ // that network so that the notification will be fired when the private
+ // DNS is broken again.
+ nai.networkMisc.hasShownBroken = false;
}
} else if (partialConnectivityChanged) {
updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities);
@@ -2863,6 +2914,13 @@
}
@Override
+ public void notifyProbeStatusChanged(int probesCompleted, int probesSucceeded) {
+ mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(
+ EVENT_PROBE_STATUS_CHANGED,
+ probesCompleted, probesSucceeded, new Integer(mNetId)));
+ }
+
+ @Override
public void showProvisioningNotification(String action, String packageName) {
final Intent intent = new Intent(action);
intent.setPackage(packageName);
@@ -3679,6 +3737,11 @@
// High priority because it is only displayed for explicitly selected networks.
highPriority = true;
break;
+ case PRIVATE_DNS_BROKEN:
+ action = Settings.ACTION_WIRELESS_SETTINGS;
+ // High priority because we should let user know why there is no internet.
+ highPriority = true;
+ break;
case LOST_INTERNET:
action = ConnectivityManager.ACTION_PROMPT_LOST_VALIDATION;
// High priority because it could help the user avoid unexpected data usage.
@@ -3696,7 +3759,7 @@
}
Intent intent = new Intent(action);
- if (type != NotificationType.LOGGED_IN) {
+ if (type != NotificationType.LOGGED_IN && type != NotificationType.PRIVATE_DNS_BROKEN) {
intent.setData(Uri.fromParts("netId", Integer.toString(nai.network.netId), null));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setClassName("com.android.settings",
@@ -5162,6 +5225,13 @@
ns.assertValidFromUid(Binder.getCallingUid());
}
+ private void ensureValid(NetworkCapabilities nc) {
+ ensureValidNetworkSpecifier(nc);
+ if (nc.isPrivateDnsBroken()) {
+ throw new IllegalArgumentException("Can't request broken private DNS");
+ }
+ }
+
@Override
public NetworkRequest requestNetwork(NetworkCapabilities networkCapabilities,
Messenger messenger, int timeoutMs, IBinder binder, int legacyType) {
@@ -5195,7 +5265,7 @@
if (timeoutMs < 0) {
throw new IllegalArgumentException("Bad timeout specified");
}
- ensureValidNetworkSpecifier(networkCapabilities);
+ ensureValid(networkCapabilities);
NetworkRequest networkRequest = new NetworkRequest(networkCapabilities, legacyType,
nextNetworkRequestId(), type);
@@ -5337,7 +5407,7 @@
// There is no need to do this for requests because an app without CHANGE_NETWORK_STATE
// can't request networks.
restrictBackgroundRequestForCaller(nc);
- ensureValidNetworkSpecifier(nc);
+ ensureValid(nc);
NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
NetworkRequest.Type.LISTEN);
@@ -5355,7 +5425,7 @@
if (!hasWifiNetworkListenPermission(networkCapabilities)) {
enforceAccessPermission();
}
- ensureValidNetworkSpecifier(networkCapabilities);
+ ensureValid(networkCapabilities);
ensureSufficientPermissionsForRequest(networkCapabilities,
Binder.getCallingPid(), Binder.getCallingUid());
@@ -5841,6 +5911,7 @@
} else {
newNc.removeCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY);
}
+ newNc.setPrivateDnsBroken(nai.networkCapabilities.isPrivateDnsBroken());
return newNc;
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 077c405..d13e675 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -53,7 +53,8 @@
NO_INTERNET(SystemMessage.NOTE_NETWORK_NO_INTERNET),
LOGGED_IN(SystemMessage.NOTE_NETWORK_LOGGED_IN),
PARTIAL_CONNECTIVITY(SystemMessage.NOTE_NETWORK_PARTIAL_CONNECTIVITY),
- SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN);
+ SIGN_IN(SystemMessage.NOTE_NETWORK_SIGN_IN),
+ PRIVATE_DNS_BROKEN(SystemMessage.NOTE_NETWORK_PRIVATE_DNS_BROKEN);
public final int eventId;
@@ -175,13 +176,23 @@
}
Resources r = Resources.getSystem();
- CharSequence title;
- CharSequence details;
+ final CharSequence title;
+ final CharSequence details;
int icon = getIcon(transportType, notifyType);
if (notifyType == NotificationType.NO_INTERNET && transportType == TRANSPORT_WIFI) {
title = r.getString(R.string.wifi_no_internet,
WifiInfo.removeDoubleQuotes(nai.networkCapabilities.getSSID()));
details = r.getString(R.string.wifi_no_internet_detailed);
+ } else if (notifyType == NotificationType.PRIVATE_DNS_BROKEN) {
+ if (transportType == TRANSPORT_CELLULAR) {
+ title = r.getString(R.string.mobile_no_internet);
+ } else if (transportType == TRANSPORT_WIFI) {
+ title = r.getString(R.string.wifi_no_internet,
+ WifiInfo.removeDoubleQuotes(nai.networkCapabilities.getSSID()));
+ } else {
+ title = r.getString(R.string.other_networks_no_internet);
+ }
+ details = r.getString(R.string.private_dns_broken_detailed);
} else if (notifyType == NotificationType.PARTIAL_CONNECTIVITY
&& transportType == TRANSPORT_WIFI) {
title = r.getString(R.string.network_partial_connectivity,
@@ -357,8 +368,10 @@
}
switch (t) {
case SIGN_IN:
- return 5;
+ return 6;
case PARTIAL_CONNECTIVITY:
+ return 5;
+ case PRIVATE_DNS_BROKEN:
return 4;
case NO_INTERNET:
return 3;
diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
index 2ca0d1a8..1569112 100644
--- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
+++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java
@@ -271,7 +271,7 @@
.addCapability(NET_CAPABILITY_NOT_METERED);
assertParcelingIsLossless(netCap);
netCap.setSSID(TEST_SSID);
- assertParcelSane(netCap, 11);
+ assertParcelSane(netCap, 12);
}
@Test
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index cf3fba8..61f37fd 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -33,6 +33,7 @@
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_CAPTIVE_PORTAL;
@@ -492,6 +493,8 @@
private INetworkMonitor mNetworkMonitor;
private INetworkMonitorCallbacks mNmCallbacks;
private int mNmValidationResult = VALIDATION_RESULT_BASE;
+ private int mProbesCompleted;
+ private int mProbesSucceeded;
private String mNmValidationRedirectUrl = null;
private boolean mNmProvNotificationRequested = false;
@@ -559,6 +562,7 @@
mNmProvNotificationRequested = false;
}
+ mNmCallbacks.notifyProbeStatusChanged(mProbesCompleted, mProbesSucceeded);
mNmCallbacks.notifyNetworkTested(
mNmValidationResult, mNmValidationRedirectUrl);
@@ -581,7 +585,7 @@
* @param validated Indicate if network should pretend to be validated.
*/
public void connect(boolean validated) {
- connect(validated, true);
+ connect(validated, true, false /* isStrictMode */);
}
/**
@@ -589,13 +593,13 @@
* @param validated Indicate if network should pretend to be validated.
* @param hasInternet Indicate if network should pretend to have NET_CAPABILITY_INTERNET.
*/
- public void connect(boolean validated, boolean hasInternet) {
+ public void connect(boolean validated, boolean hasInternet, boolean isStrictMode) {
assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_INTERNET));
ConnectivityManager.NetworkCallback callback = null;
final ConditionVariable validatedCv = new ConditionVariable();
if (validated) {
- setNetworkValid();
+ setNetworkValid(isStrictMode);
NetworkRequest request = new NetworkRequest.Builder()
.addTransportType(getNetworkCapabilities().getTransportTypes()[0])
.clearCapabilities()
@@ -620,15 +624,15 @@
if (validated) {
// Wait for network to validate.
waitFor(validatedCv);
- setNetworkInvalid();
+ setNetworkInvalid(isStrictMode);
}
if (callback != null) mCm.unregisterNetworkCallback(callback);
}
- public void connectWithCaptivePortal(String redirectUrl) {
- setNetworkPortal(redirectUrl);
- connect(false);
+ public void connectWithCaptivePortal(String redirectUrl, boolean isStrictMode) {
+ setNetworkPortal(redirectUrl, isStrictMode);
+ connect(false, true /* hasInternet */, isStrictMode);
}
public void connectWithPartialConnectivity() {
@@ -636,34 +640,75 @@
connect(false);
}
- public void connectWithPartialValidConnectivity() {
- setNetworkPartialValid();
- connect(false);
+ public void connectWithPartialValidConnectivity(boolean isStrictMode) {
+ setNetworkPartialValid(isStrictMode);
+ connect(false, true /* hasInternet */, isStrictMode);
}
- void setNetworkValid() {
+ void setNetworkValid(boolean isStrictMode) {
mNmValidationResult = VALIDATION_RESULT_VALID;
mNmValidationRedirectUrl = null;
+ int probesSucceeded = VALIDATION_RESULT_BASE & ~NETWORK_VALIDATION_PROBE_HTTP;
+ if (isStrictMode) {
+ probesSucceeded |= NETWORK_VALIDATION_PROBE_PRIVDNS;
+ }
+ // The probesCompleted equals to probesSucceeded for the case of valid network, so put
+ // the same value into two different parameter of the method.
+ setProbesStatus(probesSucceeded, probesSucceeded);
}
- void setNetworkInvalid() {
+ void setNetworkInvalid(boolean isStrictMode) {
mNmValidationResult = VALIDATION_RESULT_INVALID;
mNmValidationRedirectUrl = null;
+ int probesCompleted = VALIDATION_RESULT_BASE;
+ int probesSucceeded = VALIDATION_RESULT_INVALID;
+ // If the isStrictMode is true, it means the network is invalid when NetworkMonitor
+ // tried to validate the private DNS but failed.
+ if (isStrictMode) {
+ probesCompleted &= ~NETWORK_VALIDATION_PROBE_HTTP;
+ probesSucceeded = probesCompleted;
+ probesCompleted |= NETWORK_VALIDATION_PROBE_PRIVDNS;
+ }
+ setProbesStatus(probesCompleted, probesSucceeded);
}
- void setNetworkPortal(String redirectUrl) {
- setNetworkInvalid();
+ void setNetworkPortal(String redirectUrl, boolean isStrictMode) {
+ setNetworkInvalid(isStrictMode);
mNmValidationRedirectUrl = redirectUrl;
+ // Suppose the portal is found when NetworkMonitor probes NETWORK_VALIDATION_PROBE_HTTP
+ // in the beginning, so the NETWORK_VALIDATION_PROBE_HTTPS hasn't probed yet.
+ int probesCompleted = VALIDATION_RESULT_BASE & ~NETWORK_VALIDATION_PROBE_HTTPS;
+ int probesSucceeded = VALIDATION_RESULT_INVALID;
+ if (isStrictMode) {
+ probesCompleted |= NETWORK_VALIDATION_PROBE_PRIVDNS;
+ }
+ setProbesStatus(probesCompleted, probesSucceeded);
}
void setNetworkPartial() {
mNmValidationResult = VALIDATION_RESULT_PARTIAL;
mNmValidationRedirectUrl = null;
+ int probesCompleted = VALIDATION_RESULT_BASE;
+ int probesSucceeded = VALIDATION_RESULT_BASE & ~NETWORK_VALIDATION_PROBE_HTTPS;
+ setProbesStatus(probesCompleted, probesSucceeded);
}
- void setNetworkPartialValid() {
- mNmValidationResult = VALIDATION_RESULT_PARTIAL | VALIDATION_RESULT_VALID;
- mNmValidationRedirectUrl = null;
+ void setNetworkPartialValid(boolean isStrictMode) {
+ setNetworkPartial();
+ mNmValidationResult |= VALIDATION_RESULT_VALID;
+ int probesCompleted = VALIDATION_RESULT_BASE;
+ int probesSucceeded = VALIDATION_RESULT_BASE & ~NETWORK_VALIDATION_PROBE_HTTPS;
+ // Suppose the partial network cannot pass the private DNS validation as well, so only
+ // add NETWORK_VALIDATION_PROBE_DNS in probesCompleted but not probesSucceeded.
+ if (isStrictMode) {
+ probesCompleted |= NETWORK_VALIDATION_PROBE_PRIVDNS;
+ }
+ setProbesStatus(probesCompleted, probesSucceeded);
+ }
+
+ void setProbesStatus(int probesCompleted, int probesSucceeded) {
+ mProbesCompleted = probesCompleted;
+ mProbesSucceeded = probesSucceeded;
}
public String waitForRedirectUrl() {
@@ -2226,7 +2271,7 @@
// With HTTPS probe disabled, NetworkMonitor should pass the network validation with http
// probe.
- mWiFiNetworkAgent.setNetworkPartialValid();
+ mWiFiNetworkAgent.setNetworkPartialValid(false /* isStrictMode */);
// 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 */,
@@ -2299,7 +2344,7 @@
callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent);
assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent);
- mWiFiNetworkAgent.setNetworkValid();
+ mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */);
// Need a trigger point to let NetworkMonitor tell ConnectivityService that network is
// validated.
@@ -2317,7 +2362,7 @@
// NetworkMonitor will immediately (once the HTTPS probe fails...) report the network as
// valid, because ConnectivityService calls setAcceptPartialConnectivity before it calls
// notifyNetworkConnected.
- mWiFiNetworkAgent.connectWithPartialValidConnectivity();
+ mWiFiNetworkAgent.connectWithPartialValidConnectivity(false /* isStrictMode */);
callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity();
callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent);
@@ -2343,7 +2388,7 @@
// Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
String redirectUrl = "http://android.com/path";
- mWiFiNetworkAgent.connectWithCaptivePortal(redirectUrl);
+ mWiFiNetworkAgent.connectWithCaptivePortal(redirectUrl, false /* isStrictMode */);
captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), redirectUrl);
@@ -2361,7 +2406,7 @@
mWiFiNetworkAgent);
// Report partial connectivity is accepted.
- mWiFiNetworkAgent.setNetworkPartialValid();
+ mWiFiNetworkAgent.setNetworkPartialValid(false /* isStrictMode */);
mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), true /* accept */,
false /* always */);
waitForIdle();
@@ -2392,7 +2437,7 @@
// Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
String firstRedirectUrl = "http://example.com/firstPath";
- mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl);
+ mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl, false /* isStrictMode */);
captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), firstRedirectUrl);
@@ -2405,13 +2450,13 @@
// Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL.
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
String secondRedirectUrl = "http://example.com/secondPath";
- mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl);
+ mWiFiNetworkAgent.connectWithCaptivePortal(secondRedirectUrl, false /* isStrictMode */);
captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), secondRedirectUrl);
// Make captive portal disappear then revalidate.
// Expect onLost callback because network no longer provides NET_CAPABILITY_CAPTIVE_PORTAL.
- mWiFiNetworkAgent.setNetworkValid();
+ mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */);
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
captivePortalCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
@@ -2423,7 +2468,7 @@
// Break network connectivity.
// Expect NET_CAPABILITY_VALIDATED onLost callback.
- mWiFiNetworkAgent.setNetworkInvalid();
+ mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */);
mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
validatedCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
}
@@ -2454,7 +2499,7 @@
mServiceContext.expectNoStartActivityIntent(fastTimeoutMs);
// Turn into a captive portal.
- mWiFiNetworkAgent.setNetworkPortal("http://example.com");
+ mWiFiNetworkAgent.setNetworkPortal("http://example.com", false /* isStrictMode */);
mCm.reportNetworkConnectivity(wifiNetwork, false);
captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
validatedCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
@@ -2475,7 +2520,7 @@
assertEquals(testValue, signInIntent.getStringExtra(testKey));
// Report that the captive portal is dismissed, and check that callbacks are fired
- mWiFiNetworkAgent.setNetworkValid();
+ mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */);
mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
captivePortalCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
@@ -2504,7 +2549,7 @@
mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
String firstRedirectUrl = "http://example.com/firstPath";
- mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl);
+ mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl, false /* isStrictMode */);
mWiFiNetworkAgent.expectDisconnected();
mWiFiNetworkAgent.expectPreventReconnectReceived();
@@ -3219,7 +3264,7 @@
Network wifiNetwork = mWiFiNetworkAgent.getNetwork();
// Fail validation on wifi.
- mWiFiNetworkAgent.setNetworkInvalid();
+ mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */);
mCm.reportNetworkConnectivity(wifiNetwork, false);
defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
validatedWifiCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
@@ -3263,7 +3308,7 @@
wifiNetwork = mWiFiNetworkAgent.getNetwork();
// Fail validation on wifi and expect the dialog to appear.
- mWiFiNetworkAgent.setNetworkInvalid();
+ mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */);
mCm.reportNetworkConnectivity(wifiNetwork, false);
defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
validatedWifiCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
@@ -4299,7 +4344,7 @@
mCm.registerNetworkCallback(request, callback);
// Bring up wifi aware network.
- wifiAware.connect(false, false);
+ wifiAware.connect(false, false, false /* isStrictMode */);
callback.expectAvailableCallbacksUnvalidated(wifiAware);
assertNull(mCm.getActiveNetworkInfo());
@@ -4537,6 +4582,44 @@
}
@Test
+ public void testPrivateDnsNotification() throws Exception {
+ NetworkRequest request = new NetworkRequest.Builder()
+ .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET)
+ .build();
+ TestNetworkCallback callback = new TestNetworkCallback();
+ mCm.registerNetworkCallback(request, callback);
+ // Bring up wifi.
+ mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
+ mWiFiNetworkAgent.connect(false);
+ callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
+ // Private DNS resolution failed, checking if the notification will be shown or not.
+ mWiFiNetworkAgent.setNetworkInvalid(true /* isStrictMode */);
+ mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
+ waitForIdle();
+ // If network validation failed, NetworkMonitor will re-evaluate the network.
+ // ConnectivityService should filter the redundant notification. This part is trying to
+ // simulate that situation and check if ConnectivityService could filter that case.
+ mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
+ waitForIdle();
+ verify(mNotificationManager, timeout(TIMEOUT_MS).times(1)).notifyAsUser(anyString(),
+ eq(NotificationType.PRIVATE_DNS_BROKEN.eventId), any(), eq(UserHandle.ALL));
+ // If private DNS resolution successful, the PRIVATE_DNS_BROKEN notification shouldn't be
+ // shown.
+ mWiFiNetworkAgent.setNetworkValid(true /* isStrictMode */);
+ mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
+ waitForIdle();
+ verify(mNotificationManager, timeout(TIMEOUT_MS).times(1)).cancelAsUser(anyString(),
+ eq(NotificationType.PRIVATE_DNS_BROKEN.eventId), eq(UserHandle.ALL));
+ // If private DNS resolution failed again, the PRIVATE_DNS_BROKEN notification should be
+ // shown again.
+ mWiFiNetworkAgent.setNetworkInvalid(true /* isStrictMode */);
+ mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
+ waitForIdle();
+ verify(mNotificationManager, timeout(TIMEOUT_MS).times(2)).notifyAsUser(anyString(),
+ eq(NotificationType.PRIVATE_DNS_BROKEN.eventId), any(), eq(UserHandle.ALL));
+ }
+
+ @Test
public void testPrivateDnsSettingsChange() throws Exception {
// Clear any interactions that occur as a result of CS starting up.
reset(mMockDnsResolver);
@@ -4793,7 +4876,7 @@
// by NetworkMonitor
assertFalse(NetworkMonitorUtils.isValidationRequired(
vpnNetworkAgent.getNetworkCapabilities()));
- vpnNetworkAgent.setNetworkValid();
+ vpnNetworkAgent.setNetworkValid(false /* isStrictMode */);
vpnNetworkAgent.connect(false);
mMockVpn.connect();
@@ -4882,7 +4965,8 @@
ranges.add(new UidRange(uid, uid));
mMockVpn.setNetworkAgent(vpnNetworkAgent);
mMockVpn.setUids(ranges);
- vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */);
+ vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */,
+ false /* isStrictMode */);
mMockVpn.connect();
defaultCallback.assertNoCallback();
@@ -4913,7 +4997,8 @@
ranges.add(new UidRange(uid, uid));
mMockVpn.setNetworkAgent(vpnNetworkAgent);
mMockVpn.setUids(ranges);
- vpnNetworkAgent.connect(true /* validated */, true /* hasInternet */);
+ vpnNetworkAgent.connect(true /* validated */, true /* hasInternet */,
+ false /* isStrictMode */);
mMockVpn.connect();
defaultCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent);
@@ -4945,7 +5030,8 @@
ranges.add(new UidRange(uid, uid));
mMockVpn.setNetworkAgent(vpnNetworkAgent);
mMockVpn.setUids(ranges);
- vpnNetworkAgent.connect(false /* validated */, true /* hasInternet */);
+ vpnNetworkAgent.connect(false /* validated */, true /* hasInternet */,
+ false /* isStrictMode */);
mMockVpn.connect();
// Even though the VPN is unvalidated, it becomes the default network for our app.
@@ -4968,7 +5054,7 @@
vpnNetworkAgent.getNetworkCapabilities()));
// Pretend that the VPN network validates.
- vpnNetworkAgent.setNetworkValid();
+ vpnNetworkAgent.setNetworkValid(false /* isStrictMode */);
vpnNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
// Expect to see the validated capability, but no other changes, because the VPN is already
// the default network for the app.
@@ -5000,7 +5086,8 @@
mMockVpn.setNetworkAgent(vpnNetworkAgent);
mMockVpn.connect();
mMockVpn.setUids(ranges);
- vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */);
+ vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */,
+ false /* isStrictMode */);
vpnNetworkCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent);
nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork());
@@ -5099,7 +5186,8 @@
mMockVpn.setNetworkAgent(vpnNetworkAgent);
mMockVpn.connect();
mMockVpn.setUids(ranges);
- vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */);
+ vpnNetworkAgent.connect(true /* validated */, false /* hasInternet */,
+ false /* isStrictMode */);
vpnNetworkCallback.expectAvailableThenValidatedCallbacks(vpnNetworkAgent);
nc = mCm.getNetworkCapabilities(vpnNetworkAgent.getNetwork());