diff --git a/service/Android.bp b/service/Android.bp index 7fe0e2b382..39f970d209 100644 --- a/service/Android.bp +++ b/service/Android.bp @@ -65,7 +65,7 @@ java_library { "ServiceConnectivityResources", ], static_libs: [ - "dnsresolver_aidl_interface-V8-java", + "dnsresolver_aidl_interface-V9-java", "modules-utils-os", "net-utils-device-common", "net-utils-framework-common", diff --git a/service/ServiceConnectivityResources/res/values/config.xml b/service/ServiceConnectivityResources/res/values/config.xml index bf32ad5ac5..b22457a9bb 100644 --- a/service/ServiceConnectivityResources/res/values/config.xml +++ b/service/ServiceConnectivityResources/res/values/config.xml @@ -114,4 +114,15 @@ true + + false + + + false + diff --git a/service/ServiceConnectivityResources/res/values/overlayable.xml b/service/ServiceConnectivityResources/res/values/overlayable.xml index 6ac6a0e6a6..5af13d764b 100644 --- a/service/ServiceConnectivityResources/res/values/overlayable.xml +++ b/service/ServiceConnectivityResources/res/values/overlayable.xml @@ -32,6 +32,8 @@ + + diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index c8a0b7f0d9..e34c0640c2 100644 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -324,7 +324,8 @@ public class ConnectivityService extends IConnectivityManager.Stub private static final int DEFAULT_NASCENT_DELAY_MS = 5_000; // The maximum number of network request allowed per uid before an exception is thrown. - private static final int MAX_NETWORK_REQUESTS_PER_UID = 100; + @VisibleForTesting + static final int MAX_NETWORK_REQUESTS_PER_UID = 100; // The maximum number of network request allowed for system UIDs before an exception is thrown. @VisibleForTesting @@ -344,7 +345,8 @@ public class ConnectivityService extends IConnectivityManager.Stub @VisibleForTesting protected final PermissionMonitor mPermissionMonitor; - private final PerUidCounter mNetworkRequestCounter; + @VisibleForTesting + final PerUidCounter mNetworkRequestCounter; @VisibleForTesting final PerUidCounter mSystemNetworkRequestCounter; @@ -1154,9 +1156,20 @@ public class ConnectivityService extends IConnectivityManager.Stub private void incrementCountOrThrow(final int uid, final int numToIncrement) { final int newRequestCount = mUidToNetworkRequestCount.get(uid, 0) + numToIncrement; - if (newRequestCount >= mMaxCountPerUid) { + if (newRequestCount >= mMaxCountPerUid + // HACK : the system server is allowed to go over the request count limit + // when it is creating requests on behalf of another app (but not itself, + // so it can still detect its own request leaks). This only happens in the + // per-app API flows in which case the old requests for that particular + // UID will be removed soon. + // TODO : instead of this hack, addPerAppDefaultNetworkRequests and other + // users of transact() should unregister the requests to decrease the count + // before they increase it again by creating a new NRI. Then remove the + // transact() method. + && (Process.myUid() == uid || Process.myUid() != Binder.getCallingUid())) { throw new ServiceSpecificException( - ConnectivityManager.Errors.TOO_MANY_REQUESTS); + ConnectivityManager.Errors.TOO_MANY_REQUESTS, + "Uid " + uid + " exceeded its allotted requests limit"); } mUidToNetworkRequestCount.put(uid, newRequestCount); } @@ -5817,7 +5830,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mUid = nri.mUid; mAsUid = nri.mAsUid; mPendingIntent = nri.mPendingIntent; - mPerUidCounter = getRequestCounter(this); + mPerUidCounter = nri.mPerUidCounter; mPerUidCounter.incrementCountOrThrow(mUid); mCallbackFlags = nri.mCallbackFlags; mCallingAttributionTag = nri.mCallingAttributionTag; @@ -10147,7 +10160,7 @@ public class ConnectivityService extends IConnectivityManager.Stub final NetworkRequestInfo trackingNri = getDefaultRequestTrackingUid(callbackRequest.mAsUid); - // If this nri is not being tracked, the change it back to an untracked nri. + // If this nri is not being tracked, then change it back to an untracked nri. if (trackingNri == mDefaultRequest) { callbackRequestsToRegister.add(new NetworkRequestInfo( callbackRequest, diff --git a/service/src/com/android/server/connectivity/DnsManager.java b/service/src/com/android/server/connectivity/DnsManager.java index 05b12bad55..1493cae79a 100644 --- a/service/src/com/android/server/connectivity/DnsManager.java +++ b/service/src/com/android/server/connectivity/DnsManager.java @@ -38,7 +38,6 @@ import android.net.IDnsResolver; import android.net.InetAddresses; import android.net.LinkProperties; import android.net.Network; -import android.net.ResolverOptionsParcel; import android.net.ResolverParamsParcel; import android.net.Uri; import android.net.shared.PrivateDnsConfig; @@ -384,7 +383,6 @@ public class DnsManager { .collect(Collectors.toList())) : useTls ? paramsParcel.servers // Opportunistic : new String[0]; // Off - paramsParcel.resolverOptions = new ResolverOptionsParcel(); paramsParcel.transportTypes = transportTypes; // Prepare to track the validation status of the DNS servers in the // resolver config when private DNS is in opportunistic or strict mode. diff --git a/service/src/com/android/server/connectivity/FullScore.java b/service/src/com/android/server/connectivity/FullScore.java index fbfa7a18c9..14cec09560 100644 --- a/service/src/com/android/server/connectivity/FullScore.java +++ b/service/src/com/android/server/connectivity/FullScore.java @@ -108,10 +108,9 @@ public class FullScore { // and all bits managed by FullScore unset. As bits are handled from 0 up in NetworkScore and // from 63 down in FullScore, cut at the 32nd bit for simplicity, but change this if some day // there are more than 32 bits handled on either side. - // YIELD_TO_BAD_WIFI is temporarily handled by ConnectivityService, but the factory is still - // allowed to set it, so that it's possible to transition from handling it in CS to handling - // it in the factory. - private static final long EXTERNAL_POLICIES_MASK = 0x00000000FFFFFFFFL; + // YIELD_TO_BAD_WIFI is temporarily handled by ConnectivityService. + private static final long EXTERNAL_POLICIES_MASK = + 0x00000000FFFFFFFFL & ~(1L << POLICY_YIELD_TO_BAD_WIFI); @VisibleForTesting static @NonNull String policyNameOf(final int policy) { diff --git a/service/src/com/android/server/connectivity/NetworkNotificationManager.java b/service/src/com/android/server/connectivity/NetworkNotificationManager.java index ae98d92073..155f6c4395 100644 --- a/service/src/com/android/server/connectivity/NetworkNotificationManager.java +++ b/service/src/com/android/server/connectivity/NetworkNotificationManager.java @@ -198,11 +198,22 @@ public class NetworkNotificationManager { } final Resources r = mResources.get(); + if (highPriority && maybeNotifyViaDialog(r, notifyType, intent)) { + Log.d(TAG, "Notified via dialog for event " + nameOf(eventId)); + return; + } + final CharSequence title; final CharSequence details; Icon icon = Icon.createWithResource( mResources.getResourcesContext(), getIcon(transportType)); - if (notifyType == NotificationType.NO_INTERNET && transportType == TRANSPORT_WIFI) { + final boolean showAsNoInternet = notifyType == NotificationType.PARTIAL_CONNECTIVITY + && r.getBoolean(R.bool.config_partialConnectivityNotifiedAsNoInternet); + if (showAsNoInternet) { + Log.d(TAG, "Showing partial connectivity as NO_INTERNET"); + } + if ((notifyType == NotificationType.NO_INTERNET || showAsNoInternet) + && transportType == TRANSPORT_WIFI) { title = r.getString(R.string.wifi_no_internet, name); details = r.getString(R.string.wifi_no_internet_detailed); } else if (notifyType == NotificationType.PRIVATE_DNS_BROKEN) { @@ -306,6 +317,24 @@ public class NetworkNotificationManager { } } + private boolean maybeNotifyViaDialog(Resources res, NotificationType notifyType, + PendingIntent intent) { + if (notifyType != NotificationType.NO_INTERNET + && notifyType != NotificationType.PARTIAL_CONNECTIVITY) { + return false; + } + if (!res.getBoolean(R.bool.config_notifyNoInternetAsDialogWhenHighPriority)) { + return false; + } + + try { + intent.send(); + } catch (PendingIntent.CanceledException e) { + Log.e(TAG, "Error sending dialog PendingIntent", e); + } + return true; + } + /** * Clear the notification with the given id, only if it matches the given type. */ diff --git a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java index ccbdbd35b5..60a20f4edf 100644 --- a/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java +++ b/tests/cts/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java @@ -22,6 +22,7 @@ import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport; import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_ATTEMPTED_BITMASK; import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_PROBES_SUCCEEDED_BITMASK; import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.KEY_NETWORK_VALIDATION_RESULT; +import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.NETWORK_VALIDATION_RESULT_SKIPPED; import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport.NETWORK_VALIDATION_RESULT_VALID; import static android.net.ConnectivityDiagnosticsManager.DataStallReport; import static android.net.ConnectivityDiagnosticsManager.DataStallReport.DETECTION_METHOD_DNS_EVENTS; @@ -78,6 +79,7 @@ import androidx.test.InstrumentationRegistry; import com.android.internal.telephony.uicc.IccUtils; import com.android.internal.util.ArrayUtils; +import com.android.modules.utils.build.SdkLevel; import com.android.net.module.util.ArrayTrackRecord; import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo; import com.android.testutils.DevSdkIgnoreRunner; @@ -427,6 +429,12 @@ public class ConnectivityDiagnosticsManagerTest { // revalidated which will trigger another onConnectivityReportAvailable callback. if (!hasConnectivity) { cb.expectOnConnectivityReportAvailable(mTestNetwork, interfaceName); + } else if (SdkLevel.isAtLeastS()) { + // All calls to #onNetworkConnectivityReported are expected to be accompanied by a call + // to #onConnectivityReportAvailable after a mainline update in the S timeframe. + // Optionally validate this, but do not fail if it does not exist. + cb.maybeVerifyOnConnectivityReportAvailable(mTestNetwork, interfaceName, TRANSPORT_TEST, + false /* requireCallbackFired */); } cb.assertNoCallback(); @@ -479,13 +487,25 @@ public class ConnectivityDiagnosticsManagerTest { public void expectOnConnectivityReportAvailable( @NonNull Network network, @NonNull String interfaceName) { - expectOnConnectivityReportAvailable(network, interfaceName, TRANSPORT_TEST); + expectOnConnectivityReportAvailable( + network, interfaceName, TRANSPORT_TEST); } - public void expectOnConnectivityReportAvailable( - @NonNull Network network, @NonNull String interfaceName, int transportType) { + public void expectOnConnectivityReportAvailable(@NonNull Network network, + @NonNull String interfaceName, int transportType) { + maybeVerifyOnConnectivityReportAvailable(network, interfaceName, transportType, + true /* requireCallbackFired */); + } + + public void maybeVerifyOnConnectivityReportAvailable(@NonNull Network network, + @NonNull String interfaceName, int transportType, boolean requireCallbackFired) { final ConnectivityReport result = (ConnectivityReport) mHistory.poll(CALLBACK_TIMEOUT_MILLIS, x -> true); + + // If callback is not required and there is no report, exit early. + if (!requireCallbackFired && result == null) { + return; + } assertEquals(network, result.getNetwork()); final NetworkCapabilities nc = result.getNetworkCapabilities(); @@ -496,9 +516,16 @@ public class ConnectivityDiagnosticsManagerTest { final PersistableBundle extras = result.getAdditionalInfo(); assertTrue(extras.containsKey(KEY_NETWORK_VALIDATION_RESULT)); - final int validationResult = extras.getInt(KEY_NETWORK_VALIDATION_RESULT); - assertEquals("Network validation result is not 'valid'", - NETWORK_VALIDATION_RESULT_VALID, validationResult); + final int actualValidationResult = extras.getInt(KEY_NETWORK_VALIDATION_RESULT); + + // Allow RESULT_VALID for networks that are expected to be skipped. Android S shipped + // with validation results being reported as VALID, but the behavior will be updated via + // mainline update. Allow both behaviors, and let MTS enforce stricter behavior + if (actualValidationResult != NETWORK_VALIDATION_RESULT_SKIPPED + && actualValidationResult != NETWORK_VALIDATION_RESULT_VALID) { + fail("Network validation result was incorrect; expected skipped or valid, but " + + "got " + actualValidationResult); + } assertTrue(extras.containsKey(KEY_NETWORK_PROBES_SUCCEEDED_BITMASK)); final int probesSucceeded = extras.getInt(KEY_NETWORK_VALIDATION_RESULT); diff --git a/tests/integration/Android.bp b/tests/integration/Android.bp index 26f7f4aaee..7b5b44f731 100644 --- a/tests/integration/Android.bp +++ b/tests/integration/Android.bp @@ -30,6 +30,7 @@ android_test { ], libs: [ "android.test.mock", + "ServiceConnectivityResources", ], static_libs: [ "NetworkStackApiStableLib", diff --git a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt index e039ef0725..80338aa2c0 100644 --- a/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt +++ b/tests/integration/src/com/android/server/net/integrationtests/ConnectivityServiceIntegrationTest.kt @@ -23,7 +23,9 @@ import android.content.Context.BIND_AUTO_CREATE import android.content.Context.BIND_IMPORTANT import android.content.Intent import android.content.ServiceConnection +import android.content.res.Resources import android.net.ConnectivityManager +import android.net.ConnectivityResources import android.net.IDnsResolver import android.net.INetd import android.net.LinkProperties @@ -35,6 +37,7 @@ import android.net.NetworkRequest import android.net.TestNetworkStackClient import android.net.Uri import android.net.metrics.IpConnectivityLog +import android.net.util.MultinetworkPolicyTracker import android.os.ConditionVariable import android.os.IBinder import android.os.SystemConfigManager @@ -43,6 +46,7 @@ import android.testing.TestableContext import android.util.Log import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry +import com.android.connectivity.resources.R import com.android.server.ConnectivityService import com.android.server.NetworkAgentWrapper import com.android.server.TestNetIdManager @@ -59,6 +63,7 @@ import org.mockito.ArgumentMatchers.anyString import org.mockito.Mock import org.mockito.Mockito.any import org.mockito.Mockito.anyInt +import org.mockito.Mockito.doAnswer import org.mockito.Mockito.doNothing import org.mockito.Mockito.doReturn import org.mockito.Mockito.eq @@ -93,6 +98,10 @@ class ConnectivityServiceIntegrationTest { private lateinit var dnsResolver: IDnsResolver @Mock private lateinit var systemConfigManager: SystemConfigManager + @Mock + private lateinit var resources: Resources + @Mock + private lateinit var resourcesContext: Context @Spy private var context = TestableContext(realContext) @@ -110,9 +119,11 @@ class ConnectivityServiceIntegrationTest { private val realContext get() = InstrumentationRegistry.getInstrumentation().context private val httpProbeUrl get() = - realContext.getResources().getString(R.string.config_captive_portal_http_url) + realContext.getResources().getString(com.android.server.net.integrationtests.R.string + .config_captive_portal_http_url) private val httpsProbeUrl get() = - realContext.getResources().getString(R.string.config_captive_portal_https_url) + realContext.getResources().getString(com.android.server.net.integrationtests.R.string + .config_captive_portal_https_url) private class InstrumentationServiceConnection : ServiceConnection { override fun onServiceConnected(name: ComponentName?, service: IBinder?) { @@ -156,6 +167,27 @@ class ConnectivityServiceIntegrationTest { .getSystemService(Context.SYSTEM_CONFIG_SERVICE) doReturn(IntArray(0)).`when`(systemConfigManager).getSystemPermissionUids(anyString()) + doReturn(60000).`when`(resources).getInteger(R.integer.config_networkTransitionTimeout) + doReturn("").`when`(resources).getString(R.string.config_networkCaptivePortalServerUrl) + doReturn(arrayOf("test_wlan_wol")).`when`(resources) + .getStringArray(R.array.config_wakeonlan_supported_interfaces) + doReturn(arrayOf("0,1", "1,3")).`when`(resources) + .getStringArray(R.array.config_networkSupportedKeepaliveCount) + doReturn(emptyArray()).`when`(resources) + .getStringArray(R.array.config_networkNotifySwitches) + doReturn(intArrayOf(10, 11, 12, 14, 15)).`when`(resources) + .getIntArray(R.array.config_protectedNetworks) + // We don't test the actual notification value strings, so just return an empty array. + // It doesn't matter what the values are as long as it's not null. + doReturn(emptyArray()).`when`(resources).getStringArray( + R.array.network_switch_type_name) + doReturn(1).`when`(resources).getInteger(R.integer.config_networkAvoidBadWifi) + doReturn(R.array.config_networkSupportedKeepaliveCount).`when`(resources) + .getIdentifier(eq("config_networkSupportedKeepaliveCount"), eq("array"), any()) + + doReturn(resources).`when`(resourcesContext).getResources() + ConnectivityResources.setResourcesContextForTest(resourcesContext) + networkStackClient = TestNetworkStackClient(realContext) networkStackClient.start() @@ -176,12 +208,19 @@ class ConnectivityServiceIntegrationTest { doReturn(mock(ProxyTracker::class.java)).`when`(deps).makeProxyTracker(any(), any()) doReturn(mock(MockableSystemProperties::class.java)).`when`(deps).systemProperties doReturn(TestNetIdManager()).`when`(deps).makeNetIdManager() + doAnswer { inv -> + object : MultinetworkPolicyTracker(inv.getArgument(0), inv.getArgument(1), + inv.getArgument(2)) { + override fun getResourcesForActiveSubId() = resources + } + }.`when`(deps).makeMultinetworkPolicyTracker(any(), any(), any()) return deps } @After fun tearDown() { nsInstrumentation.clearAllState() + ConnectivityResources.setResourcesContextForTest(null) } @Test diff --git a/tests/unit/Android.bp b/tests/unit/Android.bp index c8476cb726..71bd6089cc 100644 --- a/tests/unit/Android.bp +++ b/tests/unit/Android.bp @@ -62,6 +62,7 @@ android_library { jarjar_rules: "jarjar-rules.txt", static_libs: [ "androidx.test.rules", + "androidx.test.uiautomator", "bouncycastle-repackaged-unbundled", "core-tests-support", "FrameworksNetCommonTests", diff --git a/tests/unit/AndroidManifest.xml b/tests/unit/AndroidManifest.xml index 4c60ccf606..887f17158b 100644 --- a/tests/unit/AndroidManifest.xml +++ b/tests/unit/AndroidManifest.xml @@ -53,6 +53,8 @@ + mStartedActivities = new LinkedBlockingQueue<>(); // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant + // For permissions granted across the board, the key is only the permission name. + // For permissions only granted to a combination of uid/pid, the key + // is ",,". PID+UID permissons have priority over generic ones. private final HashMap mMockedPermissions = new HashMap<>(); MockContext(Context base, ContentProvider settingsProvider) { @@ -640,30 +644,40 @@ public class ConnectivityServiceTest { return mPackageManager; } - private int checkMockedPermission(String permission, Supplier ifAbsent) { - final Integer granted = mMockedPermissions.get(permission); - return granted != null ? granted : ifAbsent.get(); + private int checkMockedPermission(String permission, int pid, int uid, + Supplier ifAbsent) { + final Integer granted = mMockedPermissions.get(permission + "," + pid + "," + uid); + if (null != granted) { + return granted; + } + final Integer allGranted = mMockedPermissions.get(permission); + if (null != allGranted) { + return allGranted; + } + return ifAbsent.get(); } @Override public int checkPermission(String permission, int pid, int uid) { - return checkMockedPermission( - permission, () -> super.checkPermission(permission, pid, uid)); + return checkMockedPermission(permission, pid, uid, + () -> super.checkPermission(permission, pid, uid)); } @Override public int checkCallingOrSelfPermission(String permission) { - return checkMockedPermission( - permission, () -> super.checkCallingOrSelfPermission(permission)); + return checkMockedPermission(permission, Process.myPid(), Process.myUid(), + () -> super.checkCallingOrSelfPermission(permission)); } @Override public void enforceCallingOrSelfPermission(String permission, String message) { - final Integer granted = mMockedPermissions.get(permission); - if (granted == null) { - super.enforceCallingOrSelfPermission(permission, message); - return; - } + final Integer granted = checkMockedPermission(permission, + Process.myPid(), Process.myUid(), + () -> { + super.enforceCallingOrSelfPermission(permission, message); + // enforce will crash if the permission is not granted + return PERMISSION_GRANTED; + }); if (!granted.equals(PERMISSION_GRANTED)) { throw new SecurityException("[Test] permission denied: " + permission); @@ -673,6 +687,8 @@ public class ConnectivityServiceTest { /** * Mock checks for the specified permission, and have them behave as per {@code granted}. * + * This will apply across the board no matter what the checked UID and PID are. + * *

Passing null reverts to default behavior, which does a real permission check on the * test package. * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or @@ -682,6 +698,21 @@ public class ConnectivityServiceTest { mMockedPermissions.put(permission, granted); } + /** + * Mock checks for the specified permission, and have them behave as per {@code granted}. + * + * This will only apply to the passed UID and PID. + * + *

Passing null reverts to default behavior, which does a real permission check on the + * test package. + * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or + * {@link PackageManager#PERMISSION_DENIED}. + */ + public void setPermission(String permission, int pid, int uid, Integer granted) { + final String key = permission + "," + pid + "," + uid; + mMockedPermissions.put(key, granted); + } + @Override public Intent registerReceiverForAllUsers(@Nullable BroadcastReceiver receiver, @NonNull IntentFilter filter, @Nullable String broadcastPermission, @@ -1569,15 +1600,21 @@ public class ConnectivityServiceTest { } private void withPermission(String permission, ExceptionalRunnable r) throws Exception { - if (mServiceContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) { - r.run(); - return; - } try { mServiceContext.setPermission(permission, PERMISSION_GRANTED); r.run(); } finally { - mServiceContext.setPermission(permission, PERMISSION_DENIED); + mServiceContext.setPermission(permission, null); + } + } + + private void withPermission(String permission, int pid, int uid, ExceptionalRunnable r) + throws Exception { + try { + mServiceContext.setPermission(permission, pid, uid, PERMISSION_GRANTED); + r.run(); + } finally { + mServiceContext.setPermission(permission, pid, uid, null); } } @@ -9509,6 +9546,19 @@ public class ConnectivityServiceTest { assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID); } + @Test + public void testStartVpnProfileFromDiffPackage() throws Exception { + final String notMyVpnPkg = "com.not.my.vpn"; + assertThrows( + SecurityException.class, () -> mVpnManagerService.startVpnProfile(notMyVpnPkg)); + } + + @Test + public void testStopVpnProfileFromDiffPackage() throws Exception { + final String notMyVpnPkg = "com.not.my.vpn"; + assertThrows(SecurityException.class, () -> mVpnManagerService.stopVpnProfile(notMyVpnPkg)); + } + @Test public void testUidUpdateChangesInterfaceFilteringRule() throws Exception { LinkProperties lp = new LinkProperties(); @@ -13368,17 +13418,45 @@ public class ConnectivityServiceTest { @Test public void testProfileNetworkPrefCountsRequestsCorrectlyOnSet() throws Exception { final UserHandle testHandle = setupEnterpriseNetwork(); - testRequestCountLimits(() -> { - // Set initially to test the limit prior to having existing requests. - final TestOnCompleteListener listener = new TestOnCompleteListener(); - mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, - Runnable::run, listener); + final TestOnCompleteListener listener = new TestOnCompleteListener(); + // Leave one request available so the profile preference can be set. + testRequestCountLimits(1 /* countToLeaveAvailable */, () -> { + withPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + Process.myPid(), Process.myUid(), () -> { + // Set initially to test the limit prior to having existing requests. + mCm.setProfileNetworkPreference(testHandle, + PROFILE_NETWORK_PREFERENCE_ENTERPRISE, + Runnable::run, listener); + }); listener.expectOnComplete(); - // re-set so as to test the limit as part of replacing existing requests. - mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, - Runnable::run, listener); + // Simulate filing requests as some app on the work profile + final int otherAppUid = UserHandle.getUid(TEST_WORK_PROFILE_USER_ID, + UserHandle.getAppId(Process.myUid() + 1)); + final int remainingCount = ConnectivityService.MAX_NETWORK_REQUESTS_PER_UID + - mService.mNetworkRequestCounter.mUidToNetworkRequestCount.get(otherAppUid) + - 1; + final NetworkCallback[] callbacks = new NetworkCallback[remainingCount]; + doAsUid(otherAppUid, () -> { + for (int i = 0; i < remainingCount; ++i) { + callbacks[i] = new TestableNetworkCallback(); + mCm.registerDefaultNetworkCallback(callbacks[i]); + } + }); + + withPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + Process.myPid(), Process.myUid(), () -> { + // re-set so as to test the limit as part of replacing existing requests. + mCm.setProfileNetworkPreference(testHandle, + PROFILE_NETWORK_PREFERENCE_ENTERPRISE, Runnable::run, listener); + }); listener.expectOnComplete(); + + doAsUid(otherAppUid, () -> { + for (final NetworkCallback callback : callbacks) { + mCm.unregisterNetworkCallback(callback); + } + }); }); } @@ -13390,39 +13468,45 @@ public class ConnectivityServiceTest { mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, true); @OemNetworkPreferences.OemNetworkPreference final int networkPref = OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; - testRequestCountLimits(() -> { - // Set initially to test the limit prior to having existing requests. - final TestOemListenerCallback listener = new TestOemListenerCallback(); - mService.setOemNetworkPreference( - createDefaultOemNetworkPreferences(networkPref), listener); - listener.expectOnComplete(); + // Leave one request available so the OEM preference can be set. + testRequestCountLimits(1 /* countToLeaveAvailable */, () -> + withPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, () -> { + // Set initially to test the limit prior to having existing requests. + final TestOemListenerCallback listener = new TestOemListenerCallback(); + mService.setOemNetworkPreference( + createDefaultOemNetworkPreferences(networkPref), listener); + listener.expectOnComplete(); - // re-set so as to test the limit as part of replacing existing requests. - mService.setOemNetworkPreference( - createDefaultOemNetworkPreferences(networkPref), listener); - listener.expectOnComplete(); - }); + // re-set so as to test the limit as part of replacing existing requests. + mService.setOemNetworkPreference( + createDefaultOemNetworkPreferences(networkPref), listener); + listener.expectOnComplete(); + })); } - private void testRequestCountLimits(@NonNull final Runnable r) throws Exception { + private void testRequestCountLimits(final int countToLeaveAvailable, + @NonNull final ExceptionalRunnable r) throws Exception { final ArraySet callbacks = new ArraySet<>(); try { final int requestCount = mService.mSystemNetworkRequestCounter .mUidToNetworkRequestCount.get(Process.myUid()); - // The limit is hit when total requests <= limit. - final int maxCount = - ConnectivityService.MAX_NETWORK_REQUESTS_PER_SYSTEM_UID - requestCount; + // The limit is hit when total requests = limit - 1, and exceeded with a crash when + // total requests >= limit. + final int countToFile = + MAX_NETWORK_REQUESTS_PER_SYSTEM_UID - requestCount - countToLeaveAvailable; // Need permission so registerDefaultNetworkCallback uses mSystemNetworkRequestCounter withPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, () -> { - for (int i = 1; i < maxCount - 1; i++) { + for (int i = 1; i < countToFile; i++) { final TestNetworkCallback cb = new TestNetworkCallback(); mCm.registerDefaultNetworkCallback(cb); callbacks.add(cb); } - - // Code to run to check if it triggers a max request count limit error. - r.run(); + assertEquals(MAX_NETWORK_REQUESTS_PER_SYSTEM_UID - 1 - countToLeaveAvailable, + mService.mSystemNetworkRequestCounter + .mUidToNetworkRequestCount.get(Process.myUid())); }); + // Code to run to check if it triggers a max request count limit error. + r.run(); } finally { for (final TestNetworkCallback cb : callbacks) { mCm.unregisterNetworkCallback(cb); @@ -13667,15 +13751,18 @@ public class ConnectivityServiceTest { public void testMobileDataPreferredUidsChangedCountsRequestsCorrectlyOnSet() throws Exception { ConnectivitySettingsManager.setMobileDataPreferredUids(mServiceContext, Set.of(PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID))); - testRequestCountLimits(() -> { - // Set initially to test the limit prior to having existing requests. - mService.updateMobileDataPreferredUids(); - waitForIdle(); + // Leave one request available so MDO preference set up above can be set. + testRequestCountLimits(1 /* countToLeaveAvailable */, () -> + withPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + Process.myPid(), Process.myUid(), () -> { + // Set initially to test the limit prior to having existing requests. + mService.updateMobileDataPreferredUids(); + waitForIdle(); - // re-set so as to test the limit as part of replacing existing requests. - mService.updateMobileDataPreferredUids(); - waitForIdle(); - }); + // re-set so as to test the limit as part of replacing existing requests + mService.updateMobileDataPreferredUids(); + waitForIdle(); + })); } @Test diff --git a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java index 9ef558fcea..24aecdb90a 100644 --- a/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java +++ b/tests/unit/java/com/android/server/connectivity/DnsManagerTest.java @@ -43,6 +43,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.net.ConnectivitySettingsManager; import android.net.IDnsResolver; @@ -106,8 +107,14 @@ public class DnsManagerTest { @Mock IDnsResolver mMockDnsResolver; private void assertResolverOptionsEquals( - @NonNull ResolverOptionsParcel actual, - @NonNull ResolverOptionsParcel expected) { + @Nullable ResolverOptionsParcel actual, + @Nullable ResolverOptionsParcel expected) { + if (actual == null) { + assertNull(expected); + return; + } else { + assertNotNull(expected); + } assertEquals(actual.hosts, expected.hosts); assertEquals(actual.tcMode, expected.tcMode); assertEquals(actual.enforceDnsUid, expected.enforceDnsUid); @@ -365,7 +372,7 @@ public class DnsManagerTest { expectedParams.tlsName = ""; expectedParams.tlsServers = new String[]{"3.3.3.3", "4.4.4.4"}; expectedParams.transportTypes = TEST_TRANSPORT_TYPES; - expectedParams.resolverOptions = new ResolverOptionsParcel(); + expectedParams.resolverOptions = null; assertResolverParamsEquals(actualParams, expectedParams); } diff --git a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java index c1059b33a8..2cf5d8eaef 100644 --- a/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java +++ b/tests/unit/java/com/android/server/connectivity/NetworkNotificationManagerTest.java @@ -27,6 +27,7 @@ import static com.android.server.connectivity.NetworkNotificationManager.Notific import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.SIGN_IN; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.clearInvocations; @@ -39,9 +40,14 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.Activity; +import android.app.Instrumentation; +import android.app.KeyguardManager; import android.app.Notification; import android.app.NotificationManager; +import android.app.PendingIntent; import android.content.Context; +import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; @@ -49,11 +55,19 @@ import android.net.ConnectivityResources; import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.os.Build; +import android.os.Bundle; import android.os.UserHandle; import android.telephony.TelephonyManager; import android.util.DisplayMetrics; +import android.widget.TextView; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.uiautomator.UiDevice; +import androidx.test.uiautomator.UiObject; +import androidx.test.uiautomator.UiSelector; import com.android.connectivity.resources.R; import com.android.server.connectivity.NetworkNotificationManager.NotificationType; @@ -84,6 +98,7 @@ public class NetworkNotificationManagerTest { private static final String TEST_EXTRA_INFO = "extra"; private static final int TEST_NOTIF_ID = 101; private static final String TEST_NOTIF_TAG = NetworkNotificationManager.tagFor(TEST_NOTIF_ID); + private static final long TEST_TIMEOUT_MS = 10_000L; static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities(); static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities(); static final NetworkCapabilities VPN_CAPABILITIES = new NetworkCapabilities(); @@ -102,6 +117,25 @@ public class NetworkNotificationManagerTest { VPN_CAPABILITIES.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN); } + /** + * Test activity that shows the action it was started with on screen, and dismisses when the + * text is tapped. + */ + public static class TestDialogActivity extends Activity { + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setTurnScreenOn(true); + getSystemService(KeyguardManager.class).requestDismissKeyguard( + this, null /* callback */); + + final TextView txt = new TextView(this); + txt.setText(getIntent().getAction()); + txt.setOnClickListener(e -> finish()); + setContentView(txt); + } + } + @Mock Context mCtx; @Mock Resources mResources; @Mock DisplayMetrics mDisplayMetrics; @@ -345,4 +379,82 @@ public class NetworkNotificationManagerTest { mManager.clearNotification(id, PARTIAL_CONNECTIVITY); verify(mNotificationManager, never()).cancel(eq(tag), eq(PARTIAL_CONNECTIVITY.eventId)); } + + @Test + public void testNotifyNoInternetAsDialogWhenHighPriority() throws Exception { + doReturn(true).when(mResources).getBoolean( + R.bool.config_notifyNoInternetAsDialogWhenHighPriority); + + mManager.showNotification(TEST_NOTIF_ID, NETWORK_SWITCH, mWifiNai, mCellNai, null, false); + // Non-"no internet" notifications are not affected + verify(mNotificationManager).notify(eq(TEST_NOTIF_TAG), eq(NETWORK_SWITCH.eventId), any()); + + final Instrumentation instr = InstrumentationRegistry.getInstrumentation(); + final Context ctx = instr.getContext(); + final String testAction = "com.android.connectivity.coverage.TEST_DIALOG"; + final Intent intent = new Intent(testAction) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .setClassName(ctx.getPackageName(), TestDialogActivity.class.getName()); + final PendingIntent pendingIntent = PendingIntent.getActivity(ctx, 0 /* requestCode */, + intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); + + mManager.showNotification(TEST_NOTIF_ID, NO_INTERNET, mWifiNai, null /* switchToNai */, + pendingIntent, true /* highPriority */); + + // Previous notifications are still dismissed + verify(mNotificationManager).cancel(TEST_NOTIF_TAG, NETWORK_SWITCH.eventId); + + // Verify that the activity is shown (the activity shows the action on screen) + final UiObject actionText = UiDevice.getInstance(instr).findObject( + new UiSelector().text(testAction)); + assertTrue("Activity not shown", actionText.waitForExists(TEST_TIMEOUT_MS)); + + // Tapping the text should dismiss the dialog + actionText.click(); + assertTrue("Activity not dismissed", actionText.waitUntilGone(TEST_TIMEOUT_MS)); + + // Verify no NO_INTERNET notification was posted + verify(mNotificationManager, never()).notify(any(), eq(NO_INTERNET.eventId), any()); + } + + private void doNotificationTextTest(NotificationType type, @StringRes int expectedTitleRes, + String expectedTitleArg, @StringRes int expectedContentRes) { + final String expectedTitle = "title " + expectedTitleArg; + final String expectedContent = "expected content"; + doReturn(expectedTitle).when(mResources).getString(expectedTitleRes, expectedTitleArg); + doReturn(expectedContent).when(mResources).getString(expectedContentRes); + + mManager.showNotification(TEST_NOTIF_ID, type, mWifiNai, mCellNai, null, false); + final ArgumentCaptor notifCap = ArgumentCaptor.forClass(Notification.class); + + verify(mNotificationManager).notify(eq(TEST_NOTIF_TAG), eq(type.eventId), + notifCap.capture()); + final Notification notif = notifCap.getValue(); + + assertEquals(expectedTitle, notif.extras.getString(Notification.EXTRA_TITLE)); + assertEquals(expectedContent, notif.extras.getString(Notification.EXTRA_TEXT)); + } + + @Test + public void testNotificationText_NoInternet() { + doNotificationTextTest(NO_INTERNET, + R.string.wifi_no_internet, TEST_EXTRA_INFO, + R.string.wifi_no_internet_detailed); + } + + @Test + public void testNotificationText_Partial() { + doNotificationTextTest(PARTIAL_CONNECTIVITY, + R.string.network_partial_connectivity, TEST_EXTRA_INFO, + R.string.network_partial_connectivity_detailed); + } + + @Test + public void testNotificationText_PartialAsNoInternet() { + doReturn(true).when(mResources).getBoolean( + R.bool.config_partialConnectivityNotifiedAsNoInternet); + doNotificationTextTest(PARTIAL_CONNECTIVITY, + R.string.wifi_no_internet, TEST_EXTRA_INFO, + R.string.wifi_no_internet_detailed); + } }