diff options
12 files changed, 303 insertions, 54 deletions
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 3e325b748f51..e3259ffa9d47 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -57,6 +57,9 @@ public final class NetworkCapabilities implements Parcelable { 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 @@ public final class NetworkCapabilities implements Parcelable { mUids = null; mEstablishingVpnAppUid = INVALID_UID; mSSID = null; + mPrivateDnsBroken = false; } /** @@ -104,6 +108,7 @@ public final class NetworkCapabilities implements Parcelable { mEstablishingVpnAppUid = nc.mEstablishingVpnAppUid; mUnwantedNetworkCapabilities = nc.mUnwantedNetworkCapabilities; mSSID = nc.mSSID; + mPrivateDnsBroken = nc.mPrivateDnsBroken; } /** @@ -557,6 +562,9 @@ public final class NetworkCapabilities implements Parcelable { } if (mLinkUpBandwidthKbps != 0 || mLinkDownBandwidthKbps != 0) return "link bandwidth"; if (hasSignalStrength()) return "signalStrength"; + if (isPrivateDnsBroken()) { + return "privateDnsBroken"; + } return null; } @@ -1443,7 +1451,8 @@ public final class NetworkCapabilities implements Parcelable { && equalsSpecifier(that) && equalsTransportInfo(that) && equalsUids(that) - && equalsSSID(that)); + && equalsSSID(that) + && equalsPrivateDnsBroken(that)); } @Override @@ -1460,7 +1469,8 @@ public final class NetworkCapabilities implements Parcelable { + (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 @@ public final class NetworkCapabilities implements Parcelable { 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 @@ public final class NetworkCapabilities implements Parcelable { 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 @@ public final class NetworkCapabilities implements Parcelable { sb.append(" SSID: ").append(mSSID); } + if (mPrivateDnsBroken) { + sb.append(" Private DNS is broken"); + } + sb.append("]"); return sb.toString(); } @@ -1706,4 +1722,28 @@ public final class NetworkCapabilities implements Parcelable { 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 9ba3bd940a96..4ad52d5aa1bc 100644 --- a/core/java/android/net/NetworkMisc.java +++ b/core/java/android/net/NetworkMisc.java @@ -77,6 +77,12 @@ public class NetworkMisc implements Parcelable { */ 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 a0ab5707dfcb..55271258c882 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3434,6 +3434,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 c2e996496eaa..33895a584206 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 0e9183940f22..bdfa21742854 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 6b1ceae4bed8..386ff65a4bf4 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.RemoteException; 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 @@ public class AccessPoint implements Comparable<AccessPoint> { 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 5352936d7224..b11585a73946 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.WifiNetworkScoreCache; 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 @@ public class WifiStatusTracker extends ConnectivityManager.NetworkCallback { 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 ffbf1ae38635..ef071a43aa3e 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -249,6 +249,8 @@ message SystemMessage { 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 0bb72cb9d554..ce0e9e7e56cf 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_NONE; 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 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 @@ public class ConnectivityService extends IConnectivityManager.Stub } @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 @@ public class ConnectivityService extends IConnectivityManager.Stub // 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 @@ public class ConnectivityService extends IConnectivityManager.Stub } 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 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 @@ public class ConnectivityService extends IConnectivityManager.Stub 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 @@ public class ConnectivityService extends IConnectivityManager.Stub // 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 @@ public class ConnectivityService extends IConnectivityManager.Stub if (!hasWifiNetworkListenPermission(networkCapabilities)) { enforceAccessPermission(); } - ensureValidNetworkSpecifier(networkCapabilities); + ensureValid(networkCapabilities); ensureSufficientPermissionsForRequest(networkCapabilities, Binder.getCallingPid(), Binder.getCallingUid()); @@ -5841,6 +5911,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } 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 077c4057a3a0..d13e6753d1b0 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 @@ public class NetworkNotificationManager { 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 @@ public class NetworkNotificationManager { } 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 @@ public class NetworkNotificationManager { } 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 2ca0d1a81e13..15691127cab7 100644 --- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java @@ -271,7 +271,7 @@ public class NetworkCapabilitiesTest { .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 cf3fba8bef91..61f37fd6c7e2 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_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_CAPTIVE_PORTAL; @@ -492,6 +493,8 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { mNmProvNotificationRequested = false; } + mNmCallbacks.notifyProbeStatusChanged(mProbesCompleted, mProbesSucceeded); mNmCallbacks.notifyNetworkTested( mNmValidationResult, mNmValidationRedirectUrl); @@ -581,7 +585,7 @@ public class ConnectivityServiceTest { * @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 @@ public class ConnectivityServiceTest { * @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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { // 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { // 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 @@ public class ConnectivityServiceTest { // 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { // 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 @@ public class ConnectivityServiceTest { // 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 @@ public class ConnectivityServiceTest { // 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { } @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 @@ public class ConnectivityServiceTest { // by NetworkMonitor assertFalse(NetworkMonitorUtils.isValidationRequired( vpnNetworkAgent.getNetworkCapabilities())); - vpnNetworkAgent.setNetworkValid(); + vpnNetworkAgent.setNetworkValid(false /* isStrictMode */); vpnNetworkAgent.connect(false); mMockVpn.connect(); @@ -4882,7 +4965,8 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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 @@ public class ConnectivityServiceTest { 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()); |