| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server; |
| |
| import static android.Manifest.permission.CHANGE_NETWORK_STATE; |
| import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS; |
| import static android.Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE; |
| import static android.Manifest.permission.CREATE_USERS; |
| import static android.Manifest.permission.DUMP; |
| import static android.Manifest.permission.GET_INTENT_SENDER_INTENT; |
| import static android.Manifest.permission.LOCAL_MAC_ADDRESS; |
| import static android.Manifest.permission.NETWORK_FACTORY; |
| import static android.Manifest.permission.NETWORK_SETTINGS; |
| import static android.Manifest.permission.NETWORK_STACK; |
| import static android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD; |
| import static android.app.PendingIntent.FLAG_IMMUTABLE; |
| import static android.content.Intent.ACTION_PACKAGE_ADDED; |
| import static android.content.Intent.ACTION_PACKAGE_REMOVED; |
| import static android.content.Intent.ACTION_PACKAGE_REPLACED; |
| import static android.content.Intent.ACTION_USER_ADDED; |
| import static android.content.Intent.ACTION_USER_REMOVED; |
| import static android.content.Intent.ACTION_USER_UNLOCKED; |
| import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED; |
| import static android.content.pm.PackageManager.FEATURE_WIFI; |
| import static android.content.pm.PackageManager.FEATURE_WIFI_DIRECT; |
| import static android.content.pm.PackageManager.GET_PERMISSIONS; |
| import static android.content.pm.PackageManager.MATCH_ANY_USER; |
| import static android.content.pm.PackageManager.PERMISSION_DENIED; |
| import static android.content.pm.PackageManager.PERMISSION_GRANTED; |
| import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN; |
| import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER; |
| import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_MASK; |
| import static android.net.ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED; |
| import static android.net.ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER; |
| import static android.net.ConnectivityManager.BLOCKED_REASON_NONE; |
| import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; |
| import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO; |
| import static android.net.ConnectivityManager.EXTRA_NETWORK_TYPE; |
| import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT; |
| import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE; |
| import static android.net.ConnectivityManager.TYPE_ETHERNET; |
| import static android.net.ConnectivityManager.TYPE_MOBILE; |
| import static android.net.ConnectivityManager.TYPE_MOBILE_FOTA; |
| import static android.net.ConnectivityManager.TYPE_MOBILE_MMS; |
| import static android.net.ConnectivityManager.TYPE_MOBILE_SUPL; |
| import static android.net.ConnectivityManager.TYPE_PROXY; |
| import static android.net.ConnectivityManager.TYPE_VPN; |
| import static android.net.ConnectivityManager.TYPE_WIFI; |
| import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF; |
| import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC; |
| import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME; |
| 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_BIP; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_IA; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_VSIM; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P; |
| import static android.net.NetworkCapabilities.NET_CAPABILITY_XCAP; |
| import static android.net.NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION; |
| import static android.net.NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS; |
| import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS; |
| import static android.net.NetworkCapabilities.REDACT_NONE; |
| import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; |
| import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; |
| import static android.net.NetworkCapabilities.TRANSPORT_VPN; |
| import static android.net.NetworkCapabilities.TRANSPORT_WIFI; |
| import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; |
| import static android.net.NetworkScore.KEEP_CONNECTED_FOR_HANDOVER; |
| import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID; |
| import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK; |
| import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; |
| import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; |
| import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST; |
| import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST_ONLY; |
| import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED; |
| import static android.net.RouteInfo.RTN_UNREACHABLE; |
| import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_ADDED; |
| import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_REMOVED; |
| import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE; |
| import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_SUCCESS; |
| import static android.os.Process.INVALID_UID; |
| import static android.system.OsConstants.IPPROTO_TCP; |
| |
| import static com.android.server.ConnectivityService.MAX_NETWORK_REQUESTS_PER_SYSTEM_UID; |
| import static com.android.server.ConnectivityService.PREFERENCE_PRIORITY_MOBILE_DATA_PREFERERRED; |
| import static com.android.server.ConnectivityService.PREFERENCE_PRIORITY_OEM; |
| import static com.android.server.ConnectivityService.PREFERENCE_PRIORITY_PROFILE; |
| import static com.android.server.ConnectivityService.PREFERENCE_PRIORITY_VPN; |
| import static com.android.server.ConnectivityServiceTestUtils.transportToLegacyType; |
| import static com.android.testutils.ConcurrentUtils.await; |
| import static com.android.testutils.ConcurrentUtils.durationOf; |
| import static com.android.testutils.ExceptionUtils.ignoreExceptions; |
| import static com.android.testutils.HandlerUtils.waitForIdleSerialExecutor; |
| import static com.android.testutils.MiscAsserts.assertContainsAll; |
| import static com.android.testutils.MiscAsserts.assertContainsExactly; |
| import static com.android.testutils.MiscAsserts.assertEmpty; |
| import static com.android.testutils.MiscAsserts.assertLength; |
| import static com.android.testutils.MiscAsserts.assertRunsInAtMost; |
| import static com.android.testutils.MiscAsserts.assertSameElements; |
| import static com.android.testutils.MiscAsserts.assertThrows; |
| import static com.android.testutils.TestPermissionUtil.runAsShell; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| import static org.mockito.AdditionalMatchers.aryEq; |
| import static org.mockito.ArgumentMatchers.anyBoolean; |
| import static org.mockito.ArgumentMatchers.anyLong; |
| import static org.mockito.ArgumentMatchers.anyString; |
| import static org.mockito.ArgumentMatchers.argThat; |
| import static org.mockito.ArgumentMatchers.eq; |
| import static org.mockito.ArgumentMatchers.isNull; |
| import static org.mockito.ArgumentMatchers.startsWith; |
| import static org.mockito.Matchers.anyInt; |
| import static org.mockito.Mockito.any; |
| import static org.mockito.Mockito.atLeastOnce; |
| import static org.mockito.Mockito.doAnswer; |
| import static org.mockito.Mockito.doNothing; |
| import static org.mockito.Mockito.doReturn; |
| import static org.mockito.Mockito.doThrow; |
| import static org.mockito.Mockito.inOrder; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.never; |
| import static org.mockito.Mockito.reset; |
| import static org.mockito.Mockito.spy; |
| import static org.mockito.Mockito.timeout; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.verifyNoMoreInteractions; |
| import static org.mockito.Mockito.when; |
| |
| import android.Manifest; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.app.AlarmManager; |
| import android.app.AppOpsManager; |
| import android.app.NotificationManager; |
| import android.app.PendingIntent; |
| import android.app.usage.NetworkStatsManager; |
| import android.content.BroadcastReceiver; |
| import android.content.ComponentName; |
| import android.content.ContentProvider; |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ResolveInfo; |
| import android.content.pm.ServiceInfo; |
| import android.content.pm.UserInfo; |
| import android.content.res.Resources; |
| import android.location.LocationManager; |
| import android.net.CaptivePortalData; |
| import android.net.ConnectionInfo; |
| import android.net.ConnectivityManager; |
| import android.net.ConnectivityManager.NetworkCallback; |
| import android.net.ConnectivityManager.PacketKeepalive; |
| import android.net.ConnectivityManager.PacketKeepaliveCallback; |
| import android.net.ConnectivityManager.TooManyRequestsException; |
| import android.net.ConnectivityResources; |
| import android.net.ConnectivitySettingsManager; |
| import android.net.ConnectivityThread; |
| import android.net.DataStallReportParcelable; |
| import android.net.EthernetManager; |
| import android.net.IConnectivityDiagnosticsCallback; |
| import android.net.IDnsResolver; |
| import android.net.INetd; |
| import android.net.INetworkMonitor; |
| import android.net.INetworkMonitorCallbacks; |
| import android.net.IOnCompleteListener; |
| import android.net.IQosCallback; |
| import android.net.InetAddresses; |
| import android.net.InterfaceConfigurationParcel; |
| import android.net.IpPrefix; |
| import android.net.IpSecManager; |
| import android.net.IpSecManager.UdpEncapsulationSocket; |
| import android.net.LinkAddress; |
| import android.net.LinkProperties; |
| import android.net.MatchAllNetworkSpecifier; |
| import android.net.NativeNetworkConfig; |
| import android.net.NativeNetworkType; |
| import android.net.Network; |
| import android.net.NetworkAgent; |
| import android.net.NetworkAgentConfig; |
| import android.net.NetworkCapabilities; |
| import android.net.NetworkFactory; |
| import android.net.NetworkInfo; |
| import android.net.NetworkInfo.DetailedState; |
| import android.net.NetworkPolicyManager; |
| import android.net.NetworkPolicyManager.NetworkPolicyCallback; |
| import android.net.NetworkRequest; |
| import android.net.NetworkScore; |
| import android.net.NetworkSpecifier; |
| import android.net.NetworkStack; |
| import android.net.NetworkStateSnapshot; |
| import android.net.NetworkTestResultParcelable; |
| import android.net.OemNetworkPreferences; |
| import android.net.ProxyInfo; |
| import android.net.QosCallbackException; |
| import android.net.QosFilter; |
| import android.net.QosSession; |
| import android.net.ResolverParamsParcel; |
| import android.net.RouteInfo; |
| import android.net.RouteInfoParcel; |
| import android.net.SocketKeepalive; |
| import android.net.TransportInfo; |
| import android.net.UidRange; |
| import android.net.UidRangeParcel; |
| import android.net.UnderlyingNetworkInfo; |
| import android.net.Uri; |
| import android.net.VpnManager; |
| import android.net.VpnTransportInfo; |
| import android.net.metrics.IpConnectivityLog; |
| import android.net.netd.aidl.NativeUidRangeConfig; |
| import android.net.networkstack.NetworkStackClientBase; |
| import android.net.resolv.aidl.Nat64PrefixEventParcel; |
| import android.net.resolv.aidl.PrivateDnsValidationEventParcel; |
| import android.net.shared.NetworkMonitorUtils; |
| import android.net.shared.PrivateDnsConfig; |
| import android.net.util.MultinetworkPolicyTracker; |
| import android.os.BadParcelableException; |
| import android.os.BatteryStatsManager; |
| import android.os.Binder; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.os.ConditionVariable; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.os.IBinder; |
| import android.os.INetworkManagementService; |
| import android.os.Looper; |
| import android.os.Messenger; |
| import android.os.Parcel; |
| import android.os.ParcelFileDescriptor; |
| import android.os.Parcelable; |
| import android.os.Process; |
| import android.os.RemoteException; |
| import android.os.ServiceSpecificException; |
| import android.os.SystemClock; |
| import android.os.SystemConfigManager; |
| import android.os.UserHandle; |
| import android.os.UserManager; |
| import android.provider.Settings; |
| import android.security.Credentials; |
| import android.system.Os; |
| import android.telephony.TelephonyManager; |
| import android.telephony.data.EpsBearerQosSessionAttributes; |
| import android.telephony.data.NrQosSessionAttributes; |
| import android.test.mock.MockContentResolver; |
| import android.text.TextUtils; |
| import android.util.ArraySet; |
| import android.util.Log; |
| import android.util.Pair; |
| import android.util.Range; |
| import android.util.SparseArray; |
| |
| import androidx.test.InstrumentationRegistry; |
| import androidx.test.filters.SmallTest; |
| |
| import com.android.connectivity.resources.R; |
| import com.android.internal.app.IBatteryStats; |
| import com.android.internal.net.VpnConfig; |
| import com.android.internal.net.VpnProfile; |
| import com.android.internal.util.ArrayUtils; |
| import com.android.internal.util.WakeupMessage; |
| import com.android.internal.util.test.BroadcastInterceptingContext; |
| import com.android.internal.util.test.FakeSettingsProvider; |
| import com.android.net.module.util.ArrayTrackRecord; |
| import com.android.net.module.util.CollectionUtils; |
| import com.android.net.module.util.LocationPermissionChecker; |
| import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo; |
| import com.android.server.ConnectivityService.NetworkRequestInfo; |
| import com.android.server.connectivity.MockableSystemProperties; |
| import com.android.server.connectivity.Nat464Xlat; |
| import com.android.server.connectivity.NetworkAgentInfo; |
| import com.android.server.connectivity.NetworkNotificationManager.NotificationType; |
| import com.android.server.connectivity.ProxyTracker; |
| import com.android.server.connectivity.QosCallbackTracker; |
| import com.android.server.connectivity.Vpn; |
| import com.android.server.connectivity.VpnProfileStore; |
| import com.android.server.net.NetworkPinner; |
| import com.android.testutils.DevSdkIgnoreRule; |
| import com.android.testutils.DevSdkIgnoreRunner; |
| import com.android.testutils.ExceptionUtils; |
| import com.android.testutils.HandlerUtils; |
| import com.android.testutils.RecorderCallback.CallbackEntry; |
| import com.android.testutils.TestableNetworkCallback; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Ignore; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.AdditionalAnswers; |
| import org.mockito.ArgumentCaptor; |
| import org.mockito.InOrder; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| import org.mockito.Spy; |
| import org.mockito.stubbing.Answer; |
| |
| import java.io.FileDescriptor; |
| import java.io.IOException; |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.net.DatagramSocket; |
| import java.net.Inet4Address; |
| import java.net.Inet6Address; |
| import java.net.InetAddress; |
| import java.net.InetSocketAddress; |
| import java.net.Socket; |
| import java.nio.charset.StandardCharsets; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Objects; |
| import java.util.Set; |
| import java.util.concurrent.CompletableFuture; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.Executor; |
| import java.util.concurrent.ExecutorService; |
| import java.util.concurrent.Executors; |
| import java.util.concurrent.LinkedBlockingQueue; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.TimeoutException; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.concurrent.atomic.AtomicReference; |
| import java.util.function.Predicate; |
| import java.util.function.Supplier; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import java.util.stream.Collectors; |
| |
| import kotlin.reflect.KClass; |
| |
| /** |
| * Tests for {@link ConnectivityService}. |
| * |
| * Build, install and run with: |
| * runtest frameworks-net -c com.android.server.ConnectivityServiceTest |
| */ |
| @RunWith(DevSdkIgnoreRunner.class) |
| @SmallTest |
| @DevSdkIgnoreRule.IgnoreUpTo(Build.VERSION_CODES.R) |
| public class ConnectivityServiceTest { |
| private static final String TAG = "ConnectivityServiceTest"; |
| |
| private static final int TIMEOUT_MS = 2_000; |
| // Broadcasts can take a long time to be delivered. The test will not wait for that long unless |
| // there is a failure, so use a long timeout. |
| private static final int BROADCAST_TIMEOUT_MS = 30_000; |
| private static final int TEST_LINGER_DELAY_MS = 400; |
| private static final int TEST_NASCENT_DELAY_MS = 300; |
| // Chosen to be less than the linger and nascent timeout. This ensures that we can distinguish |
| // between a LOST callback that arrives immediately and a LOST callback that arrives after |
| // the linger/nascent timeout. For this, our assertions should run fast enough to leave |
| // less than (mService.mLingerDelayMs - TEST_CALLBACK_TIMEOUT_MS) between the time callbacks are |
| // supposedly fired, and the time we call expectCallback. |
| private static final int TEST_CALLBACK_TIMEOUT_MS = 250; |
| // Chosen to be less than TEST_CALLBACK_TIMEOUT_MS. This ensures that requests have time to |
| // complete before callbacks are verified. |
| private static final int TEST_REQUEST_TIMEOUT_MS = 150; |
| |
| private static final int UNREASONABLY_LONG_ALARM_WAIT_MS = 1000; |
| |
| private static final long TIMESTAMP = 1234L; |
| |
| private static final int NET_ID = 110; |
| private static final int OEM_PREF_ANY_NET_ID = -1; |
| // Set a non-zero value to verify the flow to set tcp init rwnd value. |
| private static final int TEST_TCP_INIT_RWND = 60; |
| |
| // Used for testing the per-work-profile default network. |
| private static final int TEST_APP_ID = 103; |
| private static final int TEST_WORK_PROFILE_USER_ID = 2; |
| private static final int TEST_WORK_PROFILE_APP_UID = |
| UserHandle.getUid(TEST_WORK_PROFILE_USER_ID, TEST_APP_ID); |
| private static final String CLAT_PREFIX = "v4-"; |
| private static final String MOBILE_IFNAME = "test_rmnet_data0"; |
| private static final String CLAT_MOBILE_IFNAME = CLAT_PREFIX + MOBILE_IFNAME; |
| private static final String WIFI_IFNAME = "test_wlan0"; |
| private static final String WIFI_WOL_IFNAME = "test_wlan_wol"; |
| private static final String VPN_IFNAME = "tun10042"; |
| private static final String TEST_PACKAGE_NAME = "com.android.test.package"; |
| private static final int TEST_PACKAGE_UID = 123; |
| private static final int TEST_PACKAGE_UID2 = 321; |
| private static final int TEST_PACKAGE_UID3 = 456; |
| private static final String ALWAYS_ON_PACKAGE = "com.android.test.alwaysonvpn"; |
| |
| private static final String INTERFACE_NAME = "interface"; |
| |
| private static final String TEST_VENUE_URL_NA_PASSPOINT = "https://android.com/"; |
| private static final String TEST_VENUE_URL_NA_OTHER = "https://example.com/"; |
| private static final String TEST_TERMS_AND_CONDITIONS_URL_NA_PASSPOINT = |
| "https://android.com/terms/"; |
| private static final String TEST_TERMS_AND_CONDITIONS_URL_NA_OTHER = |
| "https://example.com/terms/"; |
| private static final String TEST_VENUE_URL_CAPPORT = "https://android.com/capport/"; |
| private static final String TEST_USER_PORTAL_API_URL_CAPPORT = |
| "https://android.com/user/api/capport/"; |
| private static final String TEST_FRIENDLY_NAME = "Network friendly name"; |
| private static final String TEST_REDIRECT_URL = "http://example.com/firstPath"; |
| |
| private MockContext mServiceContext; |
| private HandlerThread mCsHandlerThread; |
| private HandlerThread mVMSHandlerThread; |
| private ConnectivityService.Dependencies mDeps; |
| private ConnectivityService mService; |
| private WrappedConnectivityManager mCm; |
| private TestNetworkAgentWrapper mWiFiNetworkAgent; |
| private TestNetworkAgentWrapper mCellNetworkAgent; |
| private TestNetworkAgentWrapper mEthernetNetworkAgent; |
| private MockVpn mMockVpn; |
| private Context mContext; |
| private NetworkPolicyCallback mPolicyCallback; |
| private WrappedMultinetworkPolicyTracker mPolicyTracker; |
| private HandlerThread mAlarmManagerThread; |
| private TestNetIdManager mNetIdManager; |
| private QosCallbackMockHelper mQosCallbackMockHelper; |
| private QosCallbackTracker mQosCallbackTracker; |
| private VpnManagerService mVpnManagerService; |
| private TestNetworkCallback mDefaultNetworkCallback; |
| private TestNetworkCallback mSystemDefaultNetworkCallback; |
| private TestNetworkCallback mProfileDefaultNetworkCallback; |
| private TestNetworkCallback mTestPackageDefaultNetworkCallback; |
| |
| // State variables required to emulate NetworkPolicyManagerService behaviour. |
| private int mBlockedReasons = BLOCKED_REASON_NONE; |
| |
| @Mock DeviceIdleInternal mDeviceIdleInternal; |
| @Mock INetworkManagementService mNetworkManagementService; |
| @Mock NetworkStatsManager mStatsManager; |
| @Mock IDnsResolver mMockDnsResolver; |
| @Mock INetd mMockNetd; |
| @Mock NetworkStackClientBase mNetworkStack; |
| @Mock PackageManager mPackageManager; |
| @Mock UserManager mUserManager; |
| @Mock NotificationManager mNotificationManager; |
| @Mock AlarmManager mAlarmManager; |
| @Mock IConnectivityDiagnosticsCallback mConnectivityDiagnosticsCallback; |
| @Mock IBinder mIBinder; |
| @Mock LocationManager mLocationManager; |
| @Mock AppOpsManager mAppOpsManager; |
| @Mock TelephonyManager mTelephonyManager; |
| @Mock MockableSystemProperties mSystemProperties; |
| @Mock EthernetManager mEthernetManager; |
| @Mock NetworkPolicyManager mNetworkPolicyManager; |
| @Mock VpnProfileStore mVpnProfileStore; |
| @Mock SystemConfigManager mSystemConfigManager; |
| @Mock Resources mResources; |
| @Mock ProxyTracker mProxyTracker; |
| |
| // BatteryStatsManager is final and cannot be mocked with regular mockito, so just mock the |
| // underlying binder calls. |
| final BatteryStatsManager mBatteryStatsManager = |
| new BatteryStatsManager(mock(IBatteryStats.class)); |
| |
| private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor = |
| ArgumentCaptor.forClass(ResolverParamsParcel.class); |
| |
| // This class exists to test bindProcessToNetwork and getBoundNetworkForProcess. These methods |
| // do not go through ConnectivityService but talk to netd directly, so they don't automatically |
| // reflect the state of our test ConnectivityService. |
| private class WrappedConnectivityManager extends ConnectivityManager { |
| private Network mFakeBoundNetwork; |
| |
| public synchronized boolean bindProcessToNetwork(Network network) { |
| mFakeBoundNetwork = network; |
| return true; |
| } |
| |
| public synchronized Network getBoundNetworkForProcess() { |
| return mFakeBoundNetwork; |
| } |
| |
| public WrappedConnectivityManager(Context context, ConnectivityService service) { |
| super(context, service); |
| } |
| } |
| |
| private class MockContext extends BroadcastInterceptingContext { |
| private final MockContentResolver mContentResolver; |
| |
| @Spy private Resources mInternalResources; |
| private final LinkedBlockingQueue<Intent> 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 "<permission name>,<pid>,<uid>". PID+UID permissons have priority over generic ones. |
| private final HashMap<String, Integer> mMockedPermissions = new HashMap<>(); |
| |
| MockContext(Context base, ContentProvider settingsProvider) { |
| super(base); |
| |
| mInternalResources = spy(base.getResources()); |
| when(mInternalResources.getStringArray(com.android.internal.R.array.networkAttributes)) |
| .thenReturn(new String[] { |
| "wifi,1,1,1,-1,true", |
| "mobile,0,0,0,-1,true", |
| "mobile_mms,2,0,2,60000,true", |
| "mobile_supl,3,0,2,60000,true", |
| }); |
| |
| mContentResolver = new MockContentResolver(); |
| mContentResolver.addProvider(Settings.AUTHORITY, settingsProvider); |
| } |
| |
| @Override |
| public void startActivityAsUser(Intent intent, UserHandle handle) { |
| mStartedActivities.offer(intent); |
| } |
| |
| public Intent expectStartActivityIntent(int timeoutMs) { |
| Intent intent = null; |
| try { |
| intent = mStartedActivities.poll(timeoutMs, TimeUnit.MILLISECONDS); |
| } catch (InterruptedException e) {} |
| assertNotNull("Did not receive sign-in intent after " + timeoutMs + "ms", intent); |
| return intent; |
| } |
| |
| public void expectNoStartActivityIntent(int timeoutMs) { |
| try { |
| assertNull("Received unexpected Intent to start activity", |
| mStartedActivities.poll(timeoutMs, TimeUnit.MILLISECONDS)); |
| } catch (InterruptedException e) {} |
| } |
| |
| @Override |
| public ComponentName startService(Intent service) { |
| final String action = service.getAction(); |
| if (!VpnConfig.SERVICE_INTERFACE.equals(action)) { |
| fail("Attempt to start unknown service, action=" + action); |
| } |
| return new ComponentName(service.getPackage(), "com.android.test.Service"); |
| } |
| |
| @Override |
| public Object getSystemService(String name) { |
| if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm; |
| if (Context.NOTIFICATION_SERVICE.equals(name)) return mNotificationManager; |
| if (Context.USER_SERVICE.equals(name)) return mUserManager; |
| if (Context.ALARM_SERVICE.equals(name)) return mAlarmManager; |
| if (Context.LOCATION_SERVICE.equals(name)) return mLocationManager; |
| if (Context.APP_OPS_SERVICE.equals(name)) return mAppOpsManager; |
| if (Context.TELEPHONY_SERVICE.equals(name)) return mTelephonyManager; |
| if (Context.ETHERNET_SERVICE.equals(name)) return mEthernetManager; |
| if (Context.NETWORK_POLICY_SERVICE.equals(name)) return mNetworkPolicyManager; |
| if (Context.SYSTEM_CONFIG_SERVICE.equals(name)) return mSystemConfigManager; |
| if (Context.NETWORK_STATS_SERVICE.equals(name)) return mStatsManager; |
| if (Context.BATTERY_STATS_SERVICE.equals(name)) return mBatteryStatsManager; |
| return super.getSystemService(name); |
| } |
| |
| final HashMap<UserHandle, UserManager> mUserManagers = new HashMap<>(); |
| @Override |
| public Context createContextAsUser(UserHandle user, int flags) { |
| final Context asUser = mock(Context.class, AdditionalAnswers.delegatesTo(this)); |
| doReturn(user).when(asUser).getUser(); |
| doAnswer((inv) -> { |
| final UserManager um = mUserManagers.computeIfAbsent(user, |
| u -> mock(UserManager.class, AdditionalAnswers.delegatesTo(mUserManager))); |
| return um; |
| }).when(asUser).getSystemService(Context.USER_SERVICE); |
| return asUser; |
| } |
| |
| public void setWorkProfile(@NonNull final UserHandle userHandle, boolean value) { |
| // This relies on all contexts for a given user returning the same UM mock |
| final UserManager umMock = createContextAsUser(userHandle, 0 /* flags */) |
| .getSystemService(UserManager.class); |
| doReturn(value).when(umMock).isManagedProfile(); |
| doReturn(value).when(mUserManager).isManagedProfile(eq(userHandle.getIdentifier())); |
| } |
| |
| @Override |
| public ContentResolver getContentResolver() { |
| return mContentResolver; |
| } |
| |
| @Override |
| public Resources getResources() { |
| return mInternalResources; |
| } |
| |
| @Override |
| public PackageManager getPackageManager() { |
| return mPackageManager; |
| } |
| |
| private int checkMockedPermission(String permission, int pid, int uid, |
| Supplier<Integer> 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, pid, uid, |
| () -> super.checkPermission(permission, pid, uid)); |
| } |
| |
| @Override |
| public int checkCallingOrSelfPermission(String permission) { |
| return checkMockedPermission(permission, Process.myPid(), Process.myUid(), |
| () -> super.checkCallingOrSelfPermission(permission)); |
| } |
| |
| @Override |
| public void enforceCallingOrSelfPermission(String permission, String message) { |
| 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); |
| } |
| } |
| |
| /** |
| * 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. |
| * |
| * <p>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, Integer granted) { |
| 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. |
| * |
| * <p>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, |
| @Nullable Handler scheduler) { |
| // TODO: ensure MultinetworkPolicyTracker's BroadcastReceiver is tested; just returning |
| // null should not pass the test |
| return null; |
| } |
| } |
| |
| private void waitForIdle() { |
| HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS); |
| waitForIdle(mCellNetworkAgent, TIMEOUT_MS); |
| waitForIdle(mWiFiNetworkAgent, TIMEOUT_MS); |
| waitForIdle(mEthernetNetworkAgent, TIMEOUT_MS); |
| HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS); |
| HandlerUtils.waitForIdle(ConnectivityThread.get(), TIMEOUT_MS); |
| } |
| |
| private void waitForIdle(TestNetworkAgentWrapper agent, long timeoutMs) { |
| if (agent == null) { |
| return; |
| } |
| agent.waitForIdle(timeoutMs); |
| } |
| |
| @Test |
| public void testWaitForIdle() throws Exception { |
| final int attempts = 50; // Causes the test to take about 200ms on bullhead-eng. |
| |
| // Tests that waitForIdle returns immediately if the service is already idle. |
| for (int i = 0; i < attempts; i++) { |
| waitForIdle(); |
| } |
| |
| // Bring up a network that we can use to send messages to ConnectivityService. |
| ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(false); |
| b.expectBroadcast(); |
| Network n = mWiFiNetworkAgent.getNetwork(); |
| assertNotNull(n); |
| |
| // Tests that calling waitForIdle waits for messages to be processed. |
| for (int i = 0; i < attempts; i++) { |
| mWiFiNetworkAgent.setSignalStrength(i); |
| waitForIdle(); |
| assertEquals(i, mCm.getNetworkCapabilities(n).getSignalStrength()); |
| } |
| } |
| |
| // This test has an inherent race condition in it, and cannot be enabled for continuous testing |
| // or presubmit tests. It is kept for manual runs and documentation purposes. |
| @Ignore |
| public void verifyThatNotWaitingForIdleCausesRaceConditions() throws Exception { |
| // Bring up a network that we can use to send messages to ConnectivityService. |
| ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(false); |
| b.expectBroadcast(); |
| Network n = mWiFiNetworkAgent.getNetwork(); |
| assertNotNull(n); |
| |
| // Ensure that not calling waitForIdle causes a race condition. |
| final int attempts = 50; // Causes the test to take about 200ms on bullhead-eng. |
| for (int i = 0; i < attempts; i++) { |
| mWiFiNetworkAgent.setSignalStrength(i); |
| if (i != mCm.getNetworkCapabilities(n).getSignalStrength()) { |
| // We hit a race condition, as expected. Pass the test. |
| return; |
| } |
| } |
| |
| // No race? There is a bug in this test. |
| fail("expected race condition at least once in " + attempts + " attempts"); |
| } |
| |
| private class TestNetworkAgentWrapper extends NetworkAgentWrapper { |
| private static final int VALIDATION_RESULT_INVALID = 0; |
| |
| private static final long DATA_STALL_TIMESTAMP = 10L; |
| private static final int DATA_STALL_DETECTION_METHOD = 1; |
| |
| private INetworkMonitor mNetworkMonitor; |
| private INetworkMonitorCallbacks mNmCallbacks; |
| private int mNmValidationResult = VALIDATION_RESULT_INVALID; |
| private int mProbesCompleted; |
| private int mProbesSucceeded; |
| private String mNmValidationRedirectUrl = null; |
| private boolean mNmProvNotificationRequested = false; |
| private Runnable mCreatedCallback; |
| private Runnable mUnwantedCallback; |
| private Runnable mDisconnectedCallback; |
| |
| private final ConditionVariable mNetworkStatusReceived = new ConditionVariable(); |
| // Contains the redirectUrl from networkStatus(). Before reading, wait for |
| // mNetworkStatusReceived. |
| private String mRedirectUrl; |
| |
| TestNetworkAgentWrapper(int transport) throws Exception { |
| this(transport, new LinkProperties(), null); |
| } |
| |
| TestNetworkAgentWrapper(int transport, LinkProperties linkProperties) |
| throws Exception { |
| this(transport, linkProperties, null); |
| } |
| |
| private TestNetworkAgentWrapper(int transport, LinkProperties linkProperties, |
| NetworkCapabilities ncTemplate) throws Exception { |
| super(transport, linkProperties, ncTemplate, mServiceContext); |
| |
| // Waits for the NetworkAgent to be registered, which includes the creation of the |
| // NetworkMonitor. |
| waitForIdle(TIMEOUT_MS); |
| HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS); |
| HandlerUtils.waitForIdle(ConnectivityThread.get(), TIMEOUT_MS); |
| } |
| |
| @Override |
| protected InstrumentedNetworkAgent makeNetworkAgent(LinkProperties linkProperties, |
| NetworkAgentConfig nac) throws Exception { |
| mNetworkMonitor = mock(INetworkMonitor.class); |
| |
| final Answer validateAnswer = inv -> { |
| new Thread(ignoreExceptions(this::onValidationRequested)).start(); |
| return null; |
| }; |
| |
| doAnswer(validateAnswer).when(mNetworkMonitor).notifyNetworkConnected(any(), any()); |
| doAnswer(validateAnswer).when(mNetworkMonitor).forceReevaluation(anyInt()); |
| |
| final ArgumentCaptor<Network> nmNetworkCaptor = ArgumentCaptor.forClass(Network.class); |
| final ArgumentCaptor<INetworkMonitorCallbacks> nmCbCaptor = |
| ArgumentCaptor.forClass(INetworkMonitorCallbacks.class); |
| doNothing().when(mNetworkStack).makeNetworkMonitor( |
| nmNetworkCaptor.capture(), |
| any() /* name */, |
| nmCbCaptor.capture()); |
| |
| final InstrumentedNetworkAgent na = |
| new InstrumentedNetworkAgent(this, linkProperties, nac) { |
| @Override |
| public void networkStatus(int status, String redirectUrl) { |
| mRedirectUrl = redirectUrl; |
| mNetworkStatusReceived.open(); |
| } |
| |
| @Override |
| public void onNetworkCreated() { |
| super.onNetworkCreated(); |
| if (mCreatedCallback != null) mCreatedCallback.run(); |
| } |
| |
| @Override |
| public void onNetworkUnwanted() { |
| super.onNetworkUnwanted(); |
| if (mUnwantedCallback != null) mUnwantedCallback.run(); |
| } |
| |
| @Override |
| public void onNetworkDestroyed() { |
| super.onNetworkDestroyed(); |
| if (mDisconnectedCallback != null) mDisconnectedCallback.run(); |
| } |
| }; |
| |
| assertEquals(na.getNetwork().netId, nmNetworkCaptor.getValue().netId); |
| mNmCallbacks = nmCbCaptor.getValue(); |
| |
| mNmCallbacks.onNetworkMonitorCreated(mNetworkMonitor); |
| |
| return na; |
| } |
| |
| private void onValidationRequested() throws Exception { |
| if (mNmProvNotificationRequested |
| && ((mNmValidationResult & NETWORK_VALIDATION_RESULT_VALID) != 0)) { |
| mNmCallbacks.hideProvisioningNotification(); |
| mNmProvNotificationRequested = false; |
| } |
| |
| mNmCallbacks.notifyProbeStatusChanged(mProbesCompleted, mProbesSucceeded); |
| final NetworkTestResultParcelable p = new NetworkTestResultParcelable(); |
| p.result = mNmValidationResult; |
| p.probesAttempted = mProbesCompleted; |
| p.probesSucceeded = mProbesSucceeded; |
| p.redirectUrl = mNmValidationRedirectUrl; |
| p.timestampMillis = TIMESTAMP; |
| mNmCallbacks.notifyNetworkTestedWithExtras(p); |
| |
| if (mNmValidationRedirectUrl != null) { |
| mNmCallbacks.showProvisioningNotification( |
| "test_provisioning_notif_action", TEST_PACKAGE_NAME); |
| mNmProvNotificationRequested = true; |
| } |
| } |
| |
| /** |
| * Connect without adding any internet capability. |
| */ |
| public void connectWithoutInternet() { |
| super.connect(); |
| } |
| |
| /** |
| * Transition this NetworkAgent to CONNECTED state with NET_CAPABILITY_INTERNET. |
| * @param validated Indicate if network should pretend to be validated. |
| */ |
| public void connect(boolean validated) { |
| connect(validated, true, false /* isStrictMode */); |
| } |
| |
| /** |
| * Transition this NetworkAgent to CONNECTED state. |
| * @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, boolean isStrictMode) { |
| assertFalse(getNetworkCapabilities().hasCapability(NET_CAPABILITY_INTERNET)); |
| |
| ConnectivityManager.NetworkCallback callback = null; |
| final ConditionVariable validatedCv = new ConditionVariable(); |
| if (validated) { |
| setNetworkValid(isStrictMode); |
| NetworkRequest request = new NetworkRequest.Builder() |
| .addTransportType(getNetworkCapabilities().getTransportTypes()[0]) |
| .clearCapabilities() |
| .build(); |
| callback = new ConnectivityManager.NetworkCallback() { |
| public void onCapabilitiesChanged(Network network, |
| NetworkCapabilities networkCapabilities) { |
| if (network.equals(getNetwork()) && |
| networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)) { |
| validatedCv.open(); |
| } |
| } |
| }; |
| mCm.registerNetworkCallback(request, callback); |
| } |
| if (hasInternet) { |
| addCapability(NET_CAPABILITY_INTERNET); |
| } |
| |
| connectWithoutInternet(); |
| |
| if (validated) { |
| // Wait for network to validate. |
| waitFor(validatedCv); |
| setNetworkInvalid(isStrictMode); |
| } |
| |
| if (callback != null) mCm.unregisterNetworkCallback(callback); |
| } |
| |
| public void connectWithCaptivePortal(String redirectUrl, boolean isStrictMode) { |
| setNetworkPortal(redirectUrl, isStrictMode); |
| connect(false, true /* hasInternet */, isStrictMode); |
| } |
| |
| public void connectWithPartialConnectivity() { |
| setNetworkPartial(); |
| connect(false); |
| } |
| |
| public void connectWithPartialValidConnectivity(boolean isStrictMode) { |
| setNetworkPartialValid(isStrictMode); |
| connect(false, true /* hasInternet */, isStrictMode); |
| } |
| |
| void setNetworkValid(boolean isStrictMode) { |
| mNmValidationResult = NETWORK_VALIDATION_RESULT_VALID; |
| mNmValidationRedirectUrl = null; |
| int probesSucceeded = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS; |
| 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(boolean isStrictMode) { |
| mNmValidationResult = VALIDATION_RESULT_INVALID; |
| mNmValidationRedirectUrl = null; |
| int probesCompleted = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS |
| | NETWORK_VALIDATION_PROBE_HTTP; |
| int probesSucceeded = 0; |
| // 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, 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 = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP; |
| int probesSucceeded = VALIDATION_RESULT_INVALID; |
| if (isStrictMode) { |
| probesCompleted |= NETWORK_VALIDATION_PROBE_PRIVDNS; |
| } |
| setProbesStatus(probesCompleted, probesSucceeded); |
| } |
| |
| void setNetworkPartial() { |
| mNmValidationResult = NETWORK_VALIDATION_RESULT_PARTIAL; |
| mNmValidationRedirectUrl = null; |
| int probesCompleted = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS |
| | NETWORK_VALIDATION_PROBE_FALLBACK; |
| int probesSucceeded = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_FALLBACK; |
| setProbesStatus(probesCompleted, probesSucceeded); |
| } |
| |
| void setNetworkPartialValid(boolean isStrictMode) { |
| setNetworkPartial(); |
| mNmValidationResult |= NETWORK_VALIDATION_RESULT_VALID; |
| int probesCompleted = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTPS |
| | NETWORK_VALIDATION_PROBE_HTTP; |
| int probesSucceeded = NETWORK_VALIDATION_PROBE_DNS | NETWORK_VALIDATION_PROBE_HTTP; |
| // 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; |
| } |
| |
| void notifyCapportApiDataChanged(CaptivePortalData data) { |
| try { |
| mNmCallbacks.notifyCaptivePortalDataChanged(data); |
| } catch (RemoteException e) { |
| throw new AssertionError("This cannot happen", e); |
| } |
| } |
| |
| public String waitForRedirectUrl() { |
| assertTrue(mNetworkStatusReceived.block(TIMEOUT_MS)); |
| return mRedirectUrl; |
| } |
| |
| public void expectDisconnected() { |
| expectDisconnected(TIMEOUT_MS); |
| } |
| |
| public void expectPreventReconnectReceived() { |
| expectPreventReconnectReceived(TIMEOUT_MS); |
| } |
| |
| void notifyDataStallSuspected() throws Exception { |
| final DataStallReportParcelable p = new DataStallReportParcelable(); |
| p.detectionMethod = DATA_STALL_DETECTION_METHOD; |
| p.timestampMillis = DATA_STALL_TIMESTAMP; |
| mNmCallbacks.notifyDataStallSuspected(p); |
| } |
| |
| public void setCreatedCallback(Runnable r) { |
| mCreatedCallback = r; |
| } |
| |
| public void setUnwantedCallback(Runnable r) { |
| mUnwantedCallback = r; |
| } |
| |
| public void setDisconnectedCallback(Runnable r) { |
| mDisconnectedCallback = r; |
| } |
| } |
| |
| /** |
| * A NetworkFactory that allows to wait until any in-flight NetworkRequest add or remove |
| * operations have been processed and test for them. |
| */ |
| private static class MockNetworkFactory extends NetworkFactory { |
| private final AtomicBoolean mNetworkStarted = new AtomicBoolean(false); |
| |
| static class RequestEntry { |
| @NonNull |
| public final NetworkRequest request; |
| |
| RequestEntry(@NonNull final NetworkRequest request) { |
| this.request = request; |
| } |
| |
| static final class Add extends RequestEntry { |
| Add(@NonNull final NetworkRequest request) { |
| super(request); |
| } |
| } |
| |
| static final class Remove extends RequestEntry { |
| Remove(@NonNull final NetworkRequest request) { |
| super(request); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return "RequestEntry [ " + getClass().getName() + " : " + request + " ]"; |
| } |
| } |
| |
| // History of received requests adds and removes. |
| private final ArrayTrackRecord<RequestEntry>.ReadHead mRequestHistory = |
| new ArrayTrackRecord<RequestEntry>().newReadHead(); |
| |
| private static <T> T failIfNull(@Nullable final T obj, @Nullable final String message) { |
| if (null == obj) fail(null != message ? message : "Must not be null"); |
| return obj; |
| } |
| |
| public RequestEntry.Add expectRequestAdd() { |
| return failIfNull((RequestEntry.Add) mRequestHistory.poll(TIMEOUT_MS, |
| it -> it instanceof RequestEntry.Add), "Expected request add"); |
| } |
| |
| public void expectRequestAdds(final int count) { |
| for (int i = count; i > 0; --i) { |
| expectRequestAdd(); |
| } |
| } |
| |
| public RequestEntry.Remove expectRequestRemove() { |
| return failIfNull((RequestEntry.Remove) mRequestHistory.poll(TIMEOUT_MS, |
| it -> it instanceof RequestEntry.Remove), "Expected request remove"); |
| } |
| |
| public void expectRequestRemoves(final int count) { |
| for (int i = count; i > 0; --i) { |
| expectRequestRemove(); |
| } |
| } |
| |
| // Used to collect the networks requests managed by this factory. This is a duplicate of |
| // the internal information stored in the NetworkFactory (which is private). |
| private SparseArray<NetworkRequest> mNetworkRequests = new SparseArray<>(); |
| private final HandlerThread mHandlerSendingRequests; |
| |
| public MockNetworkFactory(Looper looper, Context context, String logTag, |
| NetworkCapabilities filter, HandlerThread threadSendingRequests) { |
| super(looper, context, logTag, filter); |
| mHandlerSendingRequests = threadSendingRequests; |
| } |
| |
| public int getMyRequestCount() { |
| return getRequestCount(); |
| } |
| |
| protected void startNetwork() { |
| mNetworkStarted.set(true); |
| } |
| |
| protected void stopNetwork() { |
| mNetworkStarted.set(false); |
| } |
| |
| public boolean getMyStartRequested() { |
| return mNetworkStarted.get(); |
| } |
| |
| |
| @Override |
| protected void needNetworkFor(NetworkRequest request) { |
| mNetworkRequests.put(request.requestId, request); |
| super.needNetworkFor(request); |
| mRequestHistory.add(new RequestEntry.Add(request)); |
| } |
| |
| @Override |
| protected void releaseNetworkFor(NetworkRequest request) { |
| mNetworkRequests.remove(request.requestId); |
| super.releaseNetworkFor(request); |
| mRequestHistory.add(new RequestEntry.Remove(request)); |
| } |
| |
| public void assertRequestCountEquals(final int count) { |
| assertEquals(count, getMyRequestCount()); |
| } |
| |
| @Override |
| public void terminate() { |
| super.terminate(); |
| // Make sure there are no remaining requests unaccounted for. |
| HandlerUtils.waitForIdle(mHandlerSendingRequests, TIMEOUT_MS); |
| assertNull(mRequestHistory.poll(0, r -> true)); |
| } |
| |
| // Trigger releasing the request as unfulfillable |
| public void triggerUnfulfillable(NetworkRequest r) { |
| super.releaseRequestAsUnfulfillableByAnyFactory(r); |
| } |
| |
| public void assertNoRequestChanged() { |
| assertNull(mRequestHistory.poll(0, r -> true)); |
| } |
| } |
| |
| private Set<UidRange> uidRangesForUids(int... uids) { |
| final ArraySet<UidRange> ranges = new ArraySet<>(); |
| for (final int uid : uids) { |
| ranges.add(new UidRange(uid, uid)); |
| } |
| return ranges; |
| } |
| |
| private Set<UidRange> uidRangesForUids(Collection<Integer> uids) { |
| return uidRangesForUids(CollectionUtils.toIntArray(uids)); |
| } |
| |
| private static Looper startHandlerThreadAndReturnLooper() { |
| final HandlerThread handlerThread = new HandlerThread("MockVpnThread"); |
| handlerThread.start(); |
| return handlerThread.getLooper(); |
| } |
| |
| private class MockVpn extends Vpn implements TestableNetworkCallback.HasNetwork { |
| // Careful ! This is different from mNetworkAgent, because MockNetworkAgent does |
| // not inherit from NetworkAgent. |
| private TestNetworkAgentWrapper mMockNetworkAgent; |
| private boolean mAgentRegistered = false; |
| |
| private int mVpnType = VpnManager.TYPE_VPN_SERVICE; |
| private UnderlyingNetworkInfo mUnderlyingNetworkInfo; |
| |
| // These ConditionVariables allow tests to wait for LegacyVpnRunner to be stopped/started. |
| // TODO: this scheme is ad-hoc and error-prone because it does not fail if, for example, the |
| // test expects two starts in a row, or even if the production code calls start twice in a |
| // row. find a better solution. Simply putting a method to create a LegacyVpnRunner into |
| // Vpn.Dependencies doesn't work because LegacyVpnRunner is not a static class and has |
| // extensive access into the internals of Vpn. |
| private ConditionVariable mStartLegacyVpnCv = new ConditionVariable(); |
| private ConditionVariable mStopVpnRunnerCv = new ConditionVariable(); |
| |
| public MockVpn(int userId) { |
| super(startHandlerThreadAndReturnLooper(), mServiceContext, |
| new Dependencies() { |
| @Override |
| public boolean isCallerSystem() { |
| return true; |
| } |
| |
| @Override |
| public DeviceIdleInternal getDeviceIdleInternal() { |
| return mDeviceIdleInternal; |
| } |
| }, |
| mNetworkManagementService, mMockNetd, userId, mVpnProfileStore, |
| new SystemServices(mServiceContext) { |
| @Override |
| public String settingsSecureGetStringForUser(String key, int userId) { |
| switch (key) { |
| // Settings keys not marked as @Readable are not readable from |
| // non-privileged apps, unless marked as testOnly=true |
| // (atest refuses to install testOnly=true apps), even if mocked |
| // in the content provider, because |
| // Settings.Secure.NameValueCache#getStringForUser checks the key |
| // before querying the mock settings provider. |
| case Settings.Secure.ALWAYS_ON_VPN_APP: |
| return null; |
| default: |
| return super.settingsSecureGetStringForUser(key, userId); |
| } |
| } |
| }, new Ikev2SessionCreator()); |
| } |
| |
| public void setUids(Set<UidRange> uids) { |
| mNetworkCapabilities.setUids(UidRange.toIntRanges(uids)); |
| if (mAgentRegistered) { |
| mMockNetworkAgent.setNetworkCapabilities(mNetworkCapabilities, true); |
| } |
| } |
| |
| public void setVpnType(int vpnType) { |
| mVpnType = vpnType; |
| } |
| |
| @Override |
| public Network getNetwork() { |
| return (mMockNetworkAgent == null) ? null : mMockNetworkAgent.getNetwork(); |
| } |
| |
| @Override |
| public int getActiveVpnType() { |
| return mVpnType; |
| } |
| |
| private LinkProperties makeLinkProperties() { |
| final LinkProperties lp = new LinkProperties(); |
| lp.setInterfaceName(VPN_IFNAME); |
| return lp; |
| } |
| |
| private void registerAgent(boolean isAlwaysMetered, Set<UidRange> uids, LinkProperties lp) |
| throws Exception { |
| if (mAgentRegistered) throw new IllegalStateException("already registered"); |
| updateState(NetworkInfo.DetailedState.CONNECTING, "registerAgent"); |
| mConfig = new VpnConfig(); |
| mConfig.session = "MySession12345"; |
| setUids(uids); |
| if (!isAlwaysMetered) mNetworkCapabilities.addCapability(NET_CAPABILITY_NOT_METERED); |
| mInterface = VPN_IFNAME; |
| mNetworkCapabilities.setTransportInfo(new VpnTransportInfo(getActiveVpnType(), |
| mConfig.session)); |
| mMockNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_VPN, lp, |
| mNetworkCapabilities); |
| mMockNetworkAgent.waitForIdle(TIMEOUT_MS); |
| |
| verify(mMockNetd, times(1)).networkAddUidRangesParcel( |
| new NativeUidRangeConfig(mMockVpn.getNetwork().getNetId(), |
| toUidRangeStableParcels(uids), PREFERENCE_PRIORITY_VPN)); |
| verify(mMockNetd, never()).networkRemoveUidRangesParcel(argThat(config -> |
| mMockVpn.getNetwork().getNetId() == config.netId |
| && PREFERENCE_PRIORITY_VPN == config.subPriority)); |
| mAgentRegistered = true; |
| verify(mMockNetd).networkCreate(nativeNetworkConfigVpn(getNetwork().netId, |
| !mMockNetworkAgent.isBypassableVpn(), mVpnType)); |
| updateState(NetworkInfo.DetailedState.CONNECTED, "registerAgent"); |
| mNetworkCapabilities.set(mMockNetworkAgent.getNetworkCapabilities()); |
| mNetworkAgent = mMockNetworkAgent.getNetworkAgent(); |
| } |
| |
| private void registerAgent(Set<UidRange> uids) throws Exception { |
| registerAgent(false /* isAlwaysMetered */, uids, makeLinkProperties()); |
| } |
| |
| private void connect(boolean validated, boolean hasInternet, boolean isStrictMode) { |
| mMockNetworkAgent.connect(validated, hasInternet, isStrictMode); |
| } |
| |
| private void connect(boolean validated) { |
| mMockNetworkAgent.connect(validated); |
| } |
| |
| private TestNetworkAgentWrapper getAgent() { |
| return mMockNetworkAgent; |
| } |
| |
| private void setOwnerAndAdminUid(int uid) throws Exception { |
| mNetworkCapabilities.setOwnerUid(uid); |
| mNetworkCapabilities.setAdministratorUids(new int[]{uid}); |
| } |
| |
| public void establish(LinkProperties lp, int uid, Set<UidRange> ranges, boolean validated, |
| boolean hasInternet, boolean isStrictMode) throws Exception { |
| setOwnerAndAdminUid(uid); |
| registerAgent(false, ranges, lp); |
| connect(validated, hasInternet, isStrictMode); |
| waitForIdle(); |
| } |
| |
| public void establish(LinkProperties lp, int uid, Set<UidRange> ranges) throws Exception { |
| establish(lp, uid, ranges, true, true, false); |
| } |
| |
| public void establishForMyUid(LinkProperties lp) throws Exception { |
| final int uid = Process.myUid(); |
| establish(lp, uid, uidRangesForUids(uid), true, true, false); |
| } |
| |
| public void establishForMyUid(boolean validated, boolean hasInternet, boolean isStrictMode) |
| throws Exception { |
| final int uid = Process.myUid(); |
| establish(makeLinkProperties(), uid, uidRangesForUids(uid), validated, hasInternet, |
| isStrictMode); |
| } |
| |
| public void establishForMyUid() throws Exception { |
| establishForMyUid(makeLinkProperties()); |
| } |
| |
| public void sendLinkProperties(LinkProperties lp) { |
| mMockNetworkAgent.sendLinkProperties(lp); |
| } |
| |
| public void disconnect() { |
| if (mMockNetworkAgent != null) { |
| mMockNetworkAgent.disconnect(); |
| updateState(NetworkInfo.DetailedState.DISCONNECTED, "disconnect"); |
| } |
| mAgentRegistered = false; |
| setUids(null); |
| // Remove NET_CAPABILITY_INTERNET or MockNetworkAgent will refuse to connect later on. |
| mNetworkCapabilities.removeCapability(NET_CAPABILITY_INTERNET); |
| mInterface = null; |
| } |
| |
| @Override |
| public void startLegacyVpnRunner() { |
| mStartLegacyVpnCv.open(); |
| } |
| |
| public void expectStartLegacyVpnRunner() { |
| assertTrue("startLegacyVpnRunner not called after " + TIMEOUT_MS + " ms", |
| mStartLegacyVpnCv.block(TIMEOUT_MS)); |
| |
| // startLegacyVpn calls stopVpnRunnerPrivileged, which will open mStopVpnRunnerCv, just |
| // before calling startLegacyVpnRunner. Restore mStopVpnRunnerCv, so the test can expect |
| // that the VpnRunner is stopped and immediately restarted by calling |
| // expectStartLegacyVpnRunner() and expectStopVpnRunnerPrivileged() back-to-back. |
| mStopVpnRunnerCv = new ConditionVariable(); |
| } |
| |
| @Override |
| public void stopVpnRunnerPrivileged() { |
| if (mVpnRunner != null) { |
| super.stopVpnRunnerPrivileged(); |
| disconnect(); |
| mStartLegacyVpnCv = new ConditionVariable(); |
| } |
| mVpnRunner = null; |
| mStopVpnRunnerCv.open(); |
| } |
| |
| public void expectStopVpnRunnerPrivileged() { |
| assertTrue("stopVpnRunnerPrivileged not called after " + TIMEOUT_MS + " ms", |
| mStopVpnRunnerCv.block(TIMEOUT_MS)); |
| } |
| |
| @Override |
| public synchronized UnderlyingNetworkInfo getUnderlyingNetworkInfo() { |
| if (mUnderlyingNetworkInfo != null) return mUnderlyingNetworkInfo; |
| |
| return super.getUnderlyingNetworkInfo(); |
| } |
| |
| private synchronized void setUnderlyingNetworkInfo( |
| UnderlyingNetworkInfo underlyingNetworkInfo) { |
| mUnderlyingNetworkInfo = underlyingNetworkInfo; |
| } |
| } |
| |
| private UidRangeParcel[] toUidRangeStableParcels(final @NonNull Set<UidRange> ranges) { |
| return ranges.stream().map( |
| r -> new UidRangeParcel(r.start, r.stop)).toArray(UidRangeParcel[]::new); |
| } |
| |
| private VpnManagerService makeVpnManagerService() { |
| final VpnManagerService.Dependencies deps = new VpnManagerService.Dependencies() { |
| public int getCallingUid() { |
| return mDeps.getCallingUid(); |
| } |
| |
| public HandlerThread makeHandlerThread() { |
| return mVMSHandlerThread; |
| } |
| |
| @Override |
| public VpnProfileStore getVpnProfileStore() { |
| return mVpnProfileStore; |
| } |
| |
| public INetd getNetd() { |
| return mMockNetd; |
| } |
| |
| public INetworkManagementService getINetworkManagementService() { |
| return mNetworkManagementService; |
| } |
| }; |
| return new VpnManagerService(mServiceContext, deps); |
| } |
| |
| private void assertVpnTransportInfo(NetworkCapabilities nc, int type) { |
| assertNotNull(nc); |
| final TransportInfo ti = nc.getTransportInfo(); |
| assertTrue("VPN TransportInfo is not a VpnTransportInfo: " + ti, |
| ti instanceof VpnTransportInfo); |
| assertEquals(type, ((VpnTransportInfo) ti).getType()); |
| |
| } |
| |
| private void processBroadcast(Intent intent) { |
| mServiceContext.sendBroadcast(intent); |
| HandlerUtils.waitForIdle(mVMSHandlerThread, TIMEOUT_MS); |
| waitForIdle(); |
| } |
| |
| private void mockVpn(int uid) { |
| synchronized (mVpnManagerService.mVpns) { |
| int userId = UserHandle.getUserId(uid); |
| mMockVpn = new MockVpn(userId); |
| // Every running user always has a Vpn in the mVpns array, even if no VPN is running. |
| mVpnManagerService.mVpns.put(userId, mMockVpn); |
| } |
| } |
| |
| private void mockUidNetworkingBlocked() { |
| doAnswer(i -> isUidBlocked(mBlockedReasons, i.getArgument(1)) |
| ).when(mNetworkPolicyManager).isUidNetworkingBlocked(anyInt(), anyBoolean()); |
| } |
| |
| private boolean isUidBlocked(int blockedReasons, boolean meteredNetwork) { |
| final int blockedOnAllNetworksReason = (blockedReasons & ~BLOCKED_METERED_REASON_MASK); |
| if (blockedOnAllNetworksReason != BLOCKED_REASON_NONE) { |
| return true; |
| } |
| if (meteredNetwork) { |
| return blockedReasons != BLOCKED_REASON_NONE; |
| } |
| return false; |
| } |
| |
| private void setBlockedReasonChanged(int blockedReasons) { |
| mBlockedReasons = blockedReasons; |
| mPolicyCallback.onUidBlockedReasonChanged(Process.myUid(), blockedReasons); |
| } |
| |
| private Nat464Xlat getNat464Xlat(NetworkAgentWrapper mna) { |
| return mService.getNetworkAgentInfoForNetwork(mna.getNetwork()).clatd; |
| } |
| |
| private class WrappedMultinetworkPolicyTracker extends MultinetworkPolicyTracker { |
| volatile int mConfigMeteredMultipathPreference; |
| |
| WrappedMultinetworkPolicyTracker(Context c, Handler h, Runnable r) { |
| super(c, h, r); |
| } |
| |
| @Override |
| protected Resources getResourcesForActiveSubId() { |
| return mResources; |
| } |
| |
| @Override |
| public int configMeteredMultipathPreference() { |
| return mConfigMeteredMultipathPreference; |
| } |
| } |
| |
| /** |
| * Wait up to TIMEOUT_MS for {@code conditionVariable} to open. |
| * Fails if TIMEOUT_MS goes by before {@code conditionVariable} opens. |
| */ |
| static private void waitFor(ConditionVariable conditionVariable) { |
| if (conditionVariable.block(TIMEOUT_MS)) { |
| return; |
| } |
| fail("ConditionVariable was blocked for more than " + TIMEOUT_MS + "ms"); |
| } |
| |
| private <T> T doAsUid(final int uid, @NonNull final Supplier<T> what) { |
| when(mDeps.getCallingUid()).thenReturn(uid); |
| try { |
| return what.get(); |
| } finally { |
| returnRealCallingUid(); |
| } |
| } |
| |
| private void doAsUid(final int uid, @NonNull final Runnable what) { |
| doAsUid(uid, () -> { |
| what.run(); return Void.TYPE; |
| }); |
| } |
| |
| private void registerNetworkCallbackAsUid(NetworkRequest request, NetworkCallback callback, |
| int uid) { |
| doAsUid(uid, () -> { |
| mCm.registerNetworkCallback(request, callback); |
| }); |
| } |
| |
| private void registerDefaultNetworkCallbackAsUid(@NonNull final NetworkCallback callback, |
| final int uid) { |
| doAsUid(uid, () -> { |
| mCm.registerDefaultNetworkCallback(callback); |
| waitForIdle(); |
| }); |
| } |
| |
| private interface ExceptionalRunnable { |
| void run() throws Exception; |
| } |
| |
| private void withPermission(String permission, ExceptionalRunnable r) throws Exception { |
| try { |
| mServiceContext.setPermission(permission, PERMISSION_GRANTED); |
| r.run(); |
| } finally { |
| 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); |
| } |
| } |
| |
| private static final int PRIMARY_USER = 0; |
| private static final int SECONDARY_USER = 10; |
| private static final int TERTIARY_USER = 11; |
| private static final UidRange PRIMARY_UIDRANGE = |
| UidRange.createForUser(UserHandle.of(PRIMARY_USER)); |
| private static final int APP1_UID = UserHandle.getUid(PRIMARY_USER, 10100); |
| private static final int APP2_UID = UserHandle.getUid(PRIMARY_USER, 10101); |
| private static final int VPN_UID = UserHandle.getUid(PRIMARY_USER, 10043); |
| private static final UserInfo PRIMARY_USER_INFO = new UserInfo(PRIMARY_USER, "", |
| UserInfo.FLAG_PRIMARY); |
| private static final UserHandle PRIMARY_USER_HANDLE = new UserHandle(PRIMARY_USER); |
| private static final UserHandle SECONDARY_USER_HANDLE = new UserHandle(SECONDARY_USER); |
| private static final UserHandle TERTIARY_USER_HANDLE = new UserHandle(TERTIARY_USER); |
| |
| private static final int RESTRICTED_USER = 1; |
| private static final UserInfo RESTRICTED_USER_INFO = new UserInfo(RESTRICTED_USER, "", |
| UserInfo.FLAG_RESTRICTED); |
| static { |
| RESTRICTED_USER_INFO.restrictedProfileParentId = PRIMARY_USER; |
| } |
| |
| @Before |
| public void setUp() throws Exception { |
| mNetIdManager = new TestNetIdManager(); |
| |
| mContext = InstrumentationRegistry.getContext(); |
| |
| MockitoAnnotations.initMocks(this); |
| |
| when(mUserManager.getAliveUsers()).thenReturn(Arrays.asList(PRIMARY_USER_INFO)); |
| when(mUserManager.getUserHandles(anyBoolean())).thenReturn( |
| Arrays.asList(PRIMARY_USER_HANDLE)); |
| when(mUserManager.getUserInfo(PRIMARY_USER)).thenReturn(PRIMARY_USER_INFO); |
| // canHaveRestrictedProfile does not take a userId. It applies to the userId of the context |
| // it was started from, i.e., PRIMARY_USER. |
| when(mUserManager.canHaveRestrictedProfile()).thenReturn(true); |
| when(mUserManager.getUserInfo(RESTRICTED_USER)).thenReturn(RESTRICTED_USER_INFO); |
| |
| final ApplicationInfo applicationInfo = new ApplicationInfo(); |
| applicationInfo.targetSdkVersion = Build.VERSION_CODES.Q; |
| when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), any())) |
| .thenReturn(applicationInfo); |
| when(mPackageManager.getTargetSdkVersion(anyString())) |
| .thenReturn(applicationInfo.targetSdkVersion); |
| when(mSystemConfigManager.getSystemPermissionUids(anyString())).thenReturn(new int[0]); |
| |
| // InstrumentationTestRunner prepares a looper, but AndroidJUnitRunner does not. |
| // http://b/25897652 . |
| if (Looper.myLooper() == null) { |
| Looper.prepare(); |
| } |
| mockDefaultPackages(); |
| mockHasSystemFeature(FEATURE_WIFI, true); |
| mockHasSystemFeature(FEATURE_WIFI_DIRECT, true); |
| doReturn(true).when(mTelephonyManager).isDataCapable(); |
| |
| FakeSettingsProvider.clearSettingsProvider(); |
| mServiceContext = new MockContext(InstrumentationRegistry.getContext(), |
| new FakeSettingsProvider()); |
| mServiceContext.setUseRegisteredHandlers(true); |
| mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED); |
| mServiceContext.setPermission(NETWORK_STACK, PERMISSION_GRANTED); |
| mServiceContext.setPermission(CONTROL_OEM_PAID_NETWORK_PREFERENCE, PERMISSION_GRANTED); |
| mServiceContext.setPermission(PACKET_KEEPALIVE_OFFLOAD, PERMISSION_GRANTED); |
| mServiceContext.setPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, PERMISSION_GRANTED); |
| |
| mAlarmManagerThread = new HandlerThread("TestAlarmManager"); |
| mAlarmManagerThread.start(); |
| initAlarmManager(mAlarmManager, mAlarmManagerThread.getThreadHandler()); |
| |
| mCsHandlerThread = new HandlerThread("TestConnectivityService"); |
| mVMSHandlerThread = new HandlerThread("TestVpnManagerService"); |
| mDeps = makeDependencies(); |
| returnRealCallingUid(); |
| mService = new ConnectivityService(mServiceContext, |
| mMockDnsResolver, |
| mock(IpConnectivityLog.class), |
| mMockNetd, |
| mDeps); |
| mService.mLingerDelayMs = TEST_LINGER_DELAY_MS; |
| mService.mNascentDelayMs = TEST_NASCENT_DELAY_MS; |
| verify(mDeps).makeMultinetworkPolicyTracker(any(), any(), any()); |
| |
| final ArgumentCaptor<NetworkPolicyCallback> policyCallbackCaptor = |
| ArgumentCaptor.forClass(NetworkPolicyCallback.class); |
| verify(mNetworkPolicyManager).registerNetworkPolicyCallback(any(), |
| policyCallbackCaptor.capture()); |
| mPolicyCallback = policyCallbackCaptor.getValue(); |
| |
| // Create local CM before sending system ready so that we can answer |
| // getSystemService() correctly. |
| mCm = new WrappedConnectivityManager(InstrumentationRegistry.getContext(), mService); |
| mService.systemReadyInternal(); |
| mVpnManagerService = makeVpnManagerService(); |
| mVpnManagerService.systemReady(); |
| mockVpn(Process.myUid()); |
| mCm.bindProcessToNetwork(null); |
| mQosCallbackTracker = mock(QosCallbackTracker.class); |
| |
| // Ensure that the default setting for Captive Portals is used for most tests |
| setCaptivePortalMode(ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_PROMPT); |
| setAlwaysOnNetworks(false); |
| setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com"); |
| } |
| |
| private void returnRealCallingUid() { |
| doAnswer((invocationOnMock) -> Binder.getCallingUid()).when(mDeps).getCallingUid(); |
| } |
| |
| private ConnectivityService.Dependencies makeDependencies() { |
| doReturn(false).when(mSystemProperties).getBoolean("ro.radio.noril", false); |
| final ConnectivityService.Dependencies deps = mock(ConnectivityService.Dependencies.class); |
| doReturn(mCsHandlerThread).when(deps).makeHandlerThread(); |
| doReturn(mNetIdManager).when(deps).makeNetIdManager(); |
| doReturn(mNetworkStack).when(deps).getNetworkStack(); |
| doReturn(mSystemProperties).when(deps).getSystemProperties(); |
| doReturn(mProxyTracker).when(deps).makeProxyTracker(any(), any()); |
| doReturn(true).when(deps).queryUserAccess(anyInt(), any(), any()); |
| doAnswer(inv -> { |
| mPolicyTracker = new WrappedMultinetworkPolicyTracker( |
| inv.getArgument(0), inv.getArgument(1), inv.getArgument(2)); |
| return mPolicyTracker; |
| }).when(deps).makeMultinetworkPolicyTracker(any(), any(), any()); |
| doReturn(true).when(deps).getCellular464XlatEnabled(); |
| doAnswer(inv -> |
| new LocationPermissionChecker(inv.getArgument(0)) { |
| @Override |
| protected int getCurrentUser() { |
| return runAsShell(CREATE_USERS, () -> super.getCurrentUser()); |
| } |
| }).when(deps).makeLocationPermissionChecker(any()); |
| |
| doReturn(60000).when(mResources).getInteger(R.integer.config_networkTransitionTimeout); |
| doReturn("").when(mResources).getString(R.string.config_networkCaptivePortalServerUrl); |
| doReturn(new String[]{ WIFI_WOL_IFNAME }).when(mResources).getStringArray( |
| R.array.config_wakeonlan_supported_interfaces); |
| doReturn(new String[] { "0,1", "1,3" }).when(mResources).getStringArray( |
| R.array.config_networkSupportedKeepaliveCount); |
| doReturn(new String[0]).when(mResources).getStringArray( |
| R.array.config_networkNotifySwitches); |
| doReturn(new int[]{10, 11, 12, 14, 15}).when(mResources).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(new String[0]).when(mResources).getStringArray(R.array.network_switch_type_name); |
| |
| doReturn(R.array.config_networkSupportedKeepaliveCount).when(mResources) |
| .getIdentifier(eq("config_networkSupportedKeepaliveCount"), eq("array"), any()); |
| doReturn(R.array.network_switch_type_name).when(mResources) |
| .getIdentifier(eq("network_switch_type_name"), eq("array"), any()); |
| doReturn(R.integer.config_networkAvoidBadWifi).when(mResources) |
| .getIdentifier(eq("config_networkAvoidBadWifi"), eq("integer"), any()); |
| doReturn(1).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi); |
| |
| final ConnectivityResources connRes = mock(ConnectivityResources.class); |
| doReturn(mResources).when(connRes).get(); |
| doReturn(connRes).when(deps).getResources(any()); |
| |
| final Context mockResContext = mock(Context.class); |
| doReturn(mResources).when(mockResContext).getResources(); |
| ConnectivityResources.setResourcesContextForTest(mockResContext); |
| |
| doAnswer(inv -> { |
| final PendingIntent a = inv.getArgument(0); |
| final PendingIntent b = inv.getArgument(1); |
| return runAsShell(GET_INTENT_SENDER_INTENT, () -> a.intentFilterEquals(b)); |
| }).when(deps).intentFilterEquals(any(), any()); |
| |
| return deps; |
| } |
| |
| private static void initAlarmManager(final AlarmManager am, final Handler alarmHandler) { |
| doAnswer(inv -> { |
| final long when = inv.getArgument(1); |
| final WakeupMessage wakeupMsg = inv.getArgument(3); |
| final Handler handler = inv.getArgument(4); |
| |
| long delayMs = when - SystemClock.elapsedRealtime(); |
| if (delayMs < 0) delayMs = 0; |
| if (delayMs > UNREASONABLY_LONG_ALARM_WAIT_MS) { |
| fail("Attempting to send msg more than " + UNREASONABLY_LONG_ALARM_WAIT_MS |
| + "ms into the future: " + delayMs); |
| } |
| alarmHandler.postDelayed(() -> handler.post(wakeupMsg::onAlarm), wakeupMsg /* token */, |
| delayMs); |
| |
| return null; |
| }).when(am).setExact(eq(AlarmManager.ELAPSED_REALTIME_WAKEUP), anyLong(), anyString(), |
| any(WakeupMessage.class), any()); |
| |
| doAnswer(inv -> { |
| final WakeupMessage wakeupMsg = inv.getArgument(0); |
| alarmHandler.removeCallbacksAndMessages(wakeupMsg /* token */); |
| return null; |
| }).when(am).cancel(any(WakeupMessage.class)); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| unregisterDefaultNetworkCallbacks(); |
| maybeTearDownEnterpriseNetwork(); |
| setAlwaysOnNetworks(false); |
| if (mCellNetworkAgent != null) { |
| mCellNetworkAgent.disconnect(); |
| mCellNetworkAgent = null; |
| } |
| if (mWiFiNetworkAgent != null) { |
| mWiFiNetworkAgent.disconnect(); |
| mWiFiNetworkAgent = null; |
| } |
| if (mEthernetNetworkAgent != null) { |
| mEthernetNetworkAgent.disconnect(); |
| mEthernetNetworkAgent = null; |
| } |
| |
| if (mQosCallbackMockHelper != null) { |
| mQosCallbackMockHelper.tearDown(); |
| mQosCallbackMockHelper = null; |
| } |
| mMockVpn.disconnect(); |
| waitForIdle(); |
| |
| FakeSettingsProvider.clearSettingsProvider(); |
| ConnectivityResources.setResourcesContextForTest(null); |
| |
| mCsHandlerThread.quitSafely(); |
| mAlarmManagerThread.quitSafely(); |
| } |
| |
| private void mockDefaultPackages() throws Exception { |
| final String myPackageName = mContext.getPackageName(); |
| final PackageInfo myPackageInfo = mContext.getPackageManager().getPackageInfo( |
| myPackageName, PackageManager.GET_PERMISSIONS); |
| when(mPackageManager.getPackagesForUid(Binder.getCallingUid())).thenReturn( |
| new String[] {myPackageName}); |
| when(mPackageManager.getPackageInfoAsUser(eq(myPackageName), anyInt(), |
| eq(UserHandle.getCallingUserId()))).thenReturn(myPackageInfo); |
| |
| when(mPackageManager.getInstalledPackages(eq(GET_PERMISSIONS | MATCH_ANY_USER))).thenReturn( |
| Arrays.asList(new PackageInfo[] { |
| buildPackageInfo(/* SYSTEM */ false, APP1_UID), |
| buildPackageInfo(/* SYSTEM */ false, APP2_UID), |
| buildPackageInfo(/* SYSTEM */ false, VPN_UID) |
| })); |
| |
| // Create a fake always-on VPN package. |
| final int userId = UserHandle.getCallingUserId(); |
| final ApplicationInfo applicationInfo = new ApplicationInfo(); |
| applicationInfo.targetSdkVersion = Build.VERSION_CODES.R; // Always-on supported in N+. |
| when(mPackageManager.getApplicationInfoAsUser(eq(ALWAYS_ON_PACKAGE), anyInt(), |
| eq(userId))).thenReturn(applicationInfo); |
| |
| // Minimal mocking to keep Vpn#isAlwaysOnPackageSupported happy. |
| ResolveInfo rInfo = new ResolveInfo(); |
| rInfo.serviceInfo = new ServiceInfo(); |
| rInfo.serviceInfo.metaData = new Bundle(); |
| final List<ResolveInfo> services = Arrays.asList(new ResolveInfo[]{rInfo}); |
| when(mPackageManager.queryIntentServicesAsUser(any(), eq(PackageManager.GET_META_DATA), |
| eq(userId))).thenReturn(services); |
| when(mPackageManager.getPackageUidAsUser(TEST_PACKAGE_NAME, userId)) |
| .thenReturn(Process.myUid()); |
| when(mPackageManager.getPackageUidAsUser(ALWAYS_ON_PACKAGE, userId)) |
| .thenReturn(VPN_UID); |
| } |
| |
| private void verifyActiveNetwork(int transport) { |
| // Test getActiveNetworkInfo() |
| assertNotNull(mCm.getActiveNetworkInfo()); |
| assertEquals(transportToLegacyType(transport), mCm.getActiveNetworkInfo().getType()); |
| // Test getActiveNetwork() |
| assertNotNull(mCm.getActiveNetwork()); |
| assertEquals(mCm.getActiveNetwork(), mCm.getActiveNetworkForUid(Process.myUid())); |
| if (!NetworkCapabilities.isValidTransport(transport)) { |
| throw new IllegalStateException("Unknown transport " + transport); |
| } |
| switch (transport) { |
| case TRANSPORT_WIFI: |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| break; |
| case TRANSPORT_CELLULAR: |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| break; |
| case TRANSPORT_ETHERNET: |
| assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| break; |
| default: |
| break; |
| } |
| // Test getNetworkInfo(Network) |
| assertNotNull(mCm.getNetworkInfo(mCm.getActiveNetwork())); |
| assertEquals(transportToLegacyType(transport), |
| mCm.getNetworkInfo(mCm.getActiveNetwork()).getType()); |
| assertNotNull(mCm.getActiveNetworkInfoForUid(Process.myUid())); |
| // Test getNetworkCapabilities(Network) |
| assertNotNull(mCm.getNetworkCapabilities(mCm.getActiveNetwork())); |
| assertTrue(mCm.getNetworkCapabilities(mCm.getActiveNetwork()).hasTransport(transport)); |
| } |
| |
| private void verifyNoNetwork() { |
| waitForIdle(); |
| // Test getActiveNetworkInfo() |
| assertNull(mCm.getActiveNetworkInfo()); |
| // Test getActiveNetwork() |
| assertNull(mCm.getActiveNetwork()); |
| assertNull(mCm.getActiveNetworkForUid(Process.myUid())); |
| // Test getAllNetworks() |
| assertEmpty(mCm.getAllNetworks()); |
| assertEmpty(mCm.getAllNetworkStateSnapshots()); |
| } |
| |
| /** |
| * Class to simplify expecting broadcasts using BroadcastInterceptingContext. |
| * Ensures that the receiver is unregistered after the expected broadcast is received. This |
| * cannot be done in the BroadcastReceiver itself because BroadcastInterceptingContext runs |
| * the receivers' receive method while iterating over the list of receivers, and unregistering |
| * the receiver during iteration throws ConcurrentModificationException. |
| */ |
| private class ExpectedBroadcast extends CompletableFuture<Intent> { |
| private final BroadcastReceiver mReceiver; |
| |
| ExpectedBroadcast(BroadcastReceiver receiver) { |
| mReceiver = receiver; |
| } |
| |
| public Intent expectBroadcast(int timeoutMs) throws Exception { |
| try { |
| return get(timeoutMs, TimeUnit.MILLISECONDS); |
| } catch (TimeoutException e) { |
| fail("Expected broadcast not received after " + timeoutMs + " ms"); |
| return null; |
| } finally { |
| mServiceContext.unregisterReceiver(mReceiver); |
| } |
| } |
| |
| public Intent expectBroadcast() throws Exception { |
| return expectBroadcast(BROADCAST_TIMEOUT_MS); |
| } |
| |
| public void expectNoBroadcast(int timeoutMs) throws Exception { |
| waitForIdle(); |
| try { |
| final Intent intent = get(timeoutMs, TimeUnit.MILLISECONDS); |
| fail("Unexpected broadcast: " + intent.getAction() + " " + intent.getExtras()); |
| } catch (TimeoutException expected) { |
| } finally { |
| mServiceContext.unregisterReceiver(mReceiver); |
| } |
| } |
| } |
| |
| /** Expects that {@code count} CONNECTIVITY_ACTION broadcasts are received. */ |
| private ExpectedBroadcast registerConnectivityBroadcast(final int count) { |
| return registerConnectivityBroadcastThat(count, intent -> true); |
| } |
| |
| private ExpectedBroadcast registerConnectivityBroadcastThat(final int count, |
| @NonNull final Predicate<Intent> filter) { |
| final IntentFilter intentFilter = new IntentFilter(CONNECTIVITY_ACTION); |
| // AtomicReference allows receiver to access expected even though it is constructed later. |
| final AtomicReference<ExpectedBroadcast> expectedRef = new AtomicReference<>(); |
| final BroadcastReceiver receiver = new BroadcastReceiver() { |
| private int mRemaining = count; |
| public void onReceive(Context context, Intent intent) { |
| final int type = intent.getIntExtra(EXTRA_NETWORK_TYPE, -1); |
| final NetworkInfo ni = intent.getParcelableExtra(EXTRA_NETWORK_INFO); |
| Log.d(TAG, "Received CONNECTIVITY_ACTION type=" + type + " ni=" + ni); |
| if (!filter.test(intent)) return; |
| if (--mRemaining == 0) { |
| expectedRef.get().complete(intent); |
| } |
| } |
| }; |
| final ExpectedBroadcast expected = new ExpectedBroadcast(receiver); |
| expectedRef.set(expected); |
| mServiceContext.registerReceiver(receiver, intentFilter); |
| return expected; |
| } |
| |
| private boolean extraInfoInBroadcastHasExpectedNullness(NetworkInfo ni) { |
| final DetailedState state = ni.getDetailedState(); |
| if (state == DetailedState.CONNECTED && ni.getExtraInfo() == null) return false; |
| // Expect a null extraInfo if the network is CONNECTING, because a CONNECTIVITY_ACTION |
| // broadcast with a state of CONNECTING only happens due to legacy VPN lockdown, which also |
| // nulls out extraInfo. |
| if (state == DetailedState.CONNECTING && ni.getExtraInfo() != null) return false; |
| // Can't make any assertions about DISCONNECTED broadcasts. When a network actually |
| // disconnects, disconnectAndDestroyNetwork sets its state to DISCONNECTED and its extraInfo |
| // to null. But if the DISCONNECTED broadcast is just simulated by LegacyTypeTracker due to |
| // a network switch, extraInfo will likely be populated. |
| // This is likely a bug in CS, but likely not one we can fix without impacting apps. |
| return true; |
| } |
| |
| private ExpectedBroadcast expectConnectivityAction(int type, NetworkInfo.DetailedState state) { |
| return registerConnectivityBroadcastThat(1, intent -> { |
| final int actualType = intent.getIntExtra(EXTRA_NETWORK_TYPE, -1); |
| final NetworkInfo ni = intent.getParcelableExtra(EXTRA_NETWORK_INFO); |
| return type == actualType |
| && state == ni.getDetailedState() |
| && extraInfoInBroadcastHasExpectedNullness(ni); |
| }); |
| } |
| |
| @Test |
| public void testNetworkTypes() { |
| // Ensure that our mocks for the networkAttributes config variable work as expected. If they |
| // don't, then tests that depend on CONNECTIVITY_ACTION broadcasts for these network types |
| // will fail. Failing here is much easier to debug. |
| assertTrue(mCm.isNetworkSupported(TYPE_WIFI)); |
| assertTrue(mCm.isNetworkSupported(TYPE_MOBILE)); |
| assertTrue(mCm.isNetworkSupported(TYPE_MOBILE_MMS)); |
| assertTrue(mCm.isNetworkSupported(TYPE_MOBILE_FOTA)); |
| assertFalse(mCm.isNetworkSupported(TYPE_PROXY)); |
| |
| // Check that TYPE_ETHERNET is supported. Unlike the asserts above, which only validate our |
| // mocks, this assert exercises the ConnectivityService code path that ensures that |
| // TYPE_ETHERNET is supported if the ethernet service is running. |
| assertTrue(mCm.isNetworkSupported(TYPE_ETHERNET)); |
| } |
| |
| @Test |
| public void testNetworkFeature() throws Exception { |
| // Connect the cell agent and wait for the connected broadcast. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.addCapability(NET_CAPABILITY_SUPL); |
| ExpectedBroadcast b = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED); |
| mCellNetworkAgent.connect(true); |
| b.expectBroadcast(); |
| |
| // Build legacy request for SUPL. |
| final NetworkCapabilities legacyCaps = new NetworkCapabilities(); |
| legacyCaps.addTransportType(TRANSPORT_CELLULAR); |
| legacyCaps.addCapability(NET_CAPABILITY_SUPL); |
| final NetworkRequest legacyRequest = new NetworkRequest(legacyCaps, TYPE_MOBILE_SUPL, |
| ConnectivityManager.REQUEST_ID_UNSET, NetworkRequest.Type.REQUEST); |
| |
| // File request, withdraw it and make sure no broadcast is sent |
| b = registerConnectivityBroadcast(1); |
| final TestNetworkCallback callback = new TestNetworkCallback(); |
| mCm.requestNetwork(legacyRequest, callback); |
| callback.expectCallback(CallbackEntry.AVAILABLE, mCellNetworkAgent); |
| mCm.unregisterNetworkCallback(callback); |
| b.expectNoBroadcast(800); // 800ms long enough to at least flake if this is sent |
| |
| // Disconnect the network and expect mobile disconnected broadcast. |
| b = expectConnectivityAction(TYPE_MOBILE, DetailedState.DISCONNECTED); |
| mCellNetworkAgent.disconnect(); |
| b.expectBroadcast(); |
| } |
| |
| @Test |
| public void testLingering() throws Exception { |
| verifyNoNetwork(); |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| assertNull(mCm.getActiveNetworkInfo()); |
| assertNull(mCm.getActiveNetwork()); |
| // Test bringing up validated cellular. |
| ExpectedBroadcast b = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED); |
| mCellNetworkAgent.connect(true); |
| b.expectBroadcast(); |
| verifyActiveNetwork(TRANSPORT_CELLULAR); |
| assertLength(2, mCm.getAllNetworks()); |
| assertTrue(mCm.getAllNetworks()[0].equals(mCm.getActiveNetwork()) || |
| mCm.getAllNetworks()[1].equals(mCm.getActiveNetwork())); |
| assertTrue(mCm.getAllNetworks()[0].equals(mWiFiNetworkAgent.getNetwork()) || |
| mCm.getAllNetworks()[1].equals(mWiFiNetworkAgent.getNetwork())); |
| // Test bringing up validated WiFi. |
| b = registerConnectivityBroadcast(2); |
| mWiFiNetworkAgent.connect(true); |
| b.expectBroadcast(); |
| verifyActiveNetwork(TRANSPORT_WIFI); |
| assertLength(2, mCm.getAllNetworks()); |
| assertTrue(mCm.getAllNetworks()[0].equals(mCm.getActiveNetwork()) || |
| mCm.getAllNetworks()[1].equals(mCm.getActiveNetwork())); |
| assertTrue(mCm.getAllNetworks()[0].equals(mCellNetworkAgent.getNetwork()) || |
| mCm.getAllNetworks()[1].equals(mCellNetworkAgent.getNetwork())); |
| // Test cellular linger timeout. |
| mCellNetworkAgent.expectDisconnected(); |
| waitForIdle(); |
| assertLength(1, mCm.getAllNetworks()); |
| verifyActiveNetwork(TRANSPORT_WIFI); |
| assertLength(1, mCm.getAllNetworks()); |
| assertEquals(mCm.getAllNetworks()[0], mCm.getActiveNetwork()); |
| // Test WiFi disconnect. |
| b = registerConnectivityBroadcast(1); |
| mWiFiNetworkAgent.disconnect(); |
| b.expectBroadcast(); |
| verifyNoNetwork(); |
| } |
| |
| /** |
| * Verify a newly created network will be inactive instead of torn down even if no one is |
| * requesting. |
| */ |
| @Test |
| public void testNewNetworkInactive() throws Exception { |
| // Create a callback that monitoring the testing network. |
| final TestNetworkCallback listenCallback = new TestNetworkCallback(); |
| mCm.registerNetworkCallback(new NetworkRequest.Builder().build(), listenCallback); |
| |
| // 1. Create a network that is not requested by anyone, and does not satisfy any of the |
| // default requests. Verify that the network will be inactive instead of torn down. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connectWithoutInternet(); |
| listenCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| listenCallback.assertNoCallback(); |
| |
| // Verify that the network will be torn down after nascent expiry. A small period of time |
| // is added in case of flakiness. |
| final int nascentTimeoutMs = |
| mService.mNascentDelayMs + mService.mNascentDelayMs / 4; |
| listenCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent, nascentTimeoutMs); |
| |
| // 2. Create a network that is satisfied by a request comes later. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connectWithoutInternet(); |
| listenCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| final NetworkRequest wifiRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_WIFI).build(); |
| final TestNetworkCallback wifiCallback = new TestNetworkCallback(); |
| mCm.requestNetwork(wifiRequest, wifiCallback); |
| wifiCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| |
| // Verify that the network will be kept since the request is still satisfied. And is able |
| // to get disconnected as usual if the request is released after the nascent timer expires. |
| listenCallback.assertNoCallback(nascentTimeoutMs); |
| mCm.unregisterNetworkCallback(wifiCallback); |
| listenCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| |
| // 3. Create a network that is satisfied by a request comes later. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connectWithoutInternet(); |
| listenCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| mCm.requestNetwork(wifiRequest, wifiCallback); |
| wifiCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| |
| // Verify that the network will still be torn down after the request gets removed. |
| mCm.unregisterNetworkCallback(wifiCallback); |
| listenCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| |
| // There is no need to ensure that LOSING is never sent in the common case that the |
| // network immediately satisfies a request that was already present, because it is already |
| // verified anywhere whenever {@code TestNetworkCallback#expectAvailable*} is called. |
| |
| mCm.unregisterNetworkCallback(listenCallback); |
| } |
| |
| /** |
| * Verify a newly created network will be inactive and switch to background if only background |
| * request is satisfied. |
| */ |
| @Test |
| public void testNewNetworkInactive_bgNetwork() throws Exception { |
| // Create a callback that monitoring the wifi network. |
| final TestNetworkCallback wifiListenCallback = new TestNetworkCallback(); |
| mCm.registerNetworkCallback(new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_WIFI).build(), wifiListenCallback); |
| |
| // Create callbacks that can monitor background and foreground mobile networks. |
| // This is done by granting using background networks permission before registration. Thus, |
| // the service will not add {@code NET_CAPABILITY_FOREGROUND} by default. |
| grantUsingBackgroundNetworksPermissionForUid(Binder.getCallingUid()); |
| final TestNetworkCallback bgMobileListenCallback = new TestNetworkCallback(); |
| final TestNetworkCallback fgMobileListenCallback = new TestNetworkCallback(); |
| mCm.registerNetworkCallback(new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_CELLULAR).build(), bgMobileListenCallback); |
| mCm.registerNetworkCallback(new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_CELLULAR) |
| .addCapability(NET_CAPABILITY_FOREGROUND).build(), fgMobileListenCallback); |
| |
| // Connect wifi, which satisfies default request. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(true); |
| wifiListenCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); |
| |
| // Connect a cellular network, verify that satisfies only the background callback. |
| setAlwaysOnNetworks(true); |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| bgMobileListenCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| fgMobileListenCallback.assertNoCallback(); |
| assertFalse(isForegroundNetwork(mCellNetworkAgent)); |
| |
| mCellNetworkAgent.disconnect(); |
| bgMobileListenCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| fgMobileListenCallback.assertNoCallback(); |
| |
| mCm.unregisterNetworkCallback(wifiListenCallback); |
| mCm.unregisterNetworkCallback(bgMobileListenCallback); |
| mCm.unregisterNetworkCallback(fgMobileListenCallback); |
| } |
| |
| @Test |
| public void testBinderDeathAfterUnregister() throws Exception { |
| final NetworkCapabilities caps = new NetworkCapabilities.Builder() |
| .addTransportType(TRANSPORT_WIFI) |
| .build(); |
| final Handler handler = new Handler(ConnectivityThread.getInstanceLooper()); |
| final Messenger messenger = new Messenger(handler); |
| final CompletableFuture<Binder.DeathRecipient> deathRecipient = new CompletableFuture<>(); |
| final Binder binder = new Binder() { |
| private DeathRecipient mDeathRecipient; |
| @Override |
| public void linkToDeath(@NonNull final DeathRecipient recipient, final int flags) { |
| synchronized (this) { |
| mDeathRecipient = recipient; |
| } |
| super.linkToDeath(recipient, flags); |
| deathRecipient.complete(recipient); |
| } |
| |
| @Override |
| public boolean unlinkToDeath(@NonNull final DeathRecipient recipient, final int flags) { |
| synchronized (this) { |
| if (null == mDeathRecipient) { |
| throw new IllegalStateException(); |
| } |
| mDeathRecipient = null; |
| } |
| return super.unlinkToDeath(recipient, flags); |
| } |
| }; |
| final NetworkRequest request = mService.listenForNetwork(caps, messenger, binder, |
| NetworkCallback.FLAG_NONE, mContext.getOpPackageName(), |
| mContext.getAttributionTag()); |
| mService.releaseNetworkRequest(request); |
| deathRecipient.get().binderDied(); |
| // Wait for the release message to be processed. |
| waitForIdle(); |
| } |
| |
| @Test |
| public void testValidatedCellularOutscoresUnvalidatedWiFi() throws Exception { |
| // Test bringing up unvalidated WiFi |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| ExpectedBroadcast b = registerConnectivityBroadcast(1); |
| mWiFiNetworkAgent.connect(false); |
| b.expectBroadcast(); |
| verifyActiveNetwork(TRANSPORT_WIFI); |
| // Test bringing up unvalidated cellular |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(false); |
| waitForIdle(); |
| verifyActiveNetwork(TRANSPORT_WIFI); |
| // Test cellular disconnect. |
| mCellNetworkAgent.disconnect(); |
| waitForIdle(); |
| verifyActiveNetwork(TRANSPORT_WIFI); |
| // Test bringing up validated cellular |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| b = registerConnectivityBroadcast(2); |
| mCellNetworkAgent.connect(true); |
| b.expectBroadcast(); |
| verifyActiveNetwork(TRANSPORT_CELLULAR); |
| // Test cellular disconnect. |
| b = registerConnectivityBroadcast(2); |
| mCellNetworkAgent.disconnect(); |
| b.expectBroadcast(); |
| verifyActiveNetwork(TRANSPORT_WIFI); |
| // Test WiFi disconnect. |
| b = registerConnectivityBroadcast(1); |
| mWiFiNetworkAgent.disconnect(); |
| b.expectBroadcast(); |
| verifyNoNetwork(); |
| } |
| |
| @Test |
| public void testUnvalidatedWifiOutscoresUnvalidatedCellular() throws Exception { |
| // Test bringing up unvalidated cellular. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| ExpectedBroadcast b = registerConnectivityBroadcast(1); |
| mCellNetworkAgent.connect(false); |
| b.expectBroadcast(); |
| verifyActiveNetwork(TRANSPORT_CELLULAR); |
| // Test bringing up unvalidated WiFi. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| b = registerConnectivityBroadcast(2); |
| mWiFiNetworkAgent.connect(false); |
| b.expectBroadcast(); |
| verifyActiveNetwork(TRANSPORT_WIFI); |
| // Test WiFi disconnect. |
| b = registerConnectivityBroadcast(2); |
| mWiFiNetworkAgent.disconnect(); |
| b.expectBroadcast(); |
| verifyActiveNetwork(TRANSPORT_CELLULAR); |
| // Test cellular disconnect. |
| b = registerConnectivityBroadcast(1); |
| mCellNetworkAgent.disconnect(); |
| b.expectBroadcast(); |
| verifyNoNetwork(); |
| } |
| |
| @Test |
| public void testUnlingeringDoesNotValidate() throws Exception { |
| // Test bringing up unvalidated WiFi. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| ExpectedBroadcast b = registerConnectivityBroadcast(1); |
| mWiFiNetworkAgent.connect(false); |
| b.expectBroadcast(); |
| verifyActiveNetwork(TRANSPORT_WIFI); |
| assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( |
| NET_CAPABILITY_VALIDATED)); |
| // Test bringing up validated cellular. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| b = registerConnectivityBroadcast(2); |
| mCellNetworkAgent.connect(true); |
| b.expectBroadcast(); |
| verifyActiveNetwork(TRANSPORT_CELLULAR); |
| assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( |
| NET_CAPABILITY_VALIDATED)); |
| // Test cellular disconnect. |
| b = registerConnectivityBroadcast(2); |
| mCellNetworkAgent.disconnect(); |
| b.expectBroadcast(); |
| verifyActiveNetwork(TRANSPORT_WIFI); |
| // Unlingering a network should not cause it to be marked as validated. |
| assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( |
| NET_CAPABILITY_VALIDATED)); |
| } |
| |
| @Test |
| public void testCellularOutscoresWeakWifi() throws Exception { |
| // Test bringing up validated cellular. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| ExpectedBroadcast b = registerConnectivityBroadcast(1); |
| mCellNetworkAgent.connect(true); |
| b.expectBroadcast(); |
| verifyActiveNetwork(TRANSPORT_CELLULAR); |
| // Test bringing up validated WiFi. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| b = registerConnectivityBroadcast(2); |
| mWiFiNetworkAgent.connect(true); |
| b.expectBroadcast(); |
| verifyActiveNetwork(TRANSPORT_WIFI); |
| // Test WiFi getting really weak. |
| b = registerConnectivityBroadcast(2); |
| mWiFiNetworkAgent.adjustScore(-11); |
| b.expectBroadcast(); |
| verifyActiveNetwork(TRANSPORT_CELLULAR); |
| // Test WiFi restoring signal strength. |
| b = registerConnectivityBroadcast(2); |
| mWiFiNetworkAgent.adjustScore(11); |
| b.expectBroadcast(); |
| verifyActiveNetwork(TRANSPORT_WIFI); |
| } |
| |
| @Test |
| public void testReapingNetwork() throws Exception { |
| // Test bringing up WiFi without NET_CAPABILITY_INTERNET. |
| // Expect it to be torn down immediately because it satisfies no requests. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connectWithoutInternet(); |
| mWiFiNetworkAgent.expectDisconnected(); |
| // Test bringing up cellular without NET_CAPABILITY_INTERNET. |
| // Expect it to be torn down immediately because it satisfies no requests. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mCellNetworkAgent.connectWithoutInternet(); |
| mCellNetworkAgent.expectDisconnected(); |
| // Test bringing up validated WiFi. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| final ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); |
| mWiFiNetworkAgent.connect(true); |
| b.expectBroadcast(); |
| verifyActiveNetwork(TRANSPORT_WIFI); |
| // Test bringing up unvalidated cellular. |
| // Expect it to be torn down because it could never be the highest scoring network |
| // satisfying the default request even if it validated. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(false); |
| mCellNetworkAgent.expectDisconnected(); |
| verifyActiveNetwork(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.disconnect(); |
| mWiFiNetworkAgent.expectDisconnected(); |
| } |
| |
| @Test |
| public void testCellularFallback() throws Exception { |
| // Test bringing up validated cellular. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| ExpectedBroadcast b = registerConnectivityBroadcast(1); |
| mCellNetworkAgent.connect(true); |
| b.expectBroadcast(); |
| verifyActiveNetwork(TRANSPORT_CELLULAR); |
| // Test bringing up validated WiFi. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| b = registerConnectivityBroadcast(2); |
| mWiFiNetworkAgent.connect(true); |
| b.expectBroadcast(); |
| verifyActiveNetwork(TRANSPORT_WIFI); |
| // Reevaluate WiFi (it'll instantly fail DNS). |
| b = registerConnectivityBroadcast(2); |
| assertTrue(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( |
| NET_CAPABILITY_VALIDATED)); |
| mCm.reportBadNetwork(mWiFiNetworkAgent.getNetwork()); |
| // Should quickly fall back to Cellular. |
| b.expectBroadcast(); |
| assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( |
| NET_CAPABILITY_VALIDATED)); |
| verifyActiveNetwork(TRANSPORT_CELLULAR); |
| // Reevaluate cellular (it'll instantly fail DNS). |
| b = registerConnectivityBroadcast(2); |
| assertTrue(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( |
| NET_CAPABILITY_VALIDATED)); |
| mCm.reportBadNetwork(mCellNetworkAgent.getNetwork()); |
| // Should quickly fall back to WiFi. |
| b.expectBroadcast(); |
| assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( |
| NET_CAPABILITY_VALIDATED)); |
| assertFalse(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability( |
| NET_CAPABILITY_VALIDATED)); |
| verifyActiveNetwork(TRANSPORT_WIFI); |
| } |
| |
| @Test |
| public void testWiFiFallback() throws Exception { |
| // Test bringing up unvalidated WiFi. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| ExpectedBroadcast b = registerConnectivityBroadcast(1); |
| mWiFiNetworkAgent.connect(false); |
| b.expectBroadcast(); |
| verifyActiveNetwork(TRANSPORT_WIFI); |
| // Test bringing up validated cellular. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| b = registerConnectivityBroadcast(2); |
| mCellNetworkAgent.connect(true); |
| b.expectBroadcast(); |
| verifyActiveNetwork(TRANSPORT_CELLULAR); |
| // Reevaluate cellular (it'll instantly fail DNS). |
| b = registerConnectivityBroadcast(2); |
| assertTrue(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( |
| NET_CAPABILITY_VALIDATED)); |
| mCm.reportBadNetwork(mCellNetworkAgent.getNetwork()); |
| // Should quickly fall back to WiFi. |
| b.expectBroadcast(); |
| assertFalse(mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()).hasCapability( |
| NET_CAPABILITY_VALIDATED)); |
| verifyActiveNetwork(TRANSPORT_WIFI); |
| } |
| |
| @Test |
| public void testRequiresValidation() { |
| assertTrue(NetworkMonitorUtils.isValidationRequired( |
| mCm.getDefaultRequest().networkCapabilities)); |
| } |
| |
| /** |
| * Utility NetworkCallback for testing. The caller must explicitly test for all the callbacks |
| * this class receives, by calling expectCallback() exactly once each time a callback is |
| * received. assertNoCallback may be called at any time. |
| */ |
| private class TestNetworkCallback extends TestableNetworkCallback { |
| TestNetworkCallback() { |
| super(TEST_CALLBACK_TIMEOUT_MS); |
| } |
| |
| @Override |
| public void assertNoCallback() { |
| // TODO: better support this use case in TestableNetworkCallback |
| waitForIdle(); |
| assertNoCallback(0 /* timeout */); |
| } |
| |
| @Override |
| public <T extends CallbackEntry> T expectCallback(final KClass<T> type, final HasNetwork n, |
| final long timeoutMs) { |
| final T callback = super.expectCallback(type, n, timeoutMs); |
| if (callback instanceof CallbackEntry.Losing) { |
| // TODO : move this to the specific test(s) needing this rather than here. |
| final CallbackEntry.Losing losing = (CallbackEntry.Losing) callback; |
| final int maxMsToLive = losing.getMaxMsToLive(); |
| String msg = String.format( |
| "Invalid linger time value %d, must be between %d and %d", |
| maxMsToLive, 0, mService.mLingerDelayMs); |
| assertTrue(msg, 0 <= maxMsToLive && maxMsToLive <= mService.mLingerDelayMs); |
| } |
| return callback; |
| } |
| } |
| |
| // Can't be part of TestNetworkCallback because "cannot be declared static; static methods can |
| // only be declared in a static or top level type". |
| static void assertNoCallbacks(TestNetworkCallback ... callbacks) { |
| for (TestNetworkCallback c : callbacks) { |
| c.assertNoCallback(); |
| } |
| } |
| |
| static void expectOnLost(TestNetworkAgentWrapper network, TestNetworkCallback ... callbacks) { |
| for (TestNetworkCallback c : callbacks) { |
| c.expectCallback(CallbackEntry.LOST, network); |
| } |
| } |
| |
| static void expectAvailableCallbacksUnvalidatedWithSpecifier(TestNetworkAgentWrapper network, |
| NetworkSpecifier specifier, TestNetworkCallback ... callbacks) { |
| for (TestNetworkCallback c : callbacks) { |
| c.expectCallback(CallbackEntry.AVAILABLE, network); |
| c.expectCapabilitiesThat(network, (nc) -> |
| !nc.hasCapability(NET_CAPABILITY_VALIDATED) |
| && Objects.equals(specifier, nc.getNetworkSpecifier())); |
| c.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, network); |
| c.expectCallback(CallbackEntry.BLOCKED_STATUS, network); |
| } |
| } |
| |
| @Test |
| public void testStateChangeNetworkCallbacks() throws Exception { |
| final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback(); |
| final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback(); |
| final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); |
| final NetworkRequest genericRequest = new NetworkRequest.Builder() |
| .clearCapabilities().build(); |
| final NetworkRequest wifiRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_WIFI).build(); |
| final NetworkRequest cellRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_CELLULAR).build(); |
| mCm.registerNetworkCallback(genericRequest, genericNetworkCallback); |
| mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback); |
| mCm.registerNetworkCallback(cellRequest, cellNetworkCallback); |
| |
| // Test unvalidated networks |
| ExpectedBroadcast b = registerConnectivityBroadcast(1); |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(false); |
| genericNetworkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); |
| cellNetworkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| b.expectBroadcast(); |
| assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); |
| |
| // This should not trigger spurious onAvailable() callbacks, b/21762680. |
| mCellNetworkAgent.adjustScore(-1); |
| waitForIdle(); |
| assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| |
| b = registerConnectivityBroadcast(2); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(false); |
| genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| b.expectBroadcast(); |
| assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); |
| |
| b = registerConnectivityBroadcast(2); |
| mWiFiNetworkAgent.disconnect(); |
| genericNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| wifiNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| cellNetworkCallback.assertNoCallback(); |
| b.expectBroadcast(); |
| assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); |
| |
| b = registerConnectivityBroadcast(1); |
| mCellNetworkAgent.disconnect(); |
| genericNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| b.expectBroadcast(); |
| assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); |
| |
| // Test validated networks |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| genericNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); |
| |
| // This should not trigger spurious onAvailable() callbacks, b/21762680. |
| mCellNetworkAgent.adjustScore(-1); |
| waitForIdle(); |
| assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(true); |
| genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| genericNetworkCallback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); |
| genericNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); |
| wifiNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); |
| cellNetworkCallback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); |
| |
| mWiFiNetworkAgent.disconnect(); |
| genericNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| wifiNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); |
| |
| mCellNetworkAgent.disconnect(); |
| genericNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| assertNoCallbacks(genericNetworkCallback, wifiNetworkCallback, cellNetworkCallback); |
| } |
| |
| private void doNetworkCallbacksSanitizationTest(boolean sanitized) throws Exception { |
| final TestNetworkCallback callback = new TestNetworkCallback(); |
| final TestNetworkCallback defaultCallback = new TestNetworkCallback(); |
| final NetworkRequest wifiRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_WIFI).build(); |
| mCm.registerNetworkCallback(wifiRequest, callback); |
| mCm.registerDefaultNetworkCallback(defaultCallback); |
| |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(false); |
| callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| |
| final LinkProperties newLp = new LinkProperties(); |
| final Uri capportUrl = Uri.parse("https://capport.example.com/api"); |
| final CaptivePortalData capportData = new CaptivePortalData.Builder() |
| .setCaptive(true).build(); |
| |
| final Uri expectedCapportUrl = sanitized ? null : capportUrl; |
| newLp.setCaptivePortalApiUrl(capportUrl); |
| mWiFiNetworkAgent.sendLinkProperties(newLp); |
| callback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp -> |
| Objects.equals(expectedCapportUrl, lp.getCaptivePortalApiUrl())); |
| defaultCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp -> |
| Objects.equals(expectedCapportUrl, lp.getCaptivePortalApiUrl())); |
| |
| final CaptivePortalData expectedCapportData = sanitized ? null : capportData; |
| mWiFiNetworkAgent.notifyCapportApiDataChanged(capportData); |
| callback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp -> |
| Objects.equals(expectedCapportData, lp.getCaptivePortalData())); |
| defaultCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, lp -> |
| Objects.equals(expectedCapportData, lp.getCaptivePortalData())); |
| |
| final LinkProperties lp = mCm.getLinkProperties(mWiFiNetworkAgent.getNetwork()); |
| assertEquals(expectedCapportUrl, lp.getCaptivePortalApiUrl()); |
| assertEquals(expectedCapportData, lp.getCaptivePortalData()); |
| } |
| |
| @Test |
| public void networkCallbacksSanitizationTest_Sanitize() throws Exception { |
| mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, |
| PERMISSION_DENIED); |
| mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_DENIED); |
| doNetworkCallbacksSanitizationTest(true /* sanitized */); |
| } |
| |
| @Test |
| public void networkCallbacksSanitizationTest_NoSanitize_NetworkStack() throws Exception { |
| mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, |
| PERMISSION_GRANTED); |
| mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_DENIED); |
| doNetworkCallbacksSanitizationTest(false /* sanitized */); |
| } |
| |
| @Test |
| public void networkCallbacksSanitizationTest_NoSanitize_Settings() throws Exception { |
| mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, |
| PERMISSION_DENIED); |
| mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED); |
| doNetworkCallbacksSanitizationTest(false /* sanitized */); |
| } |
| |
| @Test |
| public void testOwnerUidCannotChange() throws Exception { |
| final NetworkCapabilities ncTemplate = new NetworkCapabilities(); |
| final int originalOwnerUid = Process.myUid(); |
| ncTemplate.setOwnerUid(originalOwnerUid); |
| |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, new LinkProperties(), |
| ncTemplate); |
| mWiFiNetworkAgent.connect(false); |
| waitForIdle(); |
| |
| // Send ConnectivityService an update to the mWiFiNetworkAgent's capabilities that changes |
| // the owner UID and an unrelated capability. |
| NetworkCapabilities agentCapabilities = mWiFiNetworkAgent.getNetworkCapabilities(); |
| assertEquals(originalOwnerUid, agentCapabilities.getOwnerUid()); |
| agentCapabilities.setOwnerUid(42); |
| assertFalse(agentCapabilities.hasCapability(NET_CAPABILITY_NOT_CONGESTED)); |
| agentCapabilities.addCapability(NET_CAPABILITY_NOT_CONGESTED); |
| mWiFiNetworkAgent.setNetworkCapabilities(agentCapabilities, true); |
| waitForIdle(); |
| |
| // Owner UIDs are not visible without location permission. |
| setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, |
| Manifest.permission.ACCESS_FINE_LOCATION); |
| |
| // Check that the capability change has been applied but the owner UID is not modified. |
| NetworkCapabilities nc = mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()); |
| assertEquals(originalOwnerUid, nc.getOwnerUid()); |
| assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_CONGESTED)); |
| } |
| |
| @Test |
| public void testMultipleLingering() throws Exception { |
| // This test would be flaky with the default 120ms timer: that is short enough that |
| // lingered networks are torn down before assertions can be run. We don't want to mock the |
| // lingering timer to keep the WakeupMessage logic realistic: this has already proven useful |
| // in detecting races. |
| mService.mLingerDelayMs = 300; |
| |
| NetworkRequest request = new NetworkRequest.Builder() |
| .clearCapabilities().addCapability(NET_CAPABILITY_NOT_METERED) |
| .build(); |
| TestNetworkCallback callback = new TestNetworkCallback(); |
| mCm.registerNetworkCallback(request, callback); |
| |
| TestNetworkCallback defaultCallback = new TestNetworkCallback(); |
| mCm.registerDefaultNetworkCallback(defaultCallback); |
| |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET); |
| |
| mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); |
| mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); |
| mEthernetNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); |
| |
| mCellNetworkAgent.connect(true); |
| callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| |
| mWiFiNetworkAgent.connect(true); |
| // We get AVAILABLE on wifi when wifi connects and satisfies our unmetered request. |
| // We then get LOSING when wifi validates and cell is outscored. |
| callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| // TODO: Investigate sending validated before losing. |
| callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); |
| callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); |
| defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| |
| mEthernetNetworkAgent.connect(true); |
| callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent); |
| // TODO: Investigate sending validated before losing. |
| callback.expectCallback(CallbackEntry.LOSING, mWiFiNetworkAgent); |
| callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent); |
| defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent); |
| assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| |
| mEthernetNetworkAgent.disconnect(); |
| callback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); |
| defaultCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); |
| defaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); |
| assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| |
| for (int i = 0; i < 4; i++) { |
| TestNetworkAgentWrapper oldNetwork, newNetwork; |
| if (i % 2 == 0) { |
| mWiFiNetworkAgent.adjustScore(-15); |
| oldNetwork = mWiFiNetworkAgent; |
| newNetwork = mCellNetworkAgent; |
| } else { |
| mWiFiNetworkAgent.adjustScore(15); |
| oldNetwork = mCellNetworkAgent; |
| newNetwork = mWiFiNetworkAgent; |
| |
| } |
| callback.expectCallback(CallbackEntry.LOSING, oldNetwork); |
| // TODO: should we send an AVAILABLE callback to newNetwork, to indicate that it is no |
| // longer lingering? |
| defaultCallback.expectAvailableCallbacksValidated(newNetwork); |
| assertEquals(newNetwork.getNetwork(), mCm.getActiveNetwork()); |
| } |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| |
| // Verify that if a network no longer satisfies a request, we send LOST and not LOSING, even |
| // if the network is still up. |
| mWiFiNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED); |
| // We expect a notification about the capabilities change, and nothing else. |
| defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED, mWiFiNetworkAgent); |
| defaultCallback.assertNoCallback(); |
| callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| |
| // Wifi no longer satisfies our listen, which is for an unmetered network. |
| // But because its score is 55, it's still up (and the default network). |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| |
| // Disconnect our test networks. |
| mWiFiNetworkAgent.disconnect(); |
| defaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); |
| assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| mCellNetworkAgent.disconnect(); |
| defaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| waitForIdle(); |
| assertEquals(null, mCm.getActiveNetwork()); |
| |
| mCm.unregisterNetworkCallback(callback); |
| waitForIdle(); |
| |
| // Check that a network is only lingered or torn down if it would not satisfy a request even |
| // if it validated. |
| request = new NetworkRequest.Builder().clearCapabilities().build(); |
| callback = new TestNetworkCallback(); |
| |
| mCm.registerNetworkCallback(request, callback); |
| |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(false); // Score: 10 |
| callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); |
| defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| |
| // Bring up wifi with a score of 20. |
| // Cell stays up because it would satisfy the default request if it validated. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(false); // Score: 20 |
| callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| |
| mWiFiNetworkAgent.disconnect(); |
| callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| defaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| |
| // Bring up wifi, then validate it. Previous versions would immediately tear down cell, but |
| // it's arguably correct to linger it, since it was the default network before it validated. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(true); |
| callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| // TODO: Investigate sending validated before losing. |
| callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); |
| callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); |
| defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| |
| mWiFiNetworkAgent.disconnect(); |
| callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| defaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| defaultCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); |
| mCellNetworkAgent.disconnect(); |
| callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| defaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| waitForIdle(); |
| assertEquals(null, mCm.getActiveNetwork()); |
| |
| // If a network is lingering, and we add and remove a request from it, resume lingering. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(true); |
| defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); |
| callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| // TODO: Investigate sending validated before losing. |
| callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); |
| callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); |
| assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| |
| NetworkRequest cellRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_CELLULAR).build(); |
| NetworkCallback noopCallback = new NetworkCallback(); |
| mCm.requestNetwork(cellRequest, noopCallback); |
| // TODO: should this cause an AVAILABLE callback, to indicate that the network is no longer |
| // lingering? |
| mCm.unregisterNetworkCallback(noopCallback); |
| callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); |
| |
| // Similar to the above: lingering can start even after the lingered request is removed. |
| // Disconnect wifi and switch to cell. |
| mWiFiNetworkAgent.disconnect(); |
| callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| defaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); |
| assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| |
| // Cell is now the default network. Pin it with a cell-specific request. |
| noopCallback = new NetworkCallback(); // Can't reuse NetworkCallbacks. http://b/20701525 |
| mCm.requestNetwork(cellRequest, noopCallback); |
| |
| // Now connect wifi, and expect it to become the default network. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(true); |
| callback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); |
| defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); |
| assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| // The default request is lingering on cell, but nothing happens to cell, and we send no |
| // callbacks for it, because it's kept up by cellRequest. |
| callback.assertNoCallback(); |
| // Now unregister cellRequest and expect cell to start lingering. |
| mCm.unregisterNetworkCallback(noopCallback); |
| callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); |
| |
| // Let linger run its course. |
| callback.assertNoCallback(); |
| final int lingerTimeoutMs = mService.mLingerDelayMs + mService.mLingerDelayMs / 4; |
| callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent, lingerTimeoutMs); |
| |
| // Register a TRACK_DEFAULT request and check that it does not affect lingering. |
| TestNetworkCallback trackDefaultCallback = new TestNetworkCallback(); |
| mCm.registerDefaultNetworkCallback(trackDefaultCallback); |
| trackDefaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); |
| mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET); |
| mEthernetNetworkAgent.connect(true); |
| callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent); |
| callback.expectCallback(CallbackEntry.LOSING, mWiFiNetworkAgent); |
| callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent); |
| trackDefaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent); |
| defaultCallback.expectAvailableDoubleValidatedCallbacks(mEthernetNetworkAgent); |
| assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| |
| // Let linger run its course. |
| callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent, lingerTimeoutMs); |
| |
| // Clean up. |
| mEthernetNetworkAgent.disconnect(); |
| callback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); |
| defaultCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); |
| trackDefaultCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); |
| |
| mCm.unregisterNetworkCallback(callback); |
| mCm.unregisterNetworkCallback(defaultCallback); |
| mCm.unregisterNetworkCallback(trackDefaultCallback); |
| } |
| |
| private void grantUsingBackgroundNetworksPermissionForUid(final int uid) throws Exception { |
| grantUsingBackgroundNetworksPermissionForUid(uid, mContext.getPackageName()); |
| } |
| |
| private void grantUsingBackgroundNetworksPermissionForUid( |
| final int uid, final String packageName) throws Exception { |
| when(mPackageManager.getPackageInfo( |
| eq(packageName), eq(GET_PERMISSIONS | MATCH_ANY_USER))) |
| .thenReturn(buildPackageInfo(true /* hasSystemPermission */, uid)); |
| mService.mPermissionMonitor.onPackageAdded(packageName, uid); |
| } |
| |
| @Test |
| public void testNetworkGoesIntoBackgroundAfterLinger() throws Exception { |
| setAlwaysOnNetworks(true); |
| grantUsingBackgroundNetworksPermissionForUid(Binder.getCallingUid()); |
| NetworkRequest request = new NetworkRequest.Builder() |
| .clearCapabilities() |
| .build(); |
| TestNetworkCallback callback = new TestNetworkCallback(); |
| mCm.registerNetworkCallback(request, callback); |
| |
| TestNetworkCallback defaultCallback = new TestNetworkCallback(); |
| mCm.registerDefaultNetworkCallback(defaultCallback); |
| |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| |
| mCellNetworkAgent.connect(true); |
| callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| |
| // Wifi comes up and cell lingers. |
| mWiFiNetworkAgent.connect(true); |
| defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); |
| callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); |
| callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); |
| |
| // File a request for cellular, then release it. |
| NetworkRequest cellRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_CELLULAR).build(); |
| NetworkCallback noopCallback = new NetworkCallback(); |
| mCm.requestNetwork(cellRequest, noopCallback); |
| mCm.unregisterNetworkCallback(noopCallback); |
| callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); |
| |
| // Let linger run its course. |
| callback.assertNoCallback(); |
| final int lingerTimeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4; |
| callback.expectCapabilitiesWithout(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent, |
| lingerTimeoutMs); |
| |
| // Clean up. |
| mCm.unregisterNetworkCallback(defaultCallback); |
| mCm.unregisterNetworkCallback(callback); |
| } |
| |
| private NativeNetworkConfig nativeNetworkConfigPhysical(int netId, int permission) { |
| return new NativeNetworkConfig(netId, NativeNetworkType.PHYSICAL, permission, |
| /*secure=*/ false, VpnManager.TYPE_VPN_NONE); |
| } |
| |
| private NativeNetworkConfig nativeNetworkConfigVpn(int netId, boolean secure, int vpnType) { |
| return new NativeNetworkConfig(netId, NativeNetworkType.VIRTUAL, INetd.PERMISSION_NONE, |
| secure, vpnType); |
| } |
| |
| @Test |
| public void testNetworkAgentCallbacks() throws Exception { |
| // Keeps track of the order of events that happen in this test. |
| final LinkedBlockingQueue<String> eventOrder = new LinkedBlockingQueue<>(); |
| |
| final NetworkRequest request = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_WIFI).build(); |
| final TestNetworkCallback callback = new TestNetworkCallback(); |
| final AtomicReference<Network> wifiNetwork = new AtomicReference<>(); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| |
| // Expectations for state when various callbacks fire. These expectations run on the handler |
| // thread and not on the test thread because they need to prevent the handler thread from |
| // advancing while they examine state. |
| |
| // 1. When onCreated fires, netd has been told to create the network. |
| mWiFiNetworkAgent.setCreatedCallback(() -> { |
| eventOrder.offer("onNetworkCreated"); |
| wifiNetwork.set(mWiFiNetworkAgent.getNetwork()); |
| assertNotNull(wifiNetwork.get()); |
| try { |
| verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical( |
| wifiNetwork.get().getNetId(), INetd.PERMISSION_NONE)); |
| } catch (RemoteException impossible) { |
| fail(); |
| } |
| }); |
| |
| // 2. onNetworkUnwanted isn't precisely ordered with respect to any particular events. Just |
| // check that it is fired at some point after disconnect. |
| mWiFiNetworkAgent.setUnwantedCallback(() -> eventOrder.offer("onNetworkUnwanted")); |
| |
| // 3. While the teardown timer is running, connectivity APIs report the network is gone, but |
| // netd has not yet been told to destroy it. |
| final Runnable duringTeardown = () -> { |
| eventOrder.offer("timePasses"); |
| assertNull(mCm.getLinkProperties(wifiNetwork.get())); |
| try { |
| verify(mMockNetd, never()).networkDestroy(wifiNetwork.get().getNetId()); |
| } catch (RemoteException impossible) { |
| fail(); |
| } |
| }; |
| |
| // 4. After onNetworkDisconnected is called, connectivity APIs report the network is gone, |
| // and netd has been told to destroy it. |
| mWiFiNetworkAgent.setDisconnectedCallback(() -> { |
| eventOrder.offer("onNetworkDisconnected"); |
| assertNull(mCm.getLinkProperties(wifiNetwork.get())); |
| try { |
| verify(mMockNetd).networkDestroy(wifiNetwork.get().getNetId()); |
| } catch (RemoteException impossible) { |
| fail(); |
| } |
| }); |
| |
| // Connect a network, and file a request for it after it has come up, to ensure the nascent |
| // timer is cleared and the test does not have to wait for it. Filing the request after the |
| // network has come up is necessary because ConnectivityService does not appear to clear the |
| // nascent timer if the first request satisfied by the network was filed before the network |
| // connected. |
| // TODO: fix this bug, file the request before connecting, and remove the waitForIdle. |
| mWiFiNetworkAgent.connectWithoutInternet(); |
| waitForIdle(); |
| mCm.requestNetwork(request, callback); |
| callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| |
| // Set teardown delay and make sure CS has processed it. |
| mWiFiNetworkAgent.getNetworkAgent().setTeardownDelayMillis(300); |
| waitForIdle(); |
| |
| // Post the duringTeardown lambda to the handler so it fires while teardown is in progress. |
| // The delay must be long enough it will run after the unregisterNetworkCallback has torn |
| // down the network and started the teardown timer, and short enough that the lambda is |
| // scheduled to run before the teardown timer. |
| final Handler h = new Handler(mCsHandlerThread.getLooper()); |
| h.postDelayed(duringTeardown, 150); |
| |
| // Disconnect the network and check that events happened in the right order. |
| mCm.unregisterNetworkCallback(callback); |
| assertEquals("onNetworkCreated", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
| assertEquals("onNetworkUnwanted", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
| assertEquals("timePasses", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
| assertEquals("onNetworkDisconnected", eventOrder.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
| |
| mCm.unregisterNetworkCallback(callback); |
| } |
| |
| @Test |
| public void testExplicitlySelected() throws Exception { |
| NetworkRequest request = new NetworkRequest.Builder() |
| .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET) |
| .build(); |
| TestNetworkCallback callback = new TestNetworkCallback(); |
| mCm.registerNetworkCallback(request, callback); |
| |
| // Bring up validated cell. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| |
| // Bring up unvalidated wifi with explicitlySelected=true. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.explicitlySelected(true, false); |
| mWiFiNetworkAgent.connect(false); |
| callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| |
| // Cell Remains the default. |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| |
| // Lower wifi's score to below than cell, and check that it doesn't disconnect because |
| // it's explicitly selected. |
| mWiFiNetworkAgent.adjustScore(-40); |
| mWiFiNetworkAgent.adjustScore(40); |
| callback.assertNoCallback(); |
| |
| // If the user chooses yes on the "No Internet access, stay connected?" dialog, we switch to |
| // wifi even though it's unvalidated. |
| mCm.setAcceptUnvalidated(mWiFiNetworkAgent.getNetwork(), true, false); |
| callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| |
| // Disconnect wifi, and then reconnect, again with explicitlySelected=true. |
| mWiFiNetworkAgent.disconnect(); |
| callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.explicitlySelected(true, false); |
| mWiFiNetworkAgent.connect(false); |
| callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| |
| // If the user chooses no on the "No Internet access, stay connected?" dialog, we ask the |
| // network to disconnect. |
| mCm.setAcceptUnvalidated(mWiFiNetworkAgent.getNetwork(), false, false); |
| callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| |
| // Reconnect, again with explicitlySelected=true, but this time validate. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.explicitlySelected(true, false); |
| mWiFiNetworkAgent.connect(true); |
| callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); |
| callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| |
| mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET); |
| mEthernetNetworkAgent.connect(true); |
| callback.expectAvailableCallbacksUnvalidated(mEthernetNetworkAgent); |
| callback.expectCallback(CallbackEntry.LOSING, mWiFiNetworkAgent); |
| callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mEthernetNetworkAgent); |
| assertEquals(mEthernetNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| callback.assertNoCallback(); |
| |
| // Disconnect wifi, and then reconnect as if the user had selected "yes, don't ask again" |
| // (i.e., with explicitlySelected=true and acceptUnvalidated=true). Expect to switch to |
| // wifi immediately. |
| mWiFiNetworkAgent.disconnect(); |
| callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.explicitlySelected(true, true); |
| mWiFiNetworkAgent.connect(false); |
| callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| callback.expectCallback(CallbackEntry.LOSING, mEthernetNetworkAgent); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| mEthernetNetworkAgent.disconnect(); |
| callback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); |
| |
| // Disconnect and reconnect with explicitlySelected=false and acceptUnvalidated=true. |
| // Check that the network is not scored specially and that the device prefers cell data. |
| mWiFiNetworkAgent.disconnect(); |
| callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.explicitlySelected(false, true); |
| mWiFiNetworkAgent.connect(false); |
| callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| |
| // Clean up. |
| mWiFiNetworkAgent.disconnect(); |
| mCellNetworkAgent.disconnect(); |
| |
| callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| } |
| |
| private void tryNetworkFactoryRequests(int capability) throws Exception { |
| // Verify NOT_RESTRICTED is set appropriately |
| final NetworkCapabilities nc = new NetworkRequest.Builder().addCapability(capability) |
| .build().networkCapabilities; |
| if (capability == NET_CAPABILITY_CBS || capability == NET_CAPABILITY_DUN |
| || capability == NET_CAPABILITY_EIMS || capability == NET_CAPABILITY_FOTA |
| || capability == NET_CAPABILITY_IA || capability == NET_CAPABILITY_IMS |
| || capability == NET_CAPABILITY_RCS || capability == NET_CAPABILITY_XCAP |
| || capability == NET_CAPABILITY_VSIM || capability == NET_CAPABILITY_BIP |
| || capability == NET_CAPABILITY_ENTERPRISE) { |
| assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); |
| } else { |
| assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); |
| } |
| |
| NetworkCapabilities filter = new NetworkCapabilities(); |
| filter.addTransportType(TRANSPORT_CELLULAR); |
| filter.addCapability(capability); |
| // Add NOT_VCN_MANAGED capability into filter unconditionally since some requests will add |
| // NOT_VCN_MANAGED automatically but not for NetworkCapabilities, |
| // see {@code NetworkCapabilities#deduceNotVcnManagedCapability} for more details. |
| filter.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); |
| final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests"); |
| handlerThread.start(); |
| final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), |
| mServiceContext, "testFactory", filter, mCsHandlerThread); |
| testFactory.setScoreFilter(45); |
| testFactory.register(); |
| |
| final NetworkCallback networkCallback; |
| if (capability != NET_CAPABILITY_INTERNET) { |
| // If the capability passed in argument is part of the default request, then the |
| // factory will see the default request. Otherwise the filter will prevent the |
| // factory from seeing it. In that case, add a request so it can be tested. |
| assertFalse(testFactory.getMyStartRequested()); |
| NetworkRequest request = new NetworkRequest.Builder().addCapability(capability).build(); |
| networkCallback = new NetworkCallback(); |
| mCm.requestNetwork(request, networkCallback); |
| } else { |
| networkCallback = null; |
| } |
| testFactory.expectRequestAdd(); |
| testFactory.assertRequestCountEquals(1); |
| assertTrue(testFactory.getMyStartRequested()); |
| |
| // Now bring in a higher scored network. |
| TestNetworkAgentWrapper testAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| // When testAgent connects, because of its score (50 legacy int / cell transport) |
| // it will beat or equal the testFactory's offer, so the request will be removed. |
| // Note the agent as validated only if the capability is INTERNET, as it's the only case |
| // where it makes sense. |
| testAgent.connect(NET_CAPABILITY_INTERNET == capability /* validated */); |
| testAgent.addCapability(capability); |
| testFactory.expectRequestRemove(); |
| testFactory.assertRequestCountEquals(0); |
| assertFalse(testFactory.getMyStartRequested()); |
| |
| // Add a request and make sure it's not sent to the factory, because the agent |
| // is satisfying it better. |
| final NetworkCallback cb = new ConnectivityManager.NetworkCallback(); |
| mCm.requestNetwork(new NetworkRequest.Builder().addCapability(capability).build(), cb); |
| expectNoRequestChanged(testFactory); |
| testFactory.assertRequestCountEquals(0); |
| assertFalse(testFactory.getMyStartRequested()); |
| |
| // If using legacy scores, make the test agent weak enough to have the exact same score as |
| // the factory (50 for cell - 5 adjustment). Make sure the factory doesn't see the request. |
| // If not using legacy score, this is a no-op and the "same score removes request" behavior |
| // has already been tested above. |
| testAgent.adjustScore(-5); |
| expectNoRequestChanged(testFactory); |
| assertFalse(testFactory.getMyStartRequested()); |
| |
| // Make the test agent weak enough that the factory will see the two requests (the one that |
| // was just sent, and either the default one or the one sent at the top of this test if |
| // the default won't be seen). |
| testAgent.setScore(new NetworkScore.Builder().setLegacyInt(2).setExiting(true).build()); |
| testFactory.expectRequestAdds(2); |
| testFactory.assertRequestCountEquals(2); |
| assertTrue(testFactory.getMyStartRequested()); |
| |
| // Now unregister and make sure the request is removed. |
| mCm.unregisterNetworkCallback(cb); |
| testFactory.expectRequestRemove(); |
| |
| // Bring in a bunch of requests. |
| assertEquals(1, testFactory.getMyRequestCount()); |
| ConnectivityManager.NetworkCallback[] networkCallbacks = |
| new ConnectivityManager.NetworkCallback[10]; |
| for (int i = 0; i< networkCallbacks.length; i++) { |
| networkCallbacks[i] = new ConnectivityManager.NetworkCallback(); |
| NetworkRequest.Builder builder = new NetworkRequest.Builder(); |
| builder.addCapability(capability); |
| mCm.requestNetwork(builder.build(), networkCallbacks[i]); |
| } |
| testFactory.expectRequestAdds(10); |
| testFactory.assertRequestCountEquals(11); // +1 for the default/test specific request |
| assertTrue(testFactory.getMyStartRequested()); |
| |
| // Remove the requests. |
| for (int i = 0; i < networkCallbacks.length; i++) { |
| mCm.unregisterNetworkCallback(networkCallbacks[i]); |
| } |
| testFactory.expectRequestRemoves(10); |
| testFactory.assertRequestCountEquals(1); |
| assertTrue(testFactory.getMyStartRequested()); |
| |
| // Adjust the agent score up again. Expect the request to be withdrawn. |
| testAgent.setScore(new NetworkScore.Builder().setLegacyInt(50).build()); |
| testFactory.expectRequestRemove(); |
| testFactory.assertRequestCountEquals(0); |
| assertFalse(testFactory.getMyStartRequested()); |
| |
| // Drop the higher scored network. |
| testAgent.disconnect(); |
| testFactory.expectRequestAdd(); |
| testFactory.assertRequestCountEquals(1); |
| assertEquals(1, testFactory.getMyRequestCount()); |
| assertTrue(testFactory.getMyStartRequested()); |
| |
| testFactory.terminate(); |
| if (networkCallback != null) mCm.unregisterNetworkCallback(networkCallback); |
| handlerThread.quit(); |
| } |
| |
| @Test |
| public void testNetworkFactoryRequests() throws Exception { |
| tryNetworkFactoryRequests(NET_CAPABILITY_MMS); |
| tryNetworkFactoryRequests(NET_CAPABILITY_SUPL); |
| tryNetworkFactoryRequests(NET_CAPABILITY_DUN); |
| tryNetworkFactoryRequests(NET_CAPABILITY_FOTA); |
| tryNetworkFactoryRequests(NET_CAPABILITY_IMS); |
| tryNetworkFactoryRequests(NET_CAPABILITY_CBS); |
| tryNetworkFactoryRequests(NET_CAPABILITY_WIFI_P2P); |
| tryNetworkFactoryRequests(NET_CAPABILITY_IA); |
| tryNetworkFactoryRequests(NET_CAPABILITY_RCS); |
| tryNetworkFactoryRequests(NET_CAPABILITY_XCAP); |
| tryNetworkFactoryRequests(NET_CAPABILITY_ENTERPRISE); |
| tryNetworkFactoryRequests(NET_CAPABILITY_EIMS); |
| tryNetworkFactoryRequests(NET_CAPABILITY_NOT_METERED); |
| tryNetworkFactoryRequests(NET_CAPABILITY_INTERNET); |
| tryNetworkFactoryRequests(NET_CAPABILITY_TRUSTED); |
| tryNetworkFactoryRequests(NET_CAPABILITY_NOT_VPN); |
| tryNetworkFactoryRequests(NET_CAPABILITY_VSIM); |
| tryNetworkFactoryRequests(NET_CAPABILITY_BIP); |
| // Skipping VALIDATED and CAPTIVE_PORTAL as they're disallowed. |
| } |
| |
| @Test |
| public void testRegisterIgnoringScore() throws Exception { |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.setScore(new NetworkScore.Builder().setLegacyInt(90).build()); |
| mWiFiNetworkAgent.connect(true /* validated */); |
| |
| // Make sure the factory sees the default network |
| final NetworkCapabilities filter = new NetworkCapabilities(); |
| filter.addTransportType(TRANSPORT_CELLULAR); |
| filter.addCapability(NET_CAPABILITY_INTERNET); |
| filter.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); |
| final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests"); |
| handlerThread.start(); |
| final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), |
| mServiceContext, "testFactory", filter, mCsHandlerThread); |
| testFactory.register(); |
| |
| final MockNetworkFactory testFactoryAll = new MockNetworkFactory(handlerThread.getLooper(), |
| mServiceContext, "testFactoryAll", filter, mCsHandlerThread); |
| testFactoryAll.registerIgnoringScore(); |
| |
| // The regular test factory should not see the request, because WiFi is stronger than cell. |
| expectNoRequestChanged(testFactory); |
| // With ignoringScore though the request is seen. |
| testFactoryAll.expectRequestAdd(); |
| |
| // The legacy int will be ignored anyway, set the only other knob to true |
| mWiFiNetworkAgent.setScore(new NetworkScore.Builder().setLegacyInt(110) |
| .setTransportPrimary(true).build()); |
| |
| expectNoRequestChanged(testFactory); // still not seeing the request |
| expectNoRequestChanged(testFactoryAll); // still seeing the request |
| |
| mWiFiNetworkAgent.disconnect(); |
| } |
| |
| @Test |
| public void testNetworkFactoryUnregister() throws Exception { |
| // Make sure the factory sees the default network |
| final NetworkCapabilities filter = new NetworkCapabilities(); |
| filter.addCapability(NET_CAPABILITY_INTERNET); |
| filter.addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); |
| |
| final HandlerThread handlerThread = new HandlerThread("testNetworkFactoryRequests"); |
| handlerThread.start(); |
| |
| // Checks that calling setScoreFilter on a NetworkFactory immediately before closing it |
| // does not crash. |
| for (int i = 0; i < 100; i++) { |
| final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), |
| mServiceContext, "testFactory", filter, mCsHandlerThread); |
| // Register the factory and don't be surprised when the default request arrives. |
| testFactory.register(); |
| testFactory.expectRequestAdd(); |
| |
| testFactory.setScoreFilter(42); |
| testFactory.terminate(); |
| |
| if (i % 2 == 0) { |
| try { |
| testFactory.register(); |
| fail("Re-registering terminated NetworkFactory should throw"); |
| } catch (IllegalStateException expected) { |
| } |
| } |
| } |
| handlerThread.quit(); |
| } |
| |
| @Test |
| public void testNoMutableNetworkRequests() throws Exception { |
| final PendingIntent pendingIntent = PendingIntent.getBroadcast( |
| mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE); |
| NetworkRequest request1 = new NetworkRequest.Builder() |
| .addCapability(NET_CAPABILITY_VALIDATED) |
| .build(); |
| NetworkRequest request2 = new NetworkRequest.Builder() |
| .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL) |
| .build(); |
| |
| Class<IllegalArgumentException> expected = IllegalArgumentException.class; |
| assertThrows(expected, () -> mCm.requestNetwork(request1, new NetworkCallback())); |
| assertThrows(expected, () -> mCm.requestNetwork(request1, pendingIntent)); |
| assertThrows(expected, () -> mCm.requestNetwork(request2, new NetworkCallback())); |
| assertThrows(expected, () -> mCm.requestNetwork(request2, pendingIntent)); |
| } |
| |
| @Test |
| public void testMMSonWiFi() throws Exception { |
| // Test bringing up cellular without MMS NetworkRequest gets reaped |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS); |
| mCellNetworkAgent.connectWithoutInternet(); |
| mCellNetworkAgent.expectDisconnected(); |
| waitForIdle(); |
| assertEmpty(mCm.getAllNetworks()); |
| verifyNoNetwork(); |
| |
| // Test bringing up validated WiFi. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| final ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); |
| mWiFiNetworkAgent.connect(true); |
| b.expectBroadcast(); |
| verifyActiveNetwork(TRANSPORT_WIFI); |
| |
| // Register MMS NetworkRequest |
| NetworkRequest.Builder builder = new NetworkRequest.Builder(); |
| builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS); |
| final TestNetworkCallback networkCallback = new TestNetworkCallback(); |
| mCm.requestNetwork(builder.build(), networkCallback); |
| |
| // Test bringing up unvalidated cellular with MMS |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS); |
| mCellNetworkAgent.connectWithoutInternet(); |
| networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); |
| verifyActiveNetwork(TRANSPORT_WIFI); |
| |
| // Test releasing NetworkRequest disconnects cellular with MMS |
| mCm.unregisterNetworkCallback(networkCallback); |
| mCellNetworkAgent.expectDisconnected(); |
| verifyActiveNetwork(TRANSPORT_WIFI); |
| } |
| |
| @Test |
| public void testMMSonCell() throws Exception { |
| // Test bringing up cellular without MMS |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| ExpectedBroadcast b = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED); |
| mCellNetworkAgent.connect(false); |
| b.expectBroadcast(); |
| verifyActiveNetwork(TRANSPORT_CELLULAR); |
| |
| // Register MMS NetworkRequest |
| NetworkRequest.Builder builder = new NetworkRequest.Builder(); |
| builder.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS); |
| final TestNetworkCallback networkCallback = new TestNetworkCallback(); |
| mCm.requestNetwork(builder.build(), networkCallback); |
| |
| // Test bringing up MMS cellular network |
| TestNetworkAgentWrapper |
| mmsNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS); |
| mmsNetworkAgent.connectWithoutInternet(); |
| networkCallback.expectAvailableCallbacksUnvalidated(mmsNetworkAgent); |
| verifyActiveNetwork(TRANSPORT_CELLULAR); |
| |
| // Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent |
| mCm.unregisterNetworkCallback(networkCallback); |
| mmsNetworkAgent.expectDisconnected(); |
| verifyActiveNetwork(TRANSPORT_CELLULAR); |
| } |
| |
| @Test |
| public void testPartialConnectivity() throws Exception { |
| // Register network callback. |
| NetworkRequest request = new NetworkRequest.Builder() |
| .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET) |
| .build(); |
| TestNetworkCallback callback = new TestNetworkCallback(); |
| mCm.registerNetworkCallback(request, callback); |
| |
| // Bring up validated mobile data. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| |
| // Bring up wifi with partial connectivity. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connectWithPartialConnectivity(); |
| callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent); |
| |
| // Mobile data should be the default network. |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| callback.assertNoCallback(); |
| |
| // With HTTPS probe disabled, NetworkMonitor should pass the network validation with http |
| // probe. |
| 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 */, |
| false /* always */); |
| // If user accepts partial connectivity network, |
| // NetworkMonitor#setAcceptPartialConnectivity() should be called too. |
| waitForIdle(); |
| verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity(); |
| |
| // Need a trigger point to let NetworkMonitor tell ConnectivityService that network is |
| // validated. |
| mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true); |
| callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); |
| NetworkCapabilities nc = callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, |
| mWiFiNetworkAgent); |
| assertTrue(nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY)); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| |
| // Disconnect and reconnect wifi with partial connectivity again. |
| mWiFiNetworkAgent.disconnect(); |
| callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connectWithPartialConnectivity(); |
| callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent); |
| |
| // Mobile data should be the default network. |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| |
| // If the user chooses no, disconnect wifi immediately. |
| mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), false/* accept */, |
| false /* always */); |
| callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| |
| // If user accepted partial connectivity before, and device reconnects to that network |
| // again, but now the network has full connectivity. The network shouldn't contain |
| // NET_CAPABILITY_PARTIAL_CONNECTIVITY. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| // acceptUnvalidated is also used as setting for accepting partial networks. |
| mWiFiNetworkAgent.explicitlySelected(true /* explicitlySelected */, |
| true /* acceptUnvalidated */); |
| mWiFiNetworkAgent.connect(true); |
| |
| // If user accepted partial connectivity network before, |
| // NetworkMonitor#setAcceptPartialConnectivity() will be called in |
| // ConnectivityService#updateNetworkInfo(). |
| callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity(); |
| callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); |
| nc = callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); |
| assertFalse(nc.hasCapability(NET_CAPABILITY_PARTIAL_CONNECTIVITY)); |
| |
| // Wifi should be the default network. |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| mWiFiNetworkAgent.disconnect(); |
| callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| |
| // The user accepted partial connectivity and selected "don't ask again". Now the user |
| // reconnects to the partial connectivity network. Switch to wifi as soon as partial |
| // connectivity is detected. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.explicitlySelected(true /* explicitlySelected */, |
| true /* acceptUnvalidated */); |
| mWiFiNetworkAgent.connectWithPartialConnectivity(); |
| // If user accepted partial connectivity network before, |
| // NetworkMonitor#setAcceptPartialConnectivity() will be called in |
| // ConnectivityService#updateNetworkInfo(). |
| callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity(); |
| callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| callback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, mWiFiNetworkAgent); |
| mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */); |
| |
| // Need a trigger point to let NetworkMonitor tell ConnectivityService that network is |
| // validated. |
| mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true); |
| callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); |
| mWiFiNetworkAgent.disconnect(); |
| callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| |
| // If the user accepted partial connectivity, and the device auto-reconnects to the partial |
| // connectivity network, it should contain both PARTIAL_CONNECTIVITY and VALIDATED. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.explicitlySelected(false /* explicitlySelected */, |
| true /* acceptUnvalidated */); |
| |
| // NetworkMonitor will immediately (once the HTTPS probe fails...) report the network as |
| // valid, because ConnectivityService calls setAcceptPartialConnectivity before it calls |
| // notifyNetworkConnected. |
| mWiFiNetworkAgent.connectWithPartialValidConnectivity(false /* isStrictMode */); |
| callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| verify(mWiFiNetworkAgent.mNetworkMonitor, times(1)).setAcceptPartialConnectivity(); |
| callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); |
| callback.expectCapabilitiesWith( |
| NET_CAPABILITY_PARTIAL_CONNECTIVITY | NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); |
| mWiFiNetworkAgent.disconnect(); |
| callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| } |
| |
| @Test |
| public void testCaptivePortalOnPartialConnectivity() throws Exception { |
| final TestNetworkCallback captivePortalCallback = new TestNetworkCallback(); |
| final NetworkRequest captivePortalRequest = new NetworkRequest.Builder() |
| .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build(); |
| mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback); |
| |
| final TestNetworkCallback validatedCallback = new TestNetworkCallback(); |
| final NetworkRequest validatedRequest = new NetworkRequest.Builder() |
| .addCapability(NET_CAPABILITY_VALIDATED).build(); |
| mCm.registerNetworkCallback(validatedRequest, validatedCallback); |
| |
| // Bring up a network with a captive portal. |
| // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| String redirectUrl = "http://android.com/path"; |
| mWiFiNetworkAgent.connectWithCaptivePortal(redirectUrl, false /* isStrictMode */); |
| captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), redirectUrl); |
| |
| // Check that startCaptivePortalApp sends the expected command to NetworkMonitor. |
| mCm.startCaptivePortalApp(mWiFiNetworkAgent.getNetwork()); |
| verify(mWiFiNetworkAgent.mNetworkMonitor, timeout(TIMEOUT_MS).times(1)) |
| .launchCaptivePortalApp(); |
| |
| // Report that the captive portal is dismissed with partial connectivity, and check that |
| // callbacks are fired. |
| mWiFiNetworkAgent.setNetworkPartial(); |
| mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true); |
| waitForIdle(); |
| captivePortalCallback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, |
| mWiFiNetworkAgent); |
| |
| // Report partial connectivity is accepted. |
| mWiFiNetworkAgent.setNetworkPartialValid(false /* isStrictMode */); |
| mCm.setAcceptPartialConnectivity(mWiFiNetworkAgent.getNetwork(), true /* accept */, |
| false /* always */); |
| waitForIdle(); |
| mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true); |
| captivePortalCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); |
| NetworkCapabilities nc = |
| validatedCallback.expectCapabilitiesWith(NET_CAPABILITY_PARTIAL_CONNECTIVITY, |
| mWiFiNetworkAgent); |
| |
| mCm.unregisterNetworkCallback(captivePortalCallback); |
| mCm.unregisterNetworkCallback(validatedCallback); |
| } |
| |
| @Test |
| public void testCaptivePortal() throws Exception { |
| final TestNetworkCallback captivePortalCallback = new TestNetworkCallback(); |
| final NetworkRequest captivePortalRequest = new NetworkRequest.Builder() |
| .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build(); |
| mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback); |
| |
| final TestNetworkCallback validatedCallback = new TestNetworkCallback(); |
| final NetworkRequest validatedRequest = new NetworkRequest.Builder() |
| .addCapability(NET_CAPABILITY_VALIDATED).build(); |
| mCm.registerNetworkCallback(validatedRequest, validatedCallback); |
| |
| // Bring up a network with a captive portal. |
| // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| String firstRedirectUrl = "http://example.com/firstPath"; |
| mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl, false /* isStrictMode */); |
| captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| assertEquals(mWiFiNetworkAgent.waitForRedirectUrl(), firstRedirectUrl); |
| |
| // Take down network. |
| // Expect onLost callback. |
| mWiFiNetworkAgent.disconnect(); |
| captivePortalCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| |
| // Bring up a network with a captive portal. |
| // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| String secondRedirectUrl = "http://example.com/secondPath"; |
| 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(false /* isStrictMode */); |
| mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true); |
| captivePortalCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| |
| // Expect NET_CAPABILITY_VALIDATED onAvailable callback. |
| validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); |
| |
| // Break network connectivity. |
| // Expect NET_CAPABILITY_VALIDATED onLost callback. |
| mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */); |
| mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false); |
| validatedCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| } |
| |
| @Test |
| public void testCaptivePortalApp() throws Exception { |
| final TestNetworkCallback captivePortalCallback = new TestNetworkCallback(); |
| final NetworkRequest captivePortalRequest = new NetworkRequest.Builder() |
| .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build(); |
| mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback); |
| |
| final TestNetworkCallback validatedCallback = new TestNetworkCallback(); |
| final NetworkRequest validatedRequest = new NetworkRequest.Builder() |
| .addCapability(NET_CAPABILITY_VALIDATED).build(); |
| mCm.registerNetworkCallback(validatedRequest, validatedCallback); |
| |
| // Bring up wifi. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(true); |
| validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); |
| Network wifiNetwork = mWiFiNetworkAgent.getNetwork(); |
| |
| // Check that calling startCaptivePortalApp does nothing. |
| final int fastTimeoutMs = 100; |
| mCm.startCaptivePortalApp(wifiNetwork); |
| waitForIdle(); |
| verify(mWiFiNetworkAgent.mNetworkMonitor, never()).launchCaptivePortalApp(); |
| mServiceContext.expectNoStartActivityIntent(fastTimeoutMs); |
| |
| // Turn into a captive portal. |
| mWiFiNetworkAgent.setNetworkPortal("http://example.com", false /* isStrictMode */); |
| mCm.reportNetworkConnectivity(wifiNetwork, false); |
| captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| validatedCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| |
| // Check that startCaptivePortalApp sends the expected command to NetworkMonitor. |
| mCm.startCaptivePortalApp(wifiNetwork); |
| waitForIdle(); |
| verify(mWiFiNetworkAgent.mNetworkMonitor).launchCaptivePortalApp(); |
| |
| // NetworkMonitor uses startCaptivePortal(Network, Bundle) (startCaptivePortalAppInternal) |
| final Bundle testBundle = new Bundle(); |
| final String testKey = "testkey"; |
| final String testValue = "testvalue"; |
| testBundle.putString(testKey, testValue); |
| mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, |
| PERMISSION_GRANTED); |
| mCm.startCaptivePortalApp(wifiNetwork, testBundle); |
| final Intent signInIntent = mServiceContext.expectStartActivityIntent(TIMEOUT_MS); |
| assertEquals(ACTION_CAPTIVE_PORTAL_SIGN_IN, signInIntent.getAction()); |
| assertEquals(testValue, signInIntent.getStringExtra(testKey)); |
| |
| // Report that the captive portal is dismissed, and check that callbacks are fired |
| mWiFiNetworkAgent.setNetworkValid(false /* isStrictMode */); |
| mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid()); |
| validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); |
| captivePortalCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| |
| mCm.unregisterNetworkCallback(validatedCallback); |
| mCm.unregisterNetworkCallback(captivePortalCallback); |
| } |
| |
| @Test |
| public void testAvoidOrIgnoreCaptivePortals() throws Exception { |
| final TestNetworkCallback captivePortalCallback = new TestNetworkCallback(); |
| final NetworkRequest captivePortalRequest = new NetworkRequest.Builder() |
| .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build(); |
| mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback); |
| |
| final TestNetworkCallback validatedCallback = new TestNetworkCallback(); |
| final NetworkRequest validatedRequest = new NetworkRequest.Builder() |
| .addCapability(NET_CAPABILITY_VALIDATED).build(); |
| mCm.registerNetworkCallback(validatedRequest, validatedCallback); |
| |
| setCaptivePortalMode(ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE_AVOID); |
| // Bring up a network with a captive portal. |
| // Expect it to fail to connect and not result in any callbacks. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| String firstRedirectUrl = "http://example.com/firstPath"; |
| |
| mWiFiNetworkAgent.connectWithCaptivePortal(firstRedirectUrl, false /* isStrictMode */); |
| mWiFiNetworkAgent.expectDisconnected(); |
| mWiFiNetworkAgent.expectPreventReconnectReceived(); |
| |
| assertNoCallbacks(captivePortalCallback, validatedCallback); |
| } |
| |
| @Test |
| public void testCaptivePortalApi() throws Exception { |
| mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED); |
| |
| final TestNetworkCallback captivePortalCallback = new TestNetworkCallback(); |
| final NetworkRequest captivePortalRequest = new NetworkRequest.Builder() |
| .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build(); |
| mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback); |
| |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| final String redirectUrl = "http://example.com/firstPath"; |
| |
| mWiFiNetworkAgent.connectWithCaptivePortal(redirectUrl, false /* isStrictMode */); |
| captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| |
| final CaptivePortalData testData = new CaptivePortalData.Builder() |
| .setUserPortalUrl(Uri.parse(redirectUrl)) |
| .setBytesRemaining(12345L) |
| .build(); |
| |
| mWiFiNetworkAgent.notifyCapportApiDataChanged(testData); |
| |
| captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, |
| lp -> testData.equals(lp.getCaptivePortalData())); |
| |
| final LinkProperties newLps = new LinkProperties(); |
| newLps.setMtu(1234); |
| mWiFiNetworkAgent.sendLinkProperties(newLps); |
| // CaptivePortalData is not lost and unchanged when LPs are received from the NetworkAgent |
| captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, |
| lp -> testData.equals(lp.getCaptivePortalData()) && lp.getMtu() == 1234); |
| } |
| |
| private TestNetworkCallback setupNetworkCallbackAndConnectToWifi() throws Exception { |
| // Grant NETWORK_SETTINGS permission to be able to receive LinkProperties change callbacks |
| // with sensitive (captive portal) data |
| mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED); |
| |
| final TestNetworkCallback captivePortalCallback = new TestNetworkCallback(); |
| final NetworkRequest captivePortalRequest = new NetworkRequest.Builder() |
| .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build(); |
| mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback); |
| |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| |
| mWiFiNetworkAgent.connectWithCaptivePortal(TEST_REDIRECT_URL, false /* isStrictMode */); |
| captivePortalCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| return captivePortalCallback; |
| } |
| |
| private class CaptivePortalTestData { |
| CaptivePortalTestData(CaptivePortalData naPasspointData, CaptivePortalData capportData, |
| CaptivePortalData naOtherData, CaptivePortalData expectedMergedPasspointData, |
| CaptivePortalData expectedMergedOtherData) { |
| mNaPasspointData = naPasspointData; |
| mCapportData = capportData; |
| mNaOtherData = naOtherData; |
| mExpectedMergedPasspointData = expectedMergedPasspointData; |
| mExpectedMergedOtherData = expectedMergedOtherData; |
| } |
| |
| public final CaptivePortalData mNaPasspointData; |
| public final CaptivePortalData mCapportData; |
| public final CaptivePortalData mNaOtherData; |
| public final CaptivePortalData mExpectedMergedPasspointData; |
| public final CaptivePortalData mExpectedMergedOtherData; |
| |
| } |
| |
| private CaptivePortalTestData setupCaptivePortalData() { |
| final CaptivePortalData capportData = new CaptivePortalData.Builder() |
| .setUserPortalUrl(Uri.parse(TEST_REDIRECT_URL)) |
| .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_CAPPORT)) |
| .setUserPortalUrl(Uri.parse(TEST_USER_PORTAL_API_URL_CAPPORT)) |
| .setExpiryTime(1000000L) |
| .setBytesRemaining(12345L) |
| .build(); |
| |
| final CaptivePortalData naPasspointData = new CaptivePortalData.Builder() |
| .setBytesRemaining(80802L) |
| .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA_PASSPOINT), |
| CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) |
| .setUserPortalUrl(Uri.parse(TEST_TERMS_AND_CONDITIONS_URL_NA_PASSPOINT), |
| CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) |
| .setVenueFriendlyName(TEST_FRIENDLY_NAME).build(); |
| |
| final CaptivePortalData naOtherData = new CaptivePortalData.Builder() |
| .setBytesRemaining(80802L) |
| .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA_OTHER), |
| CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER) |
| .setUserPortalUrl(Uri.parse(TEST_TERMS_AND_CONDITIONS_URL_NA_OTHER), |
| CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_OTHER) |
| .setVenueFriendlyName(TEST_FRIENDLY_NAME).build(); |
| |
| final CaptivePortalData expectedMergedPasspointData = new CaptivePortalData.Builder() |
| .setUserPortalUrl(Uri.parse(TEST_REDIRECT_URL)) |
| .setBytesRemaining(12345L) |
| .setExpiryTime(1000000L) |
| .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_NA_PASSPOINT), |
| CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) |
| .setUserPortalUrl(Uri.parse(TEST_TERMS_AND_CONDITIONS_URL_NA_PASSPOINT), |
| CaptivePortalData.CAPTIVE_PORTAL_DATA_SOURCE_PASSPOINT) |
| .setVenueFriendlyName(TEST_FRIENDLY_NAME).build(); |
| |
| final CaptivePortalData expectedMergedOtherData = new CaptivePortalData.Builder() |
| .setUserPortalUrl(Uri.parse(TEST_REDIRECT_URL)) |
| .setBytesRemaining(12345L) |
| .setExpiryTime(1000000L) |
| .setVenueInfoUrl(Uri.parse(TEST_VENUE_URL_CAPPORT)) |
| .setUserPortalUrl(Uri.parse(TEST_USER_PORTAL_API_URL_CAPPORT)) |
| .setVenueFriendlyName(TEST_FRIENDLY_NAME).build(); |
| return new CaptivePortalTestData(naPasspointData, capportData, naOtherData, |
| expectedMergedPasspointData, expectedMergedOtherData); |
| } |
| |
| @Test |
| public void testMergeCaptivePortalApiWithFriendlyNameAndVenueUrl() throws Exception { |
| final TestNetworkCallback captivePortalCallback = setupNetworkCallbackAndConnectToWifi(); |
| final CaptivePortalTestData captivePortalTestData = setupCaptivePortalData(); |
| |
| // Baseline capport data |
| mWiFiNetworkAgent.notifyCapportApiDataChanged(captivePortalTestData.mCapportData); |
| |
| captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, |
| lp -> captivePortalTestData.mCapportData.equals(lp.getCaptivePortalData())); |
| |
| // Venue URL, T&C URL and friendly name from Network agent with Passpoint source, confirm |
| // that API data gets precedence on the bytes remaining. |
| final LinkProperties linkProperties = new LinkProperties(); |
| linkProperties.setCaptivePortalData(captivePortalTestData.mNaPasspointData); |
| mWiFiNetworkAgent.sendLinkProperties(linkProperties); |
| |
| // Make sure that the capport data is merged |
| captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, |
| lp -> captivePortalTestData.mExpectedMergedPasspointData |
| .equals(lp.getCaptivePortalData())); |
| |
| // Now send this information from non-Passpoint source, confirm that Capport data takes |
| // precedence |
| linkProperties.setCaptivePortalData(captivePortalTestData.mNaOtherData); |
| mWiFiNetworkAgent.sendLinkProperties(linkProperties); |
| |
| // Make sure that the capport data is merged |
| captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, |
| lp -> captivePortalTestData.mExpectedMergedOtherData |
| .equals(lp.getCaptivePortalData())); |
| |
| // Create a new LP with no Network agent capport data |
| final LinkProperties newLps = new LinkProperties(); |
| newLps.setMtu(1234); |
| mWiFiNetworkAgent.sendLinkProperties(newLps); |
| // CaptivePortalData is not lost and has the original values when LPs are received from the |
| // NetworkAgent |
| captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, |
| lp -> captivePortalTestData.mCapportData.equals(lp.getCaptivePortalData()) |
| && lp.getMtu() == 1234); |
| |
| // Now send capport data only from the Network agent |
| mWiFiNetworkAgent.notifyCapportApiDataChanged(null); |
| captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, |
| lp -> lp.getCaptivePortalData() == null); |
| |
| newLps.setCaptivePortalData(captivePortalTestData.mNaPasspointData); |
| mWiFiNetworkAgent.sendLinkProperties(newLps); |
| |
| // Make sure that only the network agent capport data is available |
| captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, |
| lp -> captivePortalTestData.mNaPasspointData.equals(lp.getCaptivePortalData())); |
| } |
| |
| @Test |
| public void testMergeCaptivePortalDataFromNetworkAgentFirstThenCapport() throws Exception { |
| final TestNetworkCallback captivePortalCallback = setupNetworkCallbackAndConnectToWifi(); |
| final CaptivePortalTestData captivePortalTestData = setupCaptivePortalData(); |
| |
| // Venue URL and friendly name from Network agent, confirm that API data gets precedence |
| // on the bytes remaining. |
| final LinkProperties linkProperties = new LinkProperties(); |
| linkProperties.setCaptivePortalData(captivePortalTestData.mNaPasspointData); |
| mWiFiNetworkAgent.sendLinkProperties(linkProperties); |
| |
| // Make sure that the data is saved correctly |
| captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, |
| lp -> captivePortalTestData.mNaPasspointData.equals(lp.getCaptivePortalData())); |
| |
| // Expected merged data: Network agent data is preferred, and values that are not used by |
| // it are merged from capport data |
| mWiFiNetworkAgent.notifyCapportApiDataChanged(captivePortalTestData.mCapportData); |
| |
| // Make sure that the Capport data is merged correctly |
| captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, |
| lp -> captivePortalTestData.mExpectedMergedPasspointData.equals( |
| lp.getCaptivePortalData())); |
| |
| // Now set the naData to null |
| linkProperties.setCaptivePortalData(null); |
| mWiFiNetworkAgent.sendLinkProperties(linkProperties); |
| |
| // Make sure that the Capport data is retained correctly |
| captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, |
| lp -> captivePortalTestData.mCapportData.equals(lp.getCaptivePortalData())); |
| } |
| |
| @Test |
| public void testMergeCaptivePortalDataFromNetworkAgentOtherSourceFirstThenCapport() |
| throws Exception { |
| final TestNetworkCallback captivePortalCallback = setupNetworkCallbackAndConnectToWifi(); |
| final CaptivePortalTestData captivePortalTestData = setupCaptivePortalData(); |
| |
| // Venue URL and friendly name from Network agent, confirm that API data gets precedence |
| // on the bytes remaining. |
| final LinkProperties linkProperties = new LinkProperties(); |
| linkProperties.setCaptivePortalData(captivePortalTestData.mNaOtherData); |
| mWiFiNetworkAgent.sendLinkProperties(linkProperties); |
| |
| // Make sure that the data is saved correctly |
| captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, |
| lp -> captivePortalTestData.mNaOtherData.equals(lp.getCaptivePortalData())); |
| |
| // Expected merged data: Network agent data is preferred, and values that are not used by |
| // it are merged from capport data |
| mWiFiNetworkAgent.notifyCapportApiDataChanged(captivePortalTestData.mCapportData); |
| |
| // Make sure that the Capport data is merged correctly |
| captivePortalCallback.expectLinkPropertiesThat(mWiFiNetworkAgent, |
| lp -> captivePortalTestData.mExpectedMergedOtherData.equals( |
| lp.getCaptivePortalData())); |
| } |
| |
| private NetworkRequest.Builder newWifiRequestBuilder() { |
| return new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI); |
| } |
| |
| /** |
| * Verify request matching behavior with network specifiers. |
| * |
| * This test does not check updating the specifier on a live network because the specifier is |
| * immutable and this triggers a WTF in |
| * {@link ConnectivityService#mixInCapabilities(NetworkAgentInfo, NetworkCapabilities)}. |
| */ |
| @Test |
| public void testNetworkSpecifier() throws Exception { |
| // A NetworkSpecifier subclass that matches all networks but must not be visible to apps. |
| class ConfidentialMatchAllNetworkSpecifier extends NetworkSpecifier implements |
| Parcelable { |
| @Override |
| public boolean canBeSatisfiedBy(NetworkSpecifier other) { |
| return true; |
| } |
| |
| @Override |
| public int describeContents() { |
| return 0; |
| } |
| |
| @Override |
| public void writeToParcel(Parcel dest, int flags) {} |
| |
| @Override |
| public NetworkSpecifier redact() { |
| return null; |
| } |
| } |
| |
| // A network specifier that matches either another LocalNetworkSpecifier with the same |
| // string or a ConfidentialMatchAllNetworkSpecifier, and can be passed to apps as is. |
| class LocalStringNetworkSpecifier extends NetworkSpecifier implements Parcelable { |
| private String mString; |
| |
| LocalStringNetworkSpecifier(String string) { |
| mString = string; |
| } |
| |
| @Override |
| public boolean canBeSatisfiedBy(NetworkSpecifier other) { |
| if (other instanceof LocalStringNetworkSpecifier) { |
| return TextUtils.equals(mString, |
| ((LocalStringNetworkSpecifier) other).mString); |
| } |
| if (other instanceof ConfidentialMatchAllNetworkSpecifier) return true; |
| return false; |
| } |
| |
| @Override |
| public int describeContents() { |
| return 0; |
| } |
| @Override |
| public void writeToParcel(Parcel dest, int flags) {} |
| } |
| |
| |
| NetworkRequest rEmpty1 = newWifiRequestBuilder().build(); |
| NetworkRequest rEmpty2 = newWifiRequestBuilder().setNetworkSpecifier((String) null).build(); |
| NetworkRequest rEmpty3 = newWifiRequestBuilder().setNetworkSpecifier("").build(); |
| NetworkRequest rEmpty4 = newWifiRequestBuilder().setNetworkSpecifier( |
| (NetworkSpecifier) null).build(); |
| NetworkRequest rFoo = newWifiRequestBuilder().setNetworkSpecifier( |
| new LocalStringNetworkSpecifier("foo")).build(); |
| NetworkRequest rBar = newWifiRequestBuilder().setNetworkSpecifier( |
| new LocalStringNetworkSpecifier("bar")).build(); |
| |
| TestNetworkCallback cEmpty1 = new TestNetworkCallback(); |
| TestNetworkCallback cEmpty2 = new TestNetworkCallback(); |
| TestNetworkCallback cEmpty3 = new TestNetworkCallback(); |
| TestNetworkCallback cEmpty4 = new TestNetworkCallback(); |
| TestNetworkCallback cFoo = new TestNetworkCallback(); |
| TestNetworkCallback cBar = new TestNetworkCallback(); |
| TestNetworkCallback[] emptyCallbacks = new TestNetworkCallback[] { |
| cEmpty1, cEmpty2, cEmpty3, cEmpty4 }; |
| |
| mCm.registerNetworkCallback(rEmpty1, cEmpty1); |
| mCm.registerNetworkCallback(rEmpty2, cEmpty2); |
| mCm.registerNetworkCallback(rEmpty3, cEmpty3); |
| mCm.registerNetworkCallback(rEmpty4, cEmpty4); |
| mCm.registerNetworkCallback(rFoo, cFoo); |
| mCm.registerNetworkCallback(rBar, cBar); |
| |
| LocalStringNetworkSpecifier nsFoo = new LocalStringNetworkSpecifier("foo"); |
| LocalStringNetworkSpecifier nsBar = new LocalStringNetworkSpecifier("bar"); |
| |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(false); |
| expectAvailableCallbacksUnvalidatedWithSpecifier(mWiFiNetworkAgent, null /* specifier */, |
| cEmpty1, cEmpty2, cEmpty3, cEmpty4); |
| assertNoCallbacks(cFoo, cBar); |
| |
| mWiFiNetworkAgent.disconnect(); |
| expectOnLost(mWiFiNetworkAgent, cEmpty1, cEmpty2, cEmpty3, cEmpty4); |
| |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.setNetworkSpecifier(nsFoo); |
| mWiFiNetworkAgent.connect(false); |
| expectAvailableCallbacksUnvalidatedWithSpecifier(mWiFiNetworkAgent, nsFoo, |
| cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo); |
| cBar.assertNoCallback(); |
| assertEquals(nsFoo, |
| mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).getNetworkSpecifier()); |
| assertNoCallbacks(cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo); |
| |
| mWiFiNetworkAgent.disconnect(); |
| expectOnLost(mWiFiNetworkAgent, cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo); |
| |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.setNetworkSpecifier(nsBar); |
| mWiFiNetworkAgent.connect(false); |
| expectAvailableCallbacksUnvalidatedWithSpecifier(mWiFiNetworkAgent, nsBar, |
| cEmpty1, cEmpty2, cEmpty3, cEmpty4, cBar); |
| cFoo.assertNoCallback(); |
| assertEquals(nsBar, |
| mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).getNetworkSpecifier()); |
| |
| mWiFiNetworkAgent.disconnect(); |
| expectOnLost(mWiFiNetworkAgent, cEmpty1, cEmpty2, cEmpty3, cEmpty4, cBar); |
| cFoo.assertNoCallback(); |
| |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.setNetworkSpecifier(new ConfidentialMatchAllNetworkSpecifier()); |
| mWiFiNetworkAgent.connect(false); |
| expectAvailableCallbacksUnvalidatedWithSpecifier(mWiFiNetworkAgent, null /* specifier */, |
| cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo, cBar); |
| assertNull( |
| mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).getNetworkSpecifier()); |
| |
| mWiFiNetworkAgent.disconnect(); |
| expectOnLost(mWiFiNetworkAgent, cEmpty1, cEmpty2, cEmpty3, cEmpty4, cFoo, cBar); |
| } |
| |
| /** |
| * @return the context's attribution tag |
| */ |
| private String getAttributionTag() { |
| return mContext.getAttributionTag(); |
| } |
| |
| @Test |
| public void testInvalidNetworkSpecifier() { |
| assertThrows(IllegalArgumentException.class, () -> { |
| NetworkRequest.Builder builder = new NetworkRequest.Builder(); |
| builder.setNetworkSpecifier(new MatchAllNetworkSpecifier()); |
| }); |
| |
| assertThrows(IllegalArgumentException.class, () -> { |
| NetworkCapabilities networkCapabilities = new NetworkCapabilities(); |
| networkCapabilities.addTransportType(TRANSPORT_WIFI) |
| .setNetworkSpecifier(new MatchAllNetworkSpecifier()); |
| mService.requestNetwork(Process.INVALID_UID, networkCapabilities, |
| NetworkRequest.Type.REQUEST.ordinal(), null, 0, null, |
| ConnectivityManager.TYPE_WIFI, NetworkCallback.FLAG_NONE, |
| mContext.getPackageName(), getAttributionTag()); |
| }); |
| |
| class NonParcelableSpecifier extends NetworkSpecifier { |
| @Override |
| public boolean canBeSatisfiedBy(NetworkSpecifier other) { |
| return false; |
| } |
| }; |
| class ParcelableSpecifier extends NonParcelableSpecifier implements Parcelable { |
| @Override public int describeContents() { return 0; } |
| @Override public void writeToParcel(Parcel p, int flags) {} |
| } |
| |
| final NetworkRequest.Builder builder = |
| new NetworkRequest.Builder().addTransportType(TRANSPORT_ETHERNET); |
| assertThrows(ClassCastException.class, () -> { |
| builder.setNetworkSpecifier(new NonParcelableSpecifier()); |
| Parcel parcelW = Parcel.obtain(); |
| builder.build().writeToParcel(parcelW, 0); |
| }); |
| |
| final NetworkRequest nr = |
| new NetworkRequest.Builder().addTransportType(TRANSPORT_ETHERNET) |
| .setNetworkSpecifier(new ParcelableSpecifier()) |
| .build(); |
| assertNotNull(nr); |
| |
| assertThrows(BadParcelableException.class, () -> { |
| Parcel parcelW = Parcel.obtain(); |
| nr.writeToParcel(parcelW, 0); |
| byte[] bytes = parcelW.marshall(); |
| parcelW.recycle(); |
| |
| Parcel parcelR = Parcel.obtain(); |
| parcelR.unmarshall(bytes, 0, bytes.length); |
| parcelR.setDataPosition(0); |
| NetworkRequest rereadNr = NetworkRequest.CREATOR.createFromParcel(parcelR); |
| }); |
| } |
| |
| @Test |
| public void testNetworkRequestUidSpoofSecurityException() throws Exception { |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(false); |
| NetworkRequest networkRequest = newWifiRequestBuilder().build(); |
| TestNetworkCallback networkCallback = new TestNetworkCallback(); |
| doThrow(new SecurityException()).when(mAppOpsManager).checkPackage(anyInt(), anyString()); |
| assertThrows(SecurityException.class, () -> { |
| mCm.requestNetwork(networkRequest, networkCallback); |
| }); |
| } |
| |
| @Test |
| public void testInvalidSignalStrength() { |
| NetworkRequest r = new NetworkRequest.Builder() |
| .addCapability(NET_CAPABILITY_INTERNET) |
| .addTransportType(TRANSPORT_WIFI) |
| .setSignalStrength(-75) |
| .build(); |
| // Registering a NetworkCallback with signal strength but w/o NETWORK_SIGNAL_STRENGTH_WAKEUP |
| // permission should get SecurityException. |
| assertThrows(SecurityException.class, () -> |
| mCm.registerNetworkCallback(r, new NetworkCallback())); |
| |
| assertThrows(SecurityException.class, () -> |
| mCm.registerNetworkCallback(r, PendingIntent.getService( |
| mServiceContext, 0 /* requestCode */, new Intent(), FLAG_IMMUTABLE))); |
| |
| // Requesting a Network with signal strength should get IllegalArgumentException. |
| assertThrows(IllegalArgumentException.class, () -> |
| mCm.requestNetwork(r, new NetworkCallback())); |
| |
| assertThrows(IllegalArgumentException.class, () -> |
| mCm.requestNetwork(r, PendingIntent.getService( |
| mServiceContext, 0 /* requestCode */, new Intent(), FLAG_IMMUTABLE))); |
| } |
| |
| @Test |
| public void testRegisterDefaultNetworkCallback() throws Exception { |
| // NETWORK_SETTINGS is necessary to call registerSystemDefaultNetworkCallback. |
| mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED); |
| |
| final TestNetworkCallback defaultNetworkCallback = new TestNetworkCallback(); |
| mCm.registerDefaultNetworkCallback(defaultNetworkCallback); |
| defaultNetworkCallback.assertNoCallback(); |
| |
| final Handler handler = new Handler(ConnectivityThread.getInstanceLooper()); |
| final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback(); |
| mCm.registerSystemDefaultNetworkCallback(systemDefaultCallback, handler); |
| systemDefaultCallback.assertNoCallback(); |
| |
| // Create a TRANSPORT_CELLULAR request to keep the mobile interface up |
| // whenever Wi-Fi is up. Without this, the mobile network agent is |
| // reaped before any other activity can take place. |
| final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); |
| final NetworkRequest cellRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_CELLULAR).build(); |
| mCm.requestNetwork(cellRequest, cellNetworkCallback); |
| cellNetworkCallback.assertNoCallback(); |
| |
| // Bring up cell and expect CALLBACK_AVAILABLE. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| systemDefaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| |
| // Bring up wifi and expect CALLBACK_AVAILABLE. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(true); |
| cellNetworkCallback.assertNoCallback(); |
| defaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); |
| systemDefaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); |
| assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| |
| // Bring down cell. Expect no default network callback, since it wasn't the default. |
| mCellNetworkAgent.disconnect(); |
| cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| defaultNetworkCallback.assertNoCallback(); |
| systemDefaultCallback.assertNoCallback(); |
| assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| |
| // Bring up cell. Expect no default network callback, since it won't be the default. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| defaultNetworkCallback.assertNoCallback(); |
| systemDefaultCallback.assertNoCallback(); |
| assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| assertEquals(systemDefaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| |
| // Bring down wifi. Expect the default network callback to notified of LOST wifi |
| // followed by AVAILABLE cell. |
| mWiFiNetworkAgent.disconnect(); |
| cellNetworkCallback.assertNoCallback(); |
| defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| defaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); |
| systemDefaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| systemDefaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); |
| mCellNetworkAgent.disconnect(); |
| cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| systemDefaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| waitForIdle(); |
| assertEquals(null, mCm.getActiveNetwork()); |
| |
| mMockVpn.establishForMyUid(); |
| assertUidRangesUpdatedForMyUid(true); |
| defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn); |
| systemDefaultCallback.assertNoCallback(); |
| assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| assertEquals(null, systemDefaultCallback.getLastAvailableNetwork()); |
| |
| mMockVpn.disconnect(); |
| defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn); |
| systemDefaultCallback.assertNoCallback(); |
| waitForIdle(); |
| assertEquals(null, mCm.getActiveNetwork()); |
| } |
| |
| @Test |
| public void testAdditionalStateCallbacks() throws Exception { |
| // File a network request for mobile. |
| final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); |
| final NetworkRequest cellRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_CELLULAR).build(); |
| mCm.requestNetwork(cellRequest, cellNetworkCallback); |
| |
| // Bring up the mobile network. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| |
| // We should get onAvailable(), onCapabilitiesChanged(), and |
| // onLinkPropertiesChanged() in rapid succession. Additionally, we |
| // should get onCapabilitiesChanged() when the mobile network validates. |
| cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| cellNetworkCallback.assertNoCallback(); |
| |
| // Update LinkProperties. |
| final LinkProperties lp = new LinkProperties(); |
| lp.setInterfaceName("foonet_data0"); |
| mCellNetworkAgent.sendLinkProperties(lp); |
| // We should get onLinkPropertiesChanged(). |
| cellNetworkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, |
| mCellNetworkAgent); |
| cellNetworkCallback.assertNoCallback(); |
| |
| // Suspend the network. |
| mCellNetworkAgent.suspend(); |
| cellNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_SUSPENDED, |
| mCellNetworkAgent); |
| cellNetworkCallback.expectCallback(CallbackEntry.SUSPENDED, mCellNetworkAgent); |
| cellNetworkCallback.assertNoCallback(); |
| assertEquals(NetworkInfo.State.SUSPENDED, mCm.getActiveNetworkInfo().getState()); |
| |
| // Register a garden variety default network request. |
| TestNetworkCallback dfltNetworkCallback = new TestNetworkCallback(); |
| mCm.registerDefaultNetworkCallback(dfltNetworkCallback); |
| // We should get onAvailable(), onCapabilitiesChanged(), onLinkPropertiesChanged(), |
| // as well as onNetworkSuspended() in rapid succession. |
| dfltNetworkCallback.expectAvailableAndSuspendedCallbacks(mCellNetworkAgent, true); |
| dfltNetworkCallback.assertNoCallback(); |
| mCm.unregisterNetworkCallback(dfltNetworkCallback); |
| |
| mCellNetworkAgent.resume(); |
| cellNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_SUSPENDED, |
| mCellNetworkAgent); |
| cellNetworkCallback.expectCallback(CallbackEntry.RESUMED, mCellNetworkAgent); |
| cellNetworkCallback.assertNoCallback(); |
| assertEquals(NetworkInfo.State.CONNECTED, mCm.getActiveNetworkInfo().getState()); |
| |
| dfltNetworkCallback = new TestNetworkCallback(); |
| mCm.registerDefaultNetworkCallback(dfltNetworkCallback); |
| // This time onNetworkSuspended should not be called. |
| dfltNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); |
| dfltNetworkCallback.assertNoCallback(); |
| |
| mCm.unregisterNetworkCallback(dfltNetworkCallback); |
| mCm.unregisterNetworkCallback(cellNetworkCallback); |
| } |
| |
| @Test |
| public void testRegisterPrivilegedDefaultCallbacksRequireNetworkSettings() throws Exception { |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(false /* validated */); |
| |
| final Handler handler = new Handler(ConnectivityThread.getInstanceLooper()); |
| final TestNetworkCallback callback = new TestNetworkCallback(); |
| assertThrows(SecurityException.class, |
| () -> mCm.registerSystemDefaultNetworkCallback(callback, handler)); |
| callback.assertNoCallback(); |
| assertThrows(SecurityException.class, |
| () -> mCm.registerDefaultNetworkCallbackForUid(APP1_UID, callback, handler)); |
| callback.assertNoCallback(); |
| |
| mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED); |
| mCm.registerSystemDefaultNetworkCallback(callback, handler); |
| callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); |
| mCm.unregisterNetworkCallback(callback); |
| |
| mCm.registerDefaultNetworkCallbackForUid(APP1_UID, callback, handler); |
| callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); |
| mCm.unregisterNetworkCallback(callback); |
| } |
| |
| @Test |
| public void testNetworkCallbackWithNullUids() throws Exception { |
| final NetworkRequest request = new NetworkRequest.Builder() |
| .removeCapability(NET_CAPABILITY_NOT_VPN) |
| .build(); |
| final TestNetworkCallback callback = new TestNetworkCallback(); |
| mCm.registerNetworkCallback(request, callback); |
| |
| // Attempt to file a callback for networks applying to another UID. This does not actually |
| // work, because this code does not currently have permission to do so. The callback behaves |
| // exactly the same as the one registered just above. |
| final int otherUid = UserHandle.getUid(RESTRICTED_USER, VPN_UID); |
| final NetworkRequest otherUidRequest = new NetworkRequest.Builder() |
| .removeCapability(NET_CAPABILITY_NOT_VPN) |
| .setUids(UidRange.toIntRanges(uidRangesForUids(otherUid))) |
| .build(); |
| final TestNetworkCallback otherUidCallback = new TestNetworkCallback(); |
| mCm.registerNetworkCallback(otherUidRequest, otherUidCallback); |
| |
| final NetworkRequest includeOtherUidsRequest = new NetworkRequest.Builder() |
| .removeCapability(NET_CAPABILITY_NOT_VPN) |
| .setIncludeOtherUidNetworks(true) |
| .build(); |
| final TestNetworkCallback includeOtherUidsCallback = new TestNetworkCallback(); |
| mCm.registerNetworkCallback(includeOtherUidsRequest, includeOtherUidsCallback); |
| |
| // Both callbacks see a network with no specifier that applies to their UID. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(false /* validated */); |
| callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| otherUidCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| includeOtherUidsCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| mWiFiNetworkAgent.disconnect(); |
| callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| otherUidCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| includeOtherUidsCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| |
| // Only the includeOtherUidsCallback sees a VPN that does not apply to its UID. |
| final UidRange range = UidRange.createForUser(UserHandle.of(RESTRICTED_USER)); |
| final Set<UidRange> vpnRanges = Collections.singleton(range); |
| mMockVpn.establish(new LinkProperties(), VPN_UID, vpnRanges); |
| includeOtherUidsCallback.expectAvailableThenValidatedCallbacks(mMockVpn); |
| callback.assertNoCallback(); |
| otherUidCallback.assertNoCallback(); |
| |
| mMockVpn.disconnect(); |
| includeOtherUidsCallback.expectCallback(CallbackEntry.LOST, mMockVpn); |
| callback.assertNoCallback(); |
| otherUidCallback.assertNoCallback(); |
| } |
| |
| private static class RedactableNetworkSpecifier extends NetworkSpecifier { |
| public static final int ID_INVALID = -1; |
| |
| public final int networkId; |
| |
| RedactableNetworkSpecifier(int networkId) { |
| this.networkId = networkId; |
| } |
| |
| @Override |
| public boolean canBeSatisfiedBy(NetworkSpecifier other) { |
| return other instanceof RedactableNetworkSpecifier |
| && this.networkId == ((RedactableNetworkSpecifier) other).networkId; |
| } |
| |
| @Override |
| public NetworkSpecifier redact() { |
| return new RedactableNetworkSpecifier(ID_INVALID); |
| } |
| } |
| |
| @Test |
| public void testNetworkCallbackWithNullUidsRedactsSpecifier() throws Exception { |
| final RedactableNetworkSpecifier specifier = new RedactableNetworkSpecifier(42); |
| final NetworkRequest request = new NetworkRequest.Builder() |
| .addCapability(NET_CAPABILITY_INTERNET) |
| .addTransportType(TRANSPORT_WIFI) |
| .setNetworkSpecifier(specifier) |
| .build(); |
| final TestNetworkCallback callback = new TestNetworkCallback(); |
| mCm.registerNetworkCallback(request, callback); |
| |
| // Attempt to file a callback for networks applying to another UID. This does not actually |
| // work, because this code does not currently have permission to do so. The callback behaves |
| // exactly the same as the one registered just above. |
| final int otherUid = UserHandle.getUid(RESTRICTED_USER, VPN_UID); |
| final NetworkRequest otherUidRequest = new NetworkRequest.Builder() |
| .addCapability(NET_CAPABILITY_INTERNET) |
| .addTransportType(TRANSPORT_WIFI) |
| .setNetworkSpecifier(specifier) |
| .setUids(UidRange.toIntRanges(uidRangesForUids(otherUid))) |
| .build(); |
| final TestNetworkCallback otherUidCallback = new TestNetworkCallback(); |
| mCm.registerNetworkCallback(otherUidRequest, otherUidCallback); |
| |
| final NetworkRequest includeOtherUidsRequest = new NetworkRequest.Builder() |
| .addCapability(NET_CAPABILITY_INTERNET) |
| .addTransportType(TRANSPORT_WIFI) |
| .setNetworkSpecifier(specifier) |
| .setIncludeOtherUidNetworks(true) |
| .build(); |
| final TestNetworkCallback includeOtherUidsCallback = new TestNetworkCallback(); |
| mCm.registerNetworkCallback(includeOtherUidsRequest, callback); |
| |
| // Only the regular callback sees the network, because callbacks filed with no UID have |
| // their specifiers redacted. |
| final LinkProperties emptyLp = new LinkProperties(); |
| final NetworkCapabilities ncTemplate = new NetworkCapabilities() |
| .addTransportType(TRANSPORT_WIFI) |
| .setNetworkSpecifier(specifier); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, emptyLp, ncTemplate); |
| mWiFiNetworkAgent.connect(false /* validated */); |
| callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| otherUidCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| includeOtherUidsCallback.assertNoCallback(); |
| } |
| |
| private void setCaptivePortalMode(int mode) { |
| ContentResolver cr = mServiceContext.getContentResolver(); |
| Settings.Global.putInt(cr, ConnectivitySettingsManager.CAPTIVE_PORTAL_MODE, mode); |
| } |
| |
| private void setAlwaysOnNetworks(boolean enable) { |
| ContentResolver cr = mServiceContext.getContentResolver(); |
| Settings.Global.putInt(cr, ConnectivitySettingsManager.MOBILE_DATA_ALWAYS_ON, |
| enable ? 1 : 0); |
| mService.updateAlwaysOnNetworks(); |
| waitForIdle(); |
| } |
| |
| private void setPrivateDnsSettings(int mode, String specifier) { |
| ConnectivitySettingsManager.setPrivateDnsMode(mServiceContext, mode); |
| ConnectivitySettingsManager.setPrivateDnsHostname(mServiceContext, specifier); |
| mService.updatePrivateDnsSettings(); |
| waitForIdle(); |
| } |
| |
| private boolean isForegroundNetwork(TestNetworkAgentWrapper network) { |
| NetworkCapabilities nc = mCm.getNetworkCapabilities(network.getNetwork()); |
| assertNotNull(nc); |
| return nc.hasCapability(NET_CAPABILITY_FOREGROUND); |
| } |
| |
| @Test |
| public void testBackgroundNetworks() throws Exception { |
| // Create a cellular background request. |
| grantUsingBackgroundNetworksPermissionForUid(Binder.getCallingUid()); |
| final TestNetworkCallback cellBgCallback = new TestNetworkCallback(); |
| mCm.requestBackgroundNetwork(new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_CELLULAR).build(), |
| cellBgCallback, mCsHandlerThread.getThreadHandler()); |
| |
| // Make callbacks for monitoring. |
| final NetworkRequest request = new NetworkRequest.Builder().build(); |
| final NetworkRequest fgRequest = new NetworkRequest.Builder() |
| .addCapability(NET_CAPABILITY_FOREGROUND).build(); |
| final TestNetworkCallback callback = new TestNetworkCallback(); |
| final TestNetworkCallback fgCallback = new TestNetworkCallback(); |
| mCm.registerNetworkCallback(request, callback); |
| mCm.registerNetworkCallback(fgRequest, fgCallback); |
| |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| fgCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| assertTrue(isForegroundNetwork(mCellNetworkAgent)); |
| |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(true); |
| |
| // When wifi connects, cell lingers. |
| callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| callback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); |
| callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); |
| fgCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| fgCallback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); |
| fgCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); |
| assertTrue(isForegroundNetwork(mCellNetworkAgent)); |
| assertTrue(isForegroundNetwork(mWiFiNetworkAgent)); |
| |
| // When lingering is complete, cell is still there but is now in the background. |
| waitForIdle(); |
| int timeoutMs = TEST_LINGER_DELAY_MS + TEST_LINGER_DELAY_MS / 4; |
| fgCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent, timeoutMs); |
| // Expect a network capabilities update sans FOREGROUND. |
| callback.expectCapabilitiesWithout(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent); |
| assertFalse(isForegroundNetwork(mCellNetworkAgent)); |
| assertTrue(isForegroundNetwork(mWiFiNetworkAgent)); |
| |
| // File a cell request and check that cell comes into the foreground. |
| final NetworkRequest cellRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_CELLULAR).build(); |
| final TestNetworkCallback cellCallback = new TestNetworkCallback(); |
| mCm.requestNetwork(cellRequest, cellCallback); |
| cellCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); |
| fgCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); |
| // Expect a network capabilities update with FOREGROUND, because the most recent |
| // request causes its state to change. |
| cellCallback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent); |
| callback.expectCapabilitiesWith(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent); |
| assertTrue(isForegroundNetwork(mCellNetworkAgent)); |
| assertTrue(isForegroundNetwork(mWiFiNetworkAgent)); |
| |
| // Release the request. The network immediately goes into the background, since it was not |
| // lingering. |
| mCm.unregisterNetworkCallback(cellCallback); |
| fgCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| // Expect a network capabilities update sans FOREGROUND. |
| callback.expectCapabilitiesWithout(NET_CAPABILITY_FOREGROUND, mCellNetworkAgent); |
| assertFalse(isForegroundNetwork(mCellNetworkAgent)); |
| assertTrue(isForegroundNetwork(mWiFiNetworkAgent)); |
| |
| // Disconnect wifi and check that cell is foreground again. |
| mWiFiNetworkAgent.disconnect(); |
| callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| fgCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| fgCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); |
| assertTrue(isForegroundNetwork(mCellNetworkAgent)); |
| |
| mCm.unregisterNetworkCallback(callback); |
| mCm.unregisterNetworkCallback(fgCallback); |
| mCm.unregisterNetworkCallback(cellBgCallback); |
| } |
| |
| @Ignore // This test has instrinsic chances of spurious failures: ignore for continuous testing. |
| public void benchmarkRequestRegistrationAndCallbackDispatch() throws Exception { |
| // TODO: turn this unit test into a real benchmarking test. |
| // Benchmarks connecting and switching performance in the presence of a large number of |
| // NetworkRequests. |
| // 1. File NUM_REQUESTS requests. |
| // 2. Have a network connect. Wait for NUM_REQUESTS onAvailable callbacks to fire. |
| // 3. Have a new network connect and outscore the previous. Wait for NUM_REQUESTS onLosing |
| // and NUM_REQUESTS onAvailable callbacks to fire. |
| // See how long it took. |
| final int NUM_REQUESTS = 90; |
| final int REGISTER_TIME_LIMIT_MS = 200; |
| final int CONNECT_TIME_LIMIT_MS = 60; |
| final int SWITCH_TIME_LIMIT_MS = 60; |
| final int UNREGISTER_TIME_LIMIT_MS = 20; |
| |
| final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build(); |
| final NetworkCallback[] callbacks = new NetworkCallback[NUM_REQUESTS]; |
| final CountDownLatch availableLatch = new CountDownLatch(NUM_REQUESTS); |
| final CountDownLatch losingLatch = new CountDownLatch(NUM_REQUESTS); |
| |
| for (int i = 0; i < NUM_REQUESTS; i++) { |
| callbacks[i] = new NetworkCallback() { |
| @Override public void onAvailable(Network n) { availableLatch.countDown(); } |
| @Override public void onLosing(Network n, int t) { losingLatch.countDown(); } |
| }; |
| } |
| |
| assertRunsInAtMost("Registering callbacks", REGISTER_TIME_LIMIT_MS, () -> { |
| for (NetworkCallback cb : callbacks) { |
| mCm.registerNetworkCallback(request, cb); |
| } |
| }); |
| |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| // Don't request that the network validate, because otherwise connect() will block until |
| // the network gets NET_CAPABILITY_VALIDATED, after all the callbacks below have fired, |
| // and we won't actually measure anything. |
| mCellNetworkAgent.connect(false); |
| |
| long onAvailableDispatchingDuration = durationOf(() -> { |
| await(availableLatch, 10 * CONNECT_TIME_LIMIT_MS); |
| }); |
| Log.d(TAG, String.format("Dispatched %d of %d onAvailable callbacks in %dms", |
| NUM_REQUESTS - availableLatch.getCount(), NUM_REQUESTS, |
| onAvailableDispatchingDuration)); |
| assertTrue(String.format("Dispatching %d onAvailable callbacks in %dms, expected %dms", |
| NUM_REQUESTS, onAvailableDispatchingDuration, CONNECT_TIME_LIMIT_MS), |
| onAvailableDispatchingDuration <= CONNECT_TIME_LIMIT_MS); |
| |
| // Give wifi a high enough score that we'll linger cell when wifi comes up. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.adjustScore(40); |
| mWiFiNetworkAgent.connect(false); |
| |
| long onLostDispatchingDuration = durationOf(() -> { |
| await(losingLatch, 10 * SWITCH_TIME_LIMIT_MS); |
| }); |
| Log.d(TAG, String.format("Dispatched %d of %d onLosing callbacks in %dms", |
| NUM_REQUESTS - losingLatch.getCount(), NUM_REQUESTS, onLostDispatchingDuration)); |
| assertTrue(String.format("Dispatching %d onLosing callbacks in %dms, expected %dms", |
| NUM_REQUESTS, onLostDispatchingDuration, SWITCH_TIME_LIMIT_MS), |
| onLostDispatchingDuration <= SWITCH_TIME_LIMIT_MS); |
| |
| assertRunsInAtMost("Unregistering callbacks", UNREGISTER_TIME_LIMIT_MS, () -> { |
| for (NetworkCallback cb : callbacks) { |
| mCm.unregisterNetworkCallback(cb); |
| } |
| }); |
| } |
| |
| @Test |
| public void testMobileDataAlwaysOn() throws Exception { |
| grantUsingBackgroundNetworksPermissionForUid(Binder.getCallingUid()); |
| final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); |
| final NetworkRequest cellRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_CELLULAR).build(); |
| mCm.registerNetworkCallback(cellRequest, cellNetworkCallback); |
| |
| final HandlerThread handlerThread = new HandlerThread("MobileDataAlwaysOnFactory"); |
| handlerThread.start(); |
| NetworkCapabilities filter = new NetworkCapabilities() |
| .addTransportType(TRANSPORT_CELLULAR) |
| .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) |
| .addCapability(NET_CAPABILITY_INTERNET); |
| final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), |
| mServiceContext, "testFactory", filter, mCsHandlerThread); |
| testFactory.setScoreFilter(40); |
| |
| // Register the factory and expect it to start looking for a network. |
| testFactory.register(); |
| |
| try { |
| // Expect the factory to receive the default network request. |
| testFactory.expectRequestAdd(); |
| testFactory.assertRequestCountEquals(1); |
| assertTrue(testFactory.getMyStartRequested()); |
| |
| // Bring up wifi. The factory stops looking for a network. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| // Score 60 - 40 penalty for not validated yet, then 60 when it validates |
| mWiFiNetworkAgent.connect(true); |
| // The network connects with a low score, so the offer can still beat it and |
| // nothing happens. Then the network validates, and the offer with its filter score |
| // of 40 can no longer beat it and the request is removed. |
| testFactory.expectRequestRemove(); |
| testFactory.assertRequestCountEquals(0); |
| |
| assertFalse(testFactory.getMyStartRequested()); |
| |
| // Turn on mobile data always on. This request will not match the wifi request, so |
| // it will be sent to the test factory whose filters allow to see it. |
| setAlwaysOnNetworks(true); |
| testFactory.expectRequestAdd(); |
| testFactory.assertRequestCountEquals(1); |
| |
| assertTrue(testFactory.getMyStartRequested()); |
| |
| // Bring up cell data and check that the factory stops looking. |
| assertLength(1, mCm.getAllNetworks()); |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(false); |
| cellNetworkCallback.expectAvailableCallbacks(mCellNetworkAgent, false, false, false, |
| TEST_CALLBACK_TIMEOUT_MS); |
| // When cell connects, it will satisfy the "mobile always on request" right away |
| // by virtue of being the only network that can satisfy the request. However, its |
| // score is low (50 - 40 = 10) so the test factory can still hope to beat it. |
| expectNoRequestChanged(testFactory); |
| |
| // Next, cell validates. This gives it a score of 50 and the test factory can't |
| // hope to beat that according to its filters. It will see the message that its |
| // offer is now unnecessary. |
| mCellNetworkAgent.setNetworkValid(true); |
| // Need a trigger point to let NetworkMonitor tell ConnectivityService that network is |
| // validated – see testPartialConnectivity. |
| mCm.reportNetworkConnectivity(mCellNetworkAgent.getNetwork(), true); |
| cellNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mCellNetworkAgent); |
| testFactory.expectRequestRemove(); |
| testFactory.assertRequestCountEquals(0); |
| // Accordingly, the factory shouldn't be started. |
| assertFalse(testFactory.getMyStartRequested()); |
| |
| // Check that cell data stays up. |
| waitForIdle(); |
| verifyActiveNetwork(TRANSPORT_WIFI); |
| assertLength(2, mCm.getAllNetworks()); |
| |
| // Cell disconnects. There is still the "mobile data always on" request outstanding, |
| // and the test factory should see it now that it isn't hopelessly outscored. |
| mCellNetworkAgent.disconnect(); |
| cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| assertLength(1, mCm.getAllNetworks()); |
| testFactory.expectRequestAdd(); |
| testFactory.assertRequestCountEquals(1); |
| |
| // Reconnect cell validated, see the request disappear again. Then withdraw the |
| // mobile always on request. This will tear down cell, and there shouldn't be a |
| // blip where the test factory briefly sees the request or anything. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| assertLength(2, mCm.getAllNetworks()); |
| testFactory.expectRequestRemove(); |
| testFactory.assertRequestCountEquals(0); |
| setAlwaysOnNetworks(false); |
| expectNoRequestChanged(testFactory); |
| testFactory.assertRequestCountEquals(0); |
| assertFalse(testFactory.getMyStartRequested()); |
| // ... and cell data to be torn down immediately since it is no longer nascent. |
| cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| waitForIdle(); |
| assertLength(1, mCm.getAllNetworks()); |
| } finally { |
| testFactory.terminate(); |
| mCm.unregisterNetworkCallback(cellNetworkCallback); |
| handlerThread.quit(); |
| } |
| } |
| |
| @Test |
| public void testSetAllowBadWifiUntil() throws Exception { |
| runAsShell(NETWORK_SETTINGS, |
| () -> mService.setTestAllowBadWifiUntil(System.currentTimeMillis() + 5_000L)); |
| waitForIdle(); |
| testAvoidBadWifiConfig_controlledBySettings(); |
| |
| runAsShell(NETWORK_SETTINGS, |
| () -> mService.setTestAllowBadWifiUntil(System.currentTimeMillis() - 5_000L)); |
| waitForIdle(); |
| testAvoidBadWifiConfig_ignoreSettings(); |
| } |
| |
| private void testAvoidBadWifiConfig_controlledBySettings() { |
| final ContentResolver cr = mServiceContext.getContentResolver(); |
| final String settingName = ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI; |
| |
| Settings.Global.putString(cr, settingName, "0"); |
| mPolicyTracker.reevaluate(); |
| waitForIdle(); |
| assertFalse(mService.avoidBadWifi()); |
| assertFalse(mPolicyTracker.shouldNotifyWifiUnvalidated()); |
| |
| Settings.Global.putString(cr, settingName, "1"); |
| mPolicyTracker.reevaluate(); |
| waitForIdle(); |
| assertTrue(mService.avoidBadWifi()); |
| assertFalse(mPolicyTracker.shouldNotifyWifiUnvalidated()); |
| |
| Settings.Global.putString(cr, settingName, null); |
| mPolicyTracker.reevaluate(); |
| waitForIdle(); |
| assertFalse(mService.avoidBadWifi()); |
| assertTrue(mPolicyTracker.shouldNotifyWifiUnvalidated()); |
| } |
| |
| private void testAvoidBadWifiConfig_ignoreSettings() { |
| final ContentResolver cr = mServiceContext.getContentResolver(); |
| final String settingName = ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI; |
| |
| String[] values = new String[] {null, "0", "1"}; |
| for (int i = 0; i < values.length; i++) { |
| Settings.Global.putString(cr, settingName, values[i]); |
| mPolicyTracker.reevaluate(); |
| waitForIdle(); |
| String msg = String.format("config=false, setting=%s", values[i]); |
| assertTrue(mService.avoidBadWifi()); |
| assertFalse(msg, mPolicyTracker.shouldNotifyWifiUnvalidated()); |
| } |
| } |
| |
| @Test |
| public void testAvoidBadWifiSetting() throws Exception { |
| final ContentResolver cr = mServiceContext.getContentResolver(); |
| final String settingName = ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI; |
| |
| doReturn(1).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi); |
| testAvoidBadWifiConfig_ignoreSettings(); |
| |
| doReturn(0).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi); |
| testAvoidBadWifiConfig_controlledBySettings(); |
| } |
| |
| @Ignore("Refactoring in progress b/178071397") |
| @Test |
| public void testAvoidBadWifi() throws Exception { |
| final ContentResolver cr = mServiceContext.getContentResolver(); |
| |
| // Pretend we're on a carrier that restricts switching away from bad wifi. |
| doReturn(0).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi); |
| |
| // File a request for cell to ensure it doesn't go down. |
| final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); |
| final NetworkRequest cellRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_CELLULAR).build(); |
| mCm.requestNetwork(cellRequest, cellNetworkCallback); |
| |
| TestNetworkCallback defaultCallback = new TestNetworkCallback(); |
| mCm.registerDefaultNetworkCallback(defaultCallback); |
| |
| NetworkRequest validatedWifiRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_WIFI) |
| .addCapability(NET_CAPABILITY_VALIDATED) |
| .build(); |
| TestNetworkCallback validatedWifiCallback = new TestNetworkCallback(); |
| mCm.registerNetworkCallback(validatedWifiRequest, validatedWifiCallback); |
| |
| Settings.Global.putInt(cr, ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, 0); |
| mPolicyTracker.reevaluate(); |
| |
| // Bring up validated cell. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| Network cellNetwork = mCellNetworkAgent.getNetwork(); |
| |
| // Bring up validated wifi. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(true); |
| defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); |
| validatedWifiCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); |
| Network wifiNetwork = mWiFiNetworkAgent.getNetwork(); |
| |
| // Fail validation on wifi. |
| mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */); |
| mCm.reportNetworkConnectivity(wifiNetwork, false); |
| defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); |
| validatedWifiCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| |
| // Because avoid bad wifi is off, we don't switch to cellular. |
| defaultCallback.assertNoCallback(); |
| assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability( |
| NET_CAPABILITY_VALIDATED)); |
| assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability( |
| NET_CAPABILITY_VALIDATED)); |
| assertEquals(mCm.getActiveNetwork(), wifiNetwork); |
| |
| // Simulate switching to a carrier that does not restrict avoiding bad wifi, and expect |
| // that we switch back to cell. |
| doReturn(1).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi); |
| mPolicyTracker.reevaluate(); |
| defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); |
| assertEquals(mCm.getActiveNetwork(), cellNetwork); |
| |
| // Switch back to a restrictive carrier. |
| doReturn(0).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi); |
| mPolicyTracker.reevaluate(); |
| defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| assertEquals(mCm.getActiveNetwork(), wifiNetwork); |
| |
| // Simulate the user selecting "switch" on the dialog, and check that we switch to cell. |
| mCm.setAvoidUnvalidated(wifiNetwork); |
| defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); |
| assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability( |
| NET_CAPABILITY_VALIDATED)); |
| assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability( |
| NET_CAPABILITY_VALIDATED)); |
| assertEquals(mCm.getActiveNetwork(), cellNetwork); |
| |
| // Disconnect and reconnect wifi to clear the one-time switch above. |
| mWiFiNetworkAgent.disconnect(); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(true); |
| defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); |
| validatedWifiCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); |
| wifiNetwork = mWiFiNetworkAgent.getNetwork(); |
| |
| // Fail validation on wifi and expect the dialog to appear. |
| mWiFiNetworkAgent.setNetworkInvalid(false /* isStrictMode */); |
| mCm.reportNetworkConnectivity(wifiNetwork, false); |
| defaultCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); |
| validatedWifiCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| |
| // Simulate the user selecting "switch" and checking the don't ask again checkbox. |
| Settings.Global.putInt(cr, ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, 1); |
| mPolicyTracker.reevaluate(); |
| |
| // We now switch to cell. |
| defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); |
| assertFalse(mCm.getNetworkCapabilities(wifiNetwork).hasCapability( |
| NET_CAPABILITY_VALIDATED)); |
| assertTrue(mCm.getNetworkCapabilities(cellNetwork).hasCapability( |
| NET_CAPABILITY_VALIDATED)); |
| assertEquals(mCm.getActiveNetwork(), cellNetwork); |
| |
| // Simulate the user turning the cellular fallback setting off and then on. |
| // We switch to wifi and then to cell. |
| Settings.Global.putString(cr, ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, null); |
| mPolicyTracker.reevaluate(); |
| defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| assertEquals(mCm.getActiveNetwork(), wifiNetwork); |
| Settings.Global.putInt(cr, ConnectivitySettingsManager.NETWORK_AVOID_BAD_WIFI, 1); |
| mPolicyTracker.reevaluate(); |
| defaultCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); |
| assertEquals(mCm.getActiveNetwork(), cellNetwork); |
| |
| // If cell goes down, we switch to wifi. |
| mCellNetworkAgent.disconnect(); |
| defaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| validatedWifiCallback.assertNoCallback(); |
| |
| mCm.unregisterNetworkCallback(cellNetworkCallback); |
| mCm.unregisterNetworkCallback(validatedWifiCallback); |
| mCm.unregisterNetworkCallback(defaultCallback); |
| } |
| |
| @Test |
| public void testMeteredMultipathPreferenceSetting() throws Exception { |
| final ContentResolver cr = mServiceContext.getContentResolver(); |
| final String settingName = ConnectivitySettingsManager.NETWORK_METERED_MULTIPATH_PREFERENCE; |
| |
| for (int config : Arrays.asList(0, 3, 2)) { |
| for (String setting: Arrays.asList(null, "0", "2", "1")) { |
| mPolicyTracker.mConfigMeteredMultipathPreference = config; |
| Settings.Global.putString(cr, settingName, setting); |
| mPolicyTracker.reevaluate(); |
| waitForIdle(); |
| |
| final int expected = (setting != null) ? Integer.parseInt(setting) : config; |
| String msg = String.format("config=%d, setting=%s", config, setting); |
| assertEquals(msg, expected, mCm.getMultipathPreference(null)); |
| } |
| } |
| } |
| |
| /** |
| * Validate that a satisfied network request does not trigger onUnavailable() once the |
| * time-out period expires. |
| */ |
| @Test |
| public void testSatisfiedNetworkRequestDoesNotTriggerOnUnavailable() throws Exception { |
| NetworkRequest nr = new NetworkRequest.Builder().addTransportType( |
| NetworkCapabilities.TRANSPORT_WIFI).build(); |
| final TestNetworkCallback networkCallback = new TestNetworkCallback(); |
| mCm.requestNetwork(nr, networkCallback, TEST_REQUEST_TIMEOUT_MS); |
| |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(false); |
| networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, false, |
| TEST_CALLBACK_TIMEOUT_MS); |
| |
| // pass timeout and validate that UNAVAILABLE is not called |
| networkCallback.assertNoCallback(); |
| } |
| |
| /** |
| * Validate that a satisfied network request followed by a disconnected (lost) network does |
| * not trigger onUnavailable() once the time-out period expires. |
| */ |
| @Test |
| public void testSatisfiedThenLostNetworkRequestDoesNotTriggerOnUnavailable() throws Exception { |
| NetworkRequest nr = new NetworkRequest.Builder().addTransportType( |
| NetworkCapabilities.TRANSPORT_WIFI).build(); |
| final TestNetworkCallback networkCallback = new TestNetworkCallback(); |
| mCm.requestNetwork(nr, networkCallback, TEST_REQUEST_TIMEOUT_MS); |
| |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(false); |
| networkCallback.expectAvailableCallbacks(mWiFiNetworkAgent, false, false, false, |
| TEST_CALLBACK_TIMEOUT_MS); |
| mWiFiNetworkAgent.disconnect(); |
| networkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| |
| // Validate that UNAVAILABLE is not called |
| networkCallback.assertNoCallback(); |
| } |
| |
| /** |
| * Validate that when a time-out is specified for a network request the onUnavailable() |
| * callback is called when time-out expires. Then validate that if network request is |
| * (somehow) satisfied - the callback isn't called later. |
| */ |
| @Test |
| public void testTimedoutNetworkRequest() throws Exception { |
| NetworkRequest nr = new NetworkRequest.Builder().addTransportType( |
| NetworkCapabilities.TRANSPORT_WIFI).build(); |
| final TestNetworkCallback networkCallback = new TestNetworkCallback(); |
| final int timeoutMs = 10; |
| mCm.requestNetwork(nr, networkCallback, timeoutMs); |
| |
| // pass timeout and validate that UNAVAILABLE is called |
| networkCallback.expectCallback(CallbackEntry.UNAVAILABLE, (Network) null); |
| |
| // create a network satisfying request - validate that request not triggered |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(false); |
| networkCallback.assertNoCallback(); |
| } |
| |
| /** |
| * Validate that when a network request is unregistered (cancelled), no posterior event can |
| * trigger the callback. |
| */ |
| @Test |
| public void testNoCallbackAfterUnregisteredNetworkRequest() throws Exception { |
| NetworkRequest nr = new NetworkRequest.Builder().addTransportType( |
| NetworkCapabilities.TRANSPORT_WIFI).build(); |
| final TestNetworkCallback networkCallback = new TestNetworkCallback(); |
| final int timeoutMs = 10; |
| |
| mCm.requestNetwork(nr, networkCallback, timeoutMs); |
| mCm.unregisterNetworkCallback(networkCallback); |
| // Regardless of the timeout, unregistering the callback in ConnectivityManager ensures |
| // that this callback will not be called. |
| networkCallback.assertNoCallback(); |
| |
| // create a network satisfying request - validate that request not triggered |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(false); |
| networkCallback.assertNoCallback(); |
| } |
| |
| @Test |
| public void testUnfulfillableNetworkRequest() throws Exception { |
| runUnfulfillableNetworkRequest(false); |
| } |
| |
| @Test |
| public void testUnfulfillableNetworkRequestAfterUnregister() throws Exception { |
| runUnfulfillableNetworkRequest(true); |
| } |
| |
| /** |
| * Validate the callback flow for a factory releasing a request as unfulfillable. |
| */ |
| private void runUnfulfillableNetworkRequest(boolean preUnregister) throws Exception { |
| NetworkRequest nr = new NetworkRequest.Builder().addTransportType( |
| NetworkCapabilities.TRANSPORT_WIFI).build(); |
| final TestNetworkCallback networkCallback = new TestNetworkCallback(); |
| |
| final HandlerThread handlerThread = new HandlerThread("testUnfulfillableNetworkRequest"); |
| handlerThread.start(); |
| NetworkCapabilities filter = new NetworkCapabilities() |
| .addTransportType(TRANSPORT_WIFI) |
| .addCapability(NET_CAPABILITY_INTERNET) |
| .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); |
| final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), |
| mServiceContext, "testFactory", filter, mCsHandlerThread); |
| testFactory.setScoreFilter(40); |
| |
| // Register the factory and expect it to receive the default request. |
| testFactory.register(); |
| testFactory.expectRequestAdd(); |
| |
| try { |
| // Now file the test request and expect it. |
| mCm.requestNetwork(nr, networkCallback); |
| final NetworkRequest newRequest = testFactory.expectRequestAdd().request; |
| |
| if (preUnregister) { |
| mCm.unregisterNetworkCallback(networkCallback); |
| |
| // The request has been released : the factory should see it removed |
| // immediately. |
| testFactory.expectRequestRemove(); |
| |
| // Simulate the factory releasing the request as unfulfillable: no-op since |
| // the callback has already been unregistered (but a test that no exceptions are |
| // thrown). |
| testFactory.triggerUnfulfillable(newRequest); |
| } else { |
| // Simulate the factory releasing the request as unfulfillable and expect |
| // onUnavailable! |
| testFactory.triggerUnfulfillable(newRequest); |
| |
| networkCallback.expectCallback(CallbackEntry.UNAVAILABLE, (Network) null); |
| |
| // Declaring a request unfulfillable releases it automatically. |
| testFactory.expectRequestRemove(); |
| |
| // unregister network callback - a no-op (since already freed by the |
| // on-unavailable), but should not fail or throw exceptions. |
| mCm.unregisterNetworkCallback(networkCallback); |
| |
| // The factory should not see any further removal, as this request has |
| // already been removed. |
| } |
| } finally { |
| testFactory.terminate(); |
| handlerThread.quit(); |
| } |
| } |
| |
| private static class TestKeepaliveCallback extends PacketKeepaliveCallback { |
| |
| public enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR } |
| |
| private class CallbackValue { |
| public CallbackType callbackType; |
| public int error; |
| |
| public CallbackValue(CallbackType type) { |
| this.callbackType = type; |
| this.error = PacketKeepalive.SUCCESS; |
| assertTrue("onError callback must have error", type != CallbackType.ON_ERROR); |
| } |
| |
| public CallbackValue(CallbackType type, int error) { |
| this.callbackType = type; |
| this.error = error; |
| assertEquals("error can only be set for onError", type, CallbackType.ON_ERROR); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| return o instanceof CallbackValue && |
| this.callbackType == ((CallbackValue) o).callbackType && |
| this.error == ((CallbackValue) o).error; |
| } |
| |
| @Override |
| public String toString() { |
| return String.format("%s(%s, %d)", getClass().getSimpleName(), callbackType, error); |
| } |
| } |
| |
| private final LinkedBlockingQueue<CallbackValue> mCallbacks = new LinkedBlockingQueue<>(); |
| |
| @Override |
| public void onStarted() { |
| mCallbacks.add(new CallbackValue(CallbackType.ON_STARTED)); |
| } |
| |
| @Override |
| public void onStopped() { |
| mCallbacks.add(new CallbackValue(CallbackType.ON_STOPPED)); |
| } |
| |
| @Override |
| public void onError(int error) { |
| mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, error)); |
| } |
| |
| private void expectCallback(CallbackValue callbackValue) throws InterruptedException { |
| assertEquals(callbackValue, mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
| } |
| |
| public void expectStarted() throws Exception { |
| expectCallback(new CallbackValue(CallbackType.ON_STARTED)); |
| } |
| |
| public void expectStopped() throws Exception { |
| expectCallback(new CallbackValue(CallbackType.ON_STOPPED)); |
| } |
| |
| public void expectError(int error) throws Exception { |
| expectCallback(new CallbackValue(CallbackType.ON_ERROR, error)); |
| } |
| } |
| |
| private static class TestSocketKeepaliveCallback extends SocketKeepalive.Callback { |
| |
| public enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR }; |
| |
| private class CallbackValue { |
| public CallbackType callbackType; |
| public int error; |
| |
| CallbackValue(CallbackType type) { |
| this.callbackType = type; |
| this.error = SocketKeepalive.SUCCESS; |
| assertTrue("onError callback must have error", type != CallbackType.ON_ERROR); |
| } |
| |
| CallbackValue(CallbackType type, int error) { |
| this.callbackType = type; |
| this.error = error; |
| assertEquals("error can only be set for onError", type, CallbackType.ON_ERROR); |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| return o instanceof CallbackValue |
| && this.callbackType == ((CallbackValue) o).callbackType |
| && this.error == ((CallbackValue) o).error; |
| } |
| |
| @Override |
| public String toString() { |
| return String.format("%s(%s, %d)", getClass().getSimpleName(), callbackType, |
| error); |
| } |
| } |
| |
| private LinkedBlockingQueue<CallbackValue> mCallbacks = new LinkedBlockingQueue<>(); |
| private final Executor mExecutor; |
| |
| TestSocketKeepaliveCallback(@NonNull Executor executor) { |
| mExecutor = executor; |
| } |
| |
| @Override |
| public void onStarted() { |
| mCallbacks.add(new CallbackValue(CallbackType.ON_STARTED)); |
| } |
| |
| @Override |
| public void onStopped() { |
| mCallbacks.add(new CallbackValue(CallbackType.ON_STOPPED)); |
| } |
| |
| @Override |
| public void onError(int error) { |
| mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, error)); |
| } |
| |
| private void expectCallback(CallbackValue callbackValue) throws InterruptedException { |
| assertEquals(callbackValue, mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS)); |
| |
| } |
| |
| public void expectStarted() throws InterruptedException { |
| expectCallback(new CallbackValue(CallbackType.ON_STARTED)); |
| } |
| |
| public void expectStopped() throws InterruptedException { |
| expectCallback(new CallbackValue(CallbackType.ON_STOPPED)); |
| } |
| |
| public void expectError(int error) throws InterruptedException { |
| expectCallback(new CallbackValue(CallbackType.ON_ERROR, error)); |
| } |
| |
| public void assertNoCallback() { |
| waitForIdleSerialExecutor(mExecutor, TIMEOUT_MS); |
| CallbackValue cv = mCallbacks.peek(); |
| assertNull("Unexpected callback: " + cv, cv); |
| } |
| } |
| |
| private Network connectKeepaliveNetwork(LinkProperties lp) throws Exception { |
| // Ensure the network is disconnected before anything else occurs |
| if (mWiFiNetworkAgent != null) { |
| assertNull(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork())); |
| } |
| |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); |
| mWiFiNetworkAgent.connect(true); |
| b.expectBroadcast(); |
| verifyActiveNetwork(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.sendLinkProperties(lp); |
| waitForIdle(); |
| return mWiFiNetworkAgent.getNetwork(); |
| } |
| |
| @Test |
| public void testPacketKeepalives() throws Exception { |
| InetAddress myIPv4 = InetAddress.getByName("192.0.2.129"); |
| InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35"); |
| InetAddress myIPv6 = InetAddress.getByName("2001:db8::1"); |
| InetAddress dstIPv4 = InetAddress.getByName("8.8.8.8"); |
| InetAddress dstIPv6 = InetAddress.getByName("2001:4860:4860::8888"); |
| |
| final int validKaInterval = 15; |
| final int invalidKaInterval = 9; |
| |
| LinkProperties lp = new LinkProperties(); |
| lp.setInterfaceName("wlan12"); |
| lp.addLinkAddress(new LinkAddress(myIPv6, 64)); |
| lp.addLinkAddress(new LinkAddress(myIPv4, 25)); |
| lp.addRoute(new RouteInfo(InetAddress.getByName("fe80::1234"))); |
| lp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254"))); |
| |
| Network notMyNet = new Network(61234); |
| Network myNet = connectKeepaliveNetwork(lp); |
| |
| TestKeepaliveCallback callback = new TestKeepaliveCallback(); |
| PacketKeepalive ka; |
| |
| // Attempt to start keepalives with invalid parameters and check for errors. |
| ka = mCm.startNattKeepalive(notMyNet, validKaInterval, callback, myIPv4, 1234, dstIPv4); |
| callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK); |
| |
| ka = mCm.startNattKeepalive(myNet, invalidKaInterval, callback, myIPv4, 1234, dstIPv4); |
| callback.expectError(PacketKeepalive.ERROR_INVALID_INTERVAL); |
| |
| ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 1234, dstIPv6); |
| callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS); |
| |
| ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv6, 1234, dstIPv4); |
| callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS); |
| |
| // NAT-T is only supported for IPv4. |
| ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv6, 1234, dstIPv6); |
| callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS); |
| |
| ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 123456, dstIPv4); |
| callback.expectError(PacketKeepalive.ERROR_INVALID_PORT); |
| |
| ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 123456, dstIPv4); |
| callback.expectError(PacketKeepalive.ERROR_INVALID_PORT); |
| |
| ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 12345, dstIPv4); |
| callback.expectError(PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED); |
| |
| ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 12345, dstIPv4); |
| callback.expectError(PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED); |
| |
| // Check that a started keepalive can be stopped. |
| mWiFiNetworkAgent.setStartKeepaliveEvent(PacketKeepalive.SUCCESS); |
| ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 12345, dstIPv4); |
| callback.expectStarted(); |
| mWiFiNetworkAgent.setStopKeepaliveEvent(PacketKeepalive.SUCCESS); |
| ka.stop(); |
| callback.expectStopped(); |
| |
| // Check that deleting the IP address stops the keepalive. |
| LinkProperties bogusLp = new LinkProperties(lp); |
| ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 12345, dstIPv4); |
| callback.expectStarted(); |
| bogusLp.removeLinkAddress(new LinkAddress(myIPv4, 25)); |
| bogusLp.addLinkAddress(new LinkAddress(notMyIPv4, 25)); |
| mWiFiNetworkAgent.sendLinkProperties(bogusLp); |
| callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS); |
| mWiFiNetworkAgent.sendLinkProperties(lp); |
| |
| // Check that a started keepalive is stopped correctly when the network disconnects. |
| ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 12345, dstIPv4); |
| callback.expectStarted(); |
| mWiFiNetworkAgent.disconnect(); |
| mWiFiNetworkAgent.expectDisconnected(); |
| callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK); |
| |
| // ... and that stopping it after that has no adverse effects. |
| waitForIdle(); |
| final Network myNetAlias = myNet; |
| assertNull(mCm.getNetworkCapabilities(myNetAlias)); |
| ka.stop(); |
| |
| // Reconnect. |
| myNet = connectKeepaliveNetwork(lp); |
| mWiFiNetworkAgent.setStartKeepaliveEvent(PacketKeepalive.SUCCESS); |
| |
| // Check that keepalive slots start from 1 and increment. The first one gets slot 1. |
| mWiFiNetworkAgent.setExpectedKeepaliveSlot(1); |
| ka = mCm.startNattKeepalive(myNet, validKaInterval, callback, myIPv4, 12345, dstIPv4); |
| callback.expectStarted(); |
| |
| // The second one gets slot 2. |
| mWiFiNetworkAgent.setExpectedKeepaliveSlot(2); |
| TestKeepaliveCallback callback2 = new TestKeepaliveCallback(); |
| PacketKeepalive ka2 = mCm.startNattKeepalive( |
| myNet, validKaInterval, callback2, myIPv4, 6789, dstIPv4); |
| callback2.expectStarted(); |
| |
| // Now stop the first one and create a third. This also gets slot 1. |
| ka.stop(); |
| callback.expectStopped(); |
| |
| mWiFiNetworkAgent.setExpectedKeepaliveSlot(1); |
| TestKeepaliveCallback callback3 = new TestKeepaliveCallback(); |
| PacketKeepalive ka3 = mCm.startNattKeepalive( |
| myNet, validKaInterval, callback3, myIPv4, 9876, dstIPv4); |
| callback3.expectStarted(); |
| |
| ka2.stop(); |
| callback2.expectStopped(); |
| |
| ka3.stop(); |
| callback3.expectStopped(); |
| } |
| |
| // Helper method to prepare the executor and run test |
| private void runTestWithSerialExecutors(ExceptionUtils.ThrowingConsumer<Executor> functor) |
| throws Exception { |
| final ExecutorService executorSingleThread = Executors.newSingleThreadExecutor(); |
| final Executor executorInline = (Runnable r) -> r.run(); |
| functor.accept(executorSingleThread); |
| executorSingleThread.shutdown(); |
| functor.accept(executorInline); |
| } |
| |
| @Test |
| public void testNattSocketKeepalives() throws Exception { |
| runTestWithSerialExecutors(executor -> doTestNattSocketKeepalivesWithExecutor(executor)); |
| runTestWithSerialExecutors(executor -> doTestNattSocketKeepalivesFdWithExecutor(executor)); |
| } |
| |
| private void doTestNattSocketKeepalivesWithExecutor(Executor executor) throws Exception { |
| // TODO: 1. Move this outside of ConnectivityServiceTest. |
| // 2. Make test to verify that Nat-T keepalive socket is created by IpSecService. |
| // 3. Mock ipsec service. |
| final InetAddress myIPv4 = InetAddress.getByName("192.0.2.129"); |
| final InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35"); |
| final InetAddress myIPv6 = InetAddress.getByName("2001:db8::1"); |
| final InetAddress dstIPv4 = InetAddress.getByName("8.8.8.8"); |
| final InetAddress dstIPv6 = InetAddress.getByName("2001:4860:4860::8888"); |
| |
| final int validKaInterval = 15; |
| final int invalidKaInterval = 9; |
| |
| final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE); |
| final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(); |
| final int srcPort = testSocket.getPort(); |
| |
| LinkProperties lp = new LinkProperties(); |
| lp.setInterfaceName("wlan12"); |
| lp.addLinkAddress(new LinkAddress(myIPv6, 64)); |
| lp.addLinkAddress(new LinkAddress(myIPv4, 25)); |
| lp.addRoute(new RouteInfo(InetAddress.getByName("fe80::1234"))); |
| lp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254"))); |
| |
| Network notMyNet = new Network(61234); |
| Network myNet = connectKeepaliveNetwork(lp); |
| |
| TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback(executor); |
| |
| // Attempt to start keepalives with invalid parameters and check for errors. |
| // Invalid network. |
| try (SocketKeepalive ka = mCm.createSocketKeepalive( |
| notMyNet, testSocket, myIPv4, dstIPv4, executor, callback)) { |
| ka.start(validKaInterval); |
| callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK); |
| } |
| |
| // Invalid interval. |
| try (SocketKeepalive ka = mCm.createSocketKeepalive( |
| myNet, testSocket, myIPv4, dstIPv4, executor, callback)) { |
| ka.start(invalidKaInterval); |
| callback.expectError(SocketKeepalive.ERROR_INVALID_INTERVAL); |
| } |
| |
| // Invalid destination. |
| try (SocketKeepalive ka = mCm.createSocketKeepalive( |
| myNet, testSocket, myIPv4, dstIPv6, executor, callback)) { |
| ka.start(validKaInterval); |
| callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS); |
| } |
| |
| // Invalid source; |
| try (SocketKeepalive ka = mCm.createSocketKeepalive( |
| myNet, testSocket, myIPv6, dstIPv4, executor, callback)) { |
| ka.start(validKaInterval); |
| callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS); |
| } |
| |
| // NAT-T is only supported for IPv4. |
| try (SocketKeepalive ka = mCm.createSocketKeepalive( |
| myNet, testSocket, myIPv6, dstIPv6, executor, callback)) { |
| ka.start(validKaInterval); |
| callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS); |
| } |
| |
| // Basic check before testing started keepalive. |
| try (SocketKeepalive ka = mCm.createSocketKeepalive( |
| myNet, testSocket, myIPv4, dstIPv4, executor, callback)) { |
| ka.start(validKaInterval); |
| callback.expectError(SocketKeepalive.ERROR_UNSUPPORTED); |
| } |
| |
| // Check that a started keepalive can be stopped. |
| mWiFiNetworkAgent.setStartKeepaliveEvent(SocketKeepalive.SUCCESS); |
| try (SocketKeepalive ka = mCm.createSocketKeepalive( |
| myNet, testSocket, myIPv4, dstIPv4, executor, callback)) { |
| ka.start(validKaInterval); |
| callback.expectStarted(); |
| mWiFiNetworkAgent.setStopKeepaliveEvent(SocketKeepalive.SUCCESS); |
| ka.stop(); |
| callback.expectStopped(); |
| |
| // Check that keepalive could be restarted. |
| ka.start(validKaInterval); |
| callback.expectStarted(); |
| ka.stop(); |
| callback.expectStopped(); |
| |
| // Check that keepalive can be restarted without waiting for callback. |
| ka.start(validKaInterval); |
| callback.expectStarted(); |
| ka.stop(); |
| ka.start(validKaInterval); |
| callback.expectStopped(); |
| callback.expectStarted(); |
| ka.stop(); |
| callback.expectStopped(); |
| } |
| |
| // Check that deleting the IP address stops the keepalive. |
| LinkProperties bogusLp = new LinkProperties(lp); |
| try (SocketKeepalive ka = mCm.createSocketKeepalive( |
| myNet, testSocket, myIPv4, dstIPv4, executor, callback)) { |
| ka.start(validKaInterval); |
| callback.expectStarted(); |
| bogusLp.removeLinkAddress(new LinkAddress(myIPv4, 25)); |
| bogusLp.addLinkAddress(new LinkAddress(notMyIPv4, 25)); |
| mWiFiNetworkAgent.sendLinkProperties(bogusLp); |
| callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS); |
| mWiFiNetworkAgent.sendLinkProperties(lp); |
| } |
| |
| // Check that a started keepalive is stopped correctly when the network disconnects. |
| try (SocketKeepalive ka = mCm.createSocketKeepalive( |
| myNet, testSocket, myIPv4, dstIPv4, executor, callback)) { |
| ka.start(validKaInterval); |
| callback.expectStarted(); |
| mWiFiNetworkAgent.disconnect(); |
| mWiFiNetworkAgent.expectDisconnected(); |
| callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK); |
| |
| // ... and that stopping it after that has no adverse effects. |
| waitForIdle(); |
| final Network myNetAlias = myNet; |
| assertNull(mCm.getNetworkCapabilities(myNetAlias)); |
| ka.stop(); |
| callback.assertNoCallback(); |
| } |
| |
| // Reconnect. |
| myNet = connectKeepaliveNetwork(lp); |
| mWiFiNetworkAgent.setStartKeepaliveEvent(SocketKeepalive.SUCCESS); |
| |
| // Check that a stop followed by network disconnects does not result in crash. |
| try (SocketKeepalive ka = mCm.createSocketKeepalive( |
| myNet, testSocket, myIPv4, dstIPv4, executor, callback)) { |
| ka.start(validKaInterval); |
| callback.expectStarted(); |
| // Delay the response of keepalive events in networkAgent long enough to make sure |
| // the follow-up network disconnection will be processed first. |
| mWiFiNetworkAgent.setKeepaliveResponseDelay(3 * TIMEOUT_MS); |
| ka.stop(); |
| // Call stop() twice shouldn't result in crash, b/182586681. |
| ka.stop(); |
| |
| // Make sure the stop has been processed. Wait for executor idle is needed to prevent |
| // flaky since the actual stop call to the service is delegated to executor thread. |
| waitForIdleSerialExecutor(executor, TIMEOUT_MS); |
| waitForIdle(); |
| |
| mWiFiNetworkAgent.disconnect(); |
| mWiFiNetworkAgent.expectDisconnected(); |
| callback.expectStopped(); |
| callback.assertNoCallback(); |
| } |
| |
| // Reconnect. |
| waitForIdle(); |
| myNet = connectKeepaliveNetwork(lp); |
| mWiFiNetworkAgent.setStartKeepaliveEvent(SocketKeepalive.SUCCESS); |
| |
| // Check that keepalive slots start from 1 and increment. The first one gets slot 1. |
| mWiFiNetworkAgent.setExpectedKeepaliveSlot(1); |
| int srcPort2 = 0; |
| try (SocketKeepalive ka = mCm.createSocketKeepalive( |
| myNet, testSocket, myIPv4, dstIPv4, executor, callback)) { |
| ka.start(validKaInterval); |
| callback.expectStarted(); |
| |
| // The second one gets slot 2. |
| mWiFiNetworkAgent.setExpectedKeepaliveSlot(2); |
| final UdpEncapsulationSocket testSocket2 = mIpSec.openUdpEncapsulationSocket(); |
| srcPort2 = testSocket2.getPort(); |
| TestSocketKeepaliveCallback callback2 = new TestSocketKeepaliveCallback(executor); |
| try (SocketKeepalive ka2 = mCm.createSocketKeepalive( |
| myNet, testSocket2, myIPv4, dstIPv4, executor, callback2)) { |
| ka2.start(validKaInterval); |
| callback2.expectStarted(); |
| |
| ka.stop(); |
| callback.expectStopped(); |
| |
| ka2.stop(); |
| callback2.expectStopped(); |
| |
| testSocket.close(); |
| testSocket2.close(); |
| } |
| } |
| |
| // Check that there is no port leaked after all keepalives and sockets are closed. |
| // TODO: enable this check after ensuring a valid free port. See b/129512753#comment7. |
| // assertFalse(isUdpPortInUse(srcPort)); |
| // assertFalse(isUdpPortInUse(srcPort2)); |
| |
| mWiFiNetworkAgent.disconnect(); |
| mWiFiNetworkAgent.expectDisconnected(); |
| mWiFiNetworkAgent = null; |
| } |
| |
| @Test |
| public void testTcpSocketKeepalives() throws Exception { |
| runTestWithSerialExecutors(executor -> doTestTcpSocketKeepalivesWithExecutor(executor)); |
| } |
| |
| private void doTestTcpSocketKeepalivesWithExecutor(Executor executor) throws Exception { |
| final int srcPortV4 = 12345; |
| final int srcPortV6 = 23456; |
| final InetAddress myIPv4 = InetAddress.getByName("127.0.0.1"); |
| final InetAddress myIPv6 = InetAddress.getByName("::1"); |
| |
| final int validKaInterval = 15; |
| |
| final LinkProperties lp = new LinkProperties(); |
| lp.setInterfaceName("wlan12"); |
| lp.addLinkAddress(new LinkAddress(myIPv6, 64)); |
| lp.addLinkAddress(new LinkAddress(myIPv4, 25)); |
| lp.addRoute(new RouteInfo(InetAddress.getByName("fe80::1234"))); |
| lp.addRoute(new RouteInfo(InetAddress.getByName("127.0.0.254"))); |
| |
| final Network notMyNet = new Network(61234); |
| final Network myNet = connectKeepaliveNetwork(lp); |
| |
| final Socket testSocketV4 = new Socket(); |
| final Socket testSocketV6 = new Socket(); |
| |
| TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback(executor); |
| |
| // Attempt to start Tcp keepalives with invalid parameters and check for errors. |
| // Invalid network. |
| try (SocketKeepalive ka = mCm.createSocketKeepalive( |
| notMyNet, testSocketV4, executor, callback)) { |
| ka.start(validKaInterval); |
| callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK); |
| } |
| |
| // Invalid Socket (socket is not bound with IPv4 address). |
| try (SocketKeepalive ka = mCm.createSocketKeepalive( |
| myNet, testSocketV4, executor, callback)) { |
| ka.start(validKaInterval); |
| callback.expectError(SocketKeepalive.ERROR_INVALID_SOCKET); |
| } |
| |
| // Invalid Socket (socket is not bound with IPv6 address). |
| try (SocketKeepalive ka = mCm.createSocketKeepalive( |
| myNet, testSocketV6, executor, callback)) { |
| ka.start(validKaInterval); |
| callback.expectError(SocketKeepalive.ERROR_INVALID_SOCKET); |
| } |
| |
| // Bind the socket address |
| testSocketV4.bind(new InetSocketAddress(myIPv4, srcPortV4)); |
| testSocketV6.bind(new InetSocketAddress(myIPv6, srcPortV6)); |
| |
| // Invalid Socket (socket is bound with IPv4 address). |
| try (SocketKeepalive ka = mCm.createSocketKeepalive( |
| myNet, testSocketV4, executor, callback)) { |
| ka.start(validKaInterval); |
| callback.expectError(SocketKeepalive.ERROR_INVALID_SOCKET); |
| } |
| |
| // Invalid Socket (socket is bound with IPv6 address). |
| try (SocketKeepalive ka = mCm.createSocketKeepalive( |
| myNet, testSocketV6, executor, callback)) { |
| ka.start(validKaInterval); |
| callback.expectError(SocketKeepalive.ERROR_INVALID_SOCKET); |
| } |
| |
| testSocketV4.close(); |
| testSocketV6.close(); |
| |
| mWiFiNetworkAgent.disconnect(); |
| mWiFiNetworkAgent.expectDisconnected(); |
| mWiFiNetworkAgent = null; |
| } |
| |
| private void doTestNattSocketKeepalivesFdWithExecutor(Executor executor) throws Exception { |
| final InetAddress myIPv4 = InetAddress.getByName("192.0.2.129"); |
| final InetAddress anyIPv4 = InetAddress.getByName("0.0.0.0"); |
| final InetAddress dstIPv4 = InetAddress.getByName("8.8.8.8"); |
| final int validKaInterval = 15; |
| |
| // Prepare the target network. |
| LinkProperties lp = new LinkProperties(); |
| lp.setInterfaceName("wlan12"); |
| lp.addLinkAddress(new LinkAddress(myIPv4, 25)); |
| lp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254"))); |
| Network myNet = connectKeepaliveNetwork(lp); |
| mWiFiNetworkAgent.setStartKeepaliveEvent(SocketKeepalive.SUCCESS); |
| mWiFiNetworkAgent.setStopKeepaliveEvent(SocketKeepalive.SUCCESS); |
| |
| TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback(executor); |
| |
| // Prepare the target file descriptor, keep only one instance. |
| final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE); |
| final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(); |
| final int srcPort = testSocket.getPort(); |
| final ParcelFileDescriptor testPfd = |
| ParcelFileDescriptor.dup(testSocket.getFileDescriptor()); |
| testSocket.close(); |
| assertTrue(isUdpPortInUse(srcPort)); |
| |
| // Start keepalive and explicit make the variable goes out of scope with try-with-resources |
| // block. |
| try (SocketKeepalive ka = mCm.createNattKeepalive( |
| myNet, testPfd, myIPv4, dstIPv4, executor, callback)) { |
| ka.start(validKaInterval); |
| callback.expectStarted(); |
| ka.stop(); |
| callback.expectStopped(); |
| } |
| |
| // Check that the ParcelFileDescriptor is still valid after keepalive stopped, |
| // ErrnoException with EBADF will be thrown if the socket is closed when checking local |
| // address. |
| assertTrue(isUdpPortInUse(srcPort)); |
| final InetSocketAddress sa = |
| (InetSocketAddress) Os.getsockname(testPfd.getFileDescriptor()); |
| assertEquals(anyIPv4, sa.getAddress()); |
| |
| testPfd.close(); |
| // TODO: enable this check after ensuring a valid free port. See b/129512753#comment7. |
| // assertFalse(isUdpPortInUse(srcPort)); |
| |
| mWiFiNetworkAgent.disconnect(); |
| mWiFiNetworkAgent.expectDisconnected(); |
| mWiFiNetworkAgent = null; |
| } |
| |
| private static boolean isUdpPortInUse(int port) { |
| try (DatagramSocket ignored = new DatagramSocket(port)) { |
| return false; |
| } catch (IOException alreadyInUse) { |
| return true; |
| } |
| } |
| |
| @Test |
| public void testGetCaptivePortalServerUrl() throws Exception { |
| String url = mCm.getCaptivePortalServerUrl(); |
| assertEquals("http://connectivitycheck.gstatic.com/generate_204", url); |
| } |
| |
| private static class TestNetworkPinner extends NetworkPinner { |
| public static boolean awaitPin(int timeoutMs) throws InterruptedException { |
| synchronized(sLock) { |
| if (sNetwork == null) { |
| sLock.wait(timeoutMs); |
| } |
| return sNetwork != null; |
| } |
| } |
| |
| public static boolean awaitUnpin(int timeoutMs) throws InterruptedException { |
| synchronized(sLock) { |
| if (sNetwork != null) { |
| sLock.wait(timeoutMs); |
| } |
| return sNetwork == null; |
| } |
| } |
| } |
| |
| private void assertPinnedToWifiWithCellDefault() { |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getBoundNetworkForProcess()); |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| } |
| |
| private void assertPinnedToWifiWithWifiDefault() { |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getBoundNetworkForProcess()); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| } |
| |
| private void assertNotPinnedToWifi() { |
| assertNull(mCm.getBoundNetworkForProcess()); |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| } |
| |
| @Test |
| public void testNetworkPinner() throws Exception { |
| NetworkRequest wifiRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_WIFI) |
| .build(); |
| assertNull(mCm.getBoundNetworkForProcess()); |
| |
| TestNetworkPinner.pin(mServiceContext, wifiRequest); |
| assertNull(mCm.getBoundNetworkForProcess()); |
| |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(false); |
| |
| // When wi-fi connects, expect to be pinned. |
| assertTrue(TestNetworkPinner.awaitPin(100)); |
| assertPinnedToWifiWithCellDefault(); |
| |
| // Disconnect and expect the pin to drop. |
| mWiFiNetworkAgent.disconnect(); |
| assertTrue(TestNetworkPinner.awaitUnpin(100)); |
| assertNotPinnedToWifi(); |
| |
| // Reconnecting does not cause the pin to come back. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(false); |
| assertFalse(TestNetworkPinner.awaitPin(100)); |
| assertNotPinnedToWifi(); |
| |
| // Pinning while connected causes the pin to take effect immediately. |
| TestNetworkPinner.pin(mServiceContext, wifiRequest); |
| assertTrue(TestNetworkPinner.awaitPin(100)); |
| assertPinnedToWifiWithCellDefault(); |
| |
| // Explicitly unpin and expect to use the default network again. |
| TestNetworkPinner.unpin(); |
| assertNotPinnedToWifi(); |
| |
| // Disconnect cell and wifi. |
| ExpectedBroadcast b = registerConnectivityBroadcast(3); // cell down, wifi up, wifi down. |
| mCellNetworkAgent.disconnect(); |
| mWiFiNetworkAgent.disconnect(); |
| b.expectBroadcast(); |
| |
| // Pinning takes effect even if the pinned network is the default when the pin is set... |
| TestNetworkPinner.pin(mServiceContext, wifiRequest); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(false); |
| assertTrue(TestNetworkPinner.awaitPin(100)); |
| assertPinnedToWifiWithWifiDefault(); |
| |
| // ... and is maintained even when that network is no longer the default. |
| b = registerConnectivityBroadcast(1); |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mCellNetworkAgent.connect(true); |
| b.expectBroadcast(); |
| assertPinnedToWifiWithCellDefault(); |
| } |
| |
| @Test |
| public void testNetworkCallbackMaximum() throws Exception { |
| final int MAX_REQUESTS = 100; |
| final int CALLBACKS = 87; |
| final int DIFF_INTENTS = 10; |
| final int SAME_INTENTS = 10; |
| final int SYSTEM_ONLY_MAX_REQUESTS = 250; |
| // Assert 1 (Default request filed before testing) + CALLBACKS + DIFF_INTENTS + |
| // 1 (same intent) = MAX_REQUESTS - 1, since the capacity is MAX_REQUEST - 1. |
| assertEquals(MAX_REQUESTS - 1, 1 + CALLBACKS + DIFF_INTENTS + 1); |
| |
| NetworkRequest networkRequest = new NetworkRequest.Builder().build(); |
| ArrayList<Object> registered = new ArrayList<>(); |
| |
| for (int j = 0; j < CALLBACKS; j++) { |
| final NetworkCallback cb = new NetworkCallback(); |
| if (j < CALLBACKS / 2) { |
| mCm.requestNetwork(networkRequest, cb); |
| } else { |
| mCm.registerNetworkCallback(networkRequest, cb); |
| } |
| registered.add(cb); |
| } |
| |
| // Since ConnectivityService will de-duplicate the request with the same intent, |
| // register multiple times does not really increase multiple requests. |
| final PendingIntent same_pi = PendingIntent.getBroadcast(mContext, 0 /* requestCode */, |
| new Intent("same"), FLAG_IMMUTABLE); |
| for (int j = 0; j < SAME_INTENTS; j++) { |
| mCm.registerNetworkCallback(networkRequest, same_pi); |
| // Wait for the requests with the same intent to be de-duplicated. Because |
| // ConnectivityService side incrementCountOrThrow in binder, decrementCount in handler |
| // thread, waitForIdle is needed to ensure decrementCount being invoked for same intent |
| // requests before doing further tests. |
| waitForIdle(); |
| } |
| for (int j = 0; j < SAME_INTENTS; j++) { |
| mCm.requestNetwork(networkRequest, same_pi); |
| // Wait for the requests with the same intent to be de-duplicated. |
| // Refer to the reason above. |
| waitForIdle(); |
| } |
| registered.add(same_pi); |
| |
| for (int j = 0; j < DIFF_INTENTS; j++) { |
| if (j < DIFF_INTENTS / 2) { |
| final PendingIntent pi = PendingIntent.getBroadcast(mContext, 0 /* requestCode */, |
| new Intent("a" + j), FLAG_IMMUTABLE); |
| mCm.requestNetwork(networkRequest, pi); |
| registered.add(pi); |
| } else { |
| final PendingIntent pi = PendingIntent.getBroadcast(mContext, 0 /* requestCode */, |
| new Intent("b" + j), FLAG_IMMUTABLE); |
| mCm.registerNetworkCallback(networkRequest, pi); |
| registered.add(pi); |
| } |
| } |
| |
| // Test that the limit is enforced when MAX_REQUESTS simultaneous requests are added. |
| assertThrows(TooManyRequestsException.class, () -> |
| mCm.requestNetwork(networkRequest, new NetworkCallback()) |
| ); |
| assertThrows(TooManyRequestsException.class, () -> |
| mCm.registerNetworkCallback(networkRequest, new NetworkCallback()) |
| ); |
| assertThrows(TooManyRequestsException.class, () -> |
| mCm.requestNetwork(networkRequest, |
| PendingIntent.getBroadcast(mContext, 0 /* requestCode */, |
| new Intent("c"), FLAG_IMMUTABLE)) |
| ); |
| assertThrows(TooManyRequestsException.class, () -> |
| mCm.registerNetworkCallback(networkRequest, |
| PendingIntent.getBroadcast(mContext, 0 /* requestCode */, |
| new Intent("d"), FLAG_IMMUTABLE)) |
| ); |
| |
| // The system gets another SYSTEM_ONLY_MAX_REQUESTS slots. |
| final Handler handler = new Handler(ConnectivityThread.getInstanceLooper()); |
| withPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, () -> { |
| ArrayList<NetworkCallback> systemRegistered = new ArrayList<>(); |
| for (int i = 0; i < SYSTEM_ONLY_MAX_REQUESTS - 1; i++) { |
| NetworkCallback cb = new NetworkCallback(); |
| if (i % 2 == 0) { |
| mCm.registerDefaultNetworkCallbackForUid(1000000 + i, cb, handler); |
| } else { |
| mCm.registerNetworkCallback(networkRequest, cb); |
| } |
| systemRegistered.add(cb); |
| } |
| waitForIdle(); |
| |
| assertThrows(TooManyRequestsException.class, () -> |
| mCm.registerDefaultNetworkCallbackForUid(1001042, new NetworkCallback(), |
| handler)); |
| assertThrows(TooManyRequestsException.class, () -> |
| mCm.registerNetworkCallback(networkRequest, new NetworkCallback())); |
| |
| for (NetworkCallback callback : systemRegistered) { |
| mCm.unregisterNetworkCallback(callback); |
| } |
| waitForIdle(); // Wait for requests to be unregistered before giving up the permission. |
| }); |
| |
| for (Object o : registered) { |
| if (o instanceof NetworkCallback) { |
| mCm.unregisterNetworkCallback((NetworkCallback) o); |
| } |
| if (o instanceof PendingIntent) { |
| mCm.unregisterNetworkCallback((PendingIntent) o); |
| } |
| } |
| waitForIdle(); |
| |
| // Test that the limit is not hit when MAX_REQUESTS requests are added and removed. |
| for (int i = 0; i < MAX_REQUESTS; i++) { |
| NetworkCallback networkCallback = new NetworkCallback(); |
| mCm.requestNetwork(networkRequest, networkCallback); |
| mCm.unregisterNetworkCallback(networkCallback); |
| } |
| waitForIdle(); |
| |
| for (int i = 0; i < MAX_REQUESTS; i++) { |
| NetworkCallback networkCallback = new NetworkCallback(); |
| mCm.registerNetworkCallback(networkRequest, networkCallback); |
| mCm.unregisterNetworkCallback(networkCallback); |
| } |
| waitForIdle(); |
| |
| for (int i = 0; i < MAX_REQUESTS; i++) { |
| NetworkCallback networkCallback = new NetworkCallback(); |
| mCm.registerDefaultNetworkCallback(networkCallback); |
| mCm.unregisterNetworkCallback(networkCallback); |
| } |
| waitForIdle(); |
| |
| for (int i = 0; i < MAX_REQUESTS; i++) { |
| NetworkCallback networkCallback = new NetworkCallback(); |
| mCm.registerDefaultNetworkCallback(networkCallback); |
| mCm.unregisterNetworkCallback(networkCallback); |
| } |
| waitForIdle(); |
| |
| withPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, () -> { |
| for (int i = 0; i < MAX_REQUESTS; i++) { |
| NetworkCallback networkCallback = new NetworkCallback(); |
| mCm.registerDefaultNetworkCallbackForUid(1000000 + i, networkCallback, |
| new Handler(ConnectivityThread.getInstanceLooper())); |
| mCm.unregisterNetworkCallback(networkCallback); |
| } |
| }); |
| waitForIdle(); |
| |
| for (int i = 0; i < MAX_REQUESTS; i++) { |
| final PendingIntent pendingIntent = PendingIntent.getBroadcast( |
| mContext, 0 /* requestCode */, new Intent("e" + i), FLAG_IMMUTABLE); |
| mCm.requestNetwork(networkRequest, pendingIntent); |
| mCm.unregisterNetworkCallback(pendingIntent); |
| } |
| waitForIdle(); |
| |
| for (int i = 0; i < MAX_REQUESTS; i++) { |
| final PendingIntent pendingIntent = PendingIntent.getBroadcast( |
| mContext, 0 /* requestCode */, new Intent("f" + i), FLAG_IMMUTABLE); |
| mCm.registerNetworkCallback(networkRequest, pendingIntent); |
| mCm.unregisterNetworkCallback(pendingIntent); |
| } |
| } |
| |
| @Test |
| public void testNetworkInfoOfTypeNone() throws Exception { |
| ExpectedBroadcast b = registerConnectivityBroadcast(1); |
| |
| verifyNoNetwork(); |
| TestNetworkAgentWrapper wifiAware = new TestNetworkAgentWrapper(TRANSPORT_WIFI_AWARE); |
| assertNull(mCm.getActiveNetworkInfo()); |
| |
| Network[] allNetworks = mCm.getAllNetworks(); |
| assertLength(1, allNetworks); |
| Network network = allNetworks[0]; |
| NetworkCapabilities capabilities = mCm.getNetworkCapabilities(network); |
| assertTrue(capabilities.hasTransport(TRANSPORT_WIFI_AWARE)); |
| |
| final NetworkRequest request = |
| new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI_AWARE).build(); |
| final TestNetworkCallback callback = new TestNetworkCallback(); |
| mCm.registerNetworkCallback(request, callback); |
| |
| // Bring up wifi aware network. |
| wifiAware.connect(false, false, false /* isStrictMode */); |
| callback.expectAvailableCallbacksUnvalidated(wifiAware); |
| |
| assertNull(mCm.getActiveNetworkInfo()); |
| assertNull(mCm.getActiveNetwork()); |
| // TODO: getAllNetworkInfo is dirty and returns a non-empty array right from the start |
| // of this test. Fix it and uncomment the assert below. |
| //assertEmpty(mCm.getAllNetworkInfo()); |
| |
| // Disconnect wifi aware network. |
| wifiAware.disconnect(); |
| callback.expectCallbackThat(TIMEOUT_MS, (info) -> info instanceof CallbackEntry.Lost); |
| mCm.unregisterNetworkCallback(callback); |
| |
| verifyNoNetwork(); |
| b.expectNoBroadcast(10); |
| } |
| |
| @Test |
| public void testDeprecatedAndUnsupportedOperations() throws Exception { |
| final int TYPE_NONE = ConnectivityManager.TYPE_NONE; |
| assertNull(mCm.getNetworkInfo(TYPE_NONE)); |
| assertNull(mCm.getNetworkForType(TYPE_NONE)); |
| assertNull(mCm.getLinkProperties(TYPE_NONE)); |
| assertFalse(mCm.isNetworkSupported(TYPE_NONE)); |
| |
| assertThrows(IllegalArgumentException.class, |
| () -> mCm.networkCapabilitiesForType(TYPE_NONE)); |
| |
| Class<UnsupportedOperationException> unsupported = UnsupportedOperationException.class; |
| assertThrows(unsupported, () -> mCm.startUsingNetworkFeature(TYPE_WIFI, "")); |
| assertThrows(unsupported, () -> mCm.stopUsingNetworkFeature(TYPE_WIFI, "")); |
| // TODO: let test context have configuration application target sdk version |
| // and test that pre-M requesting for TYPE_NONE sends back APN_REQUEST_FAILED |
| assertThrows(unsupported, () -> mCm.startUsingNetworkFeature(TYPE_NONE, "")); |
| assertThrows(unsupported, () -> mCm.stopUsingNetworkFeature(TYPE_NONE, "")); |
| assertThrows(unsupported, () -> mCm.requestRouteToHostAddress(TYPE_NONE, null)); |
| } |
| |
| @Test |
| public void testLinkPropertiesEnsuresDirectlyConnectedRoutes() throws Exception { |
| final NetworkRequest networkRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_WIFI).build(); |
| final TestNetworkCallback networkCallback = new TestNetworkCallback(); |
| mCm.registerNetworkCallback(networkRequest, networkCallback); |
| |
| LinkProperties lp = new LinkProperties(); |
| lp.setInterfaceName(WIFI_IFNAME); |
| LinkAddress myIpv4Address = new LinkAddress("192.168.12.3/24"); |
| RouteInfo myIpv4DefaultRoute = new RouteInfo((IpPrefix) null, |
| InetAddresses.parseNumericAddress("192.168.12.1"), lp.getInterfaceName()); |
| lp.addLinkAddress(myIpv4Address); |
| lp.addRoute(myIpv4DefaultRoute); |
| |
| // Verify direct routes are added when network agent is first registered in |
| // ConnectivityService. |
| TestNetworkAgentWrapper networkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp); |
| networkAgent.connect(true); |
| networkCallback.expectCallback(CallbackEntry.AVAILABLE, networkAgent); |
| networkCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, networkAgent); |
| CallbackEntry.LinkPropertiesChanged cbi = |
| networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, |
| networkAgent); |
| networkCallback.expectCallback(CallbackEntry.BLOCKED_STATUS, networkAgent); |
| networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, networkAgent); |
| networkCallback.assertNoCallback(); |
| checkDirectlyConnectedRoutes(cbi.getLp(), Arrays.asList(myIpv4Address), |
| Arrays.asList(myIpv4DefaultRoute)); |
| checkDirectlyConnectedRoutes(mCm.getLinkProperties(networkAgent.getNetwork()), |
| Arrays.asList(myIpv4Address), Arrays.asList(myIpv4DefaultRoute)); |
| |
| // Verify direct routes are added during subsequent link properties updates. |
| LinkProperties newLp = new LinkProperties(lp); |
| LinkAddress myIpv6Address1 = new LinkAddress("fe80::cafe/64"); |
| LinkAddress myIpv6Address2 = new LinkAddress("2001:db8::2/64"); |
| newLp.addLinkAddress(myIpv6Address1); |
| newLp.addLinkAddress(myIpv6Address2); |
| networkAgent.sendLinkProperties(newLp); |
| cbi = networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, networkAgent); |
| networkCallback.assertNoCallback(); |
| checkDirectlyConnectedRoutes(cbi.getLp(), |
| Arrays.asList(myIpv4Address, myIpv6Address1, myIpv6Address2), |
| Arrays.asList(myIpv4DefaultRoute)); |
| mCm.unregisterNetworkCallback(networkCallback); |
| } |
| |
| private void expectNotifyNetworkStatus(List<Network> networks, String defaultIface, |
| Integer vpnUid, String vpnIfname, List<String> underlyingIfaces) throws Exception { |
| ArgumentCaptor<List<Network>> networksCaptor = ArgumentCaptor.forClass(List.class); |
| ArgumentCaptor<List<UnderlyingNetworkInfo>> vpnInfosCaptor = |
| ArgumentCaptor.forClass(List.class); |
| |
| verify(mStatsManager, atLeastOnce()).notifyNetworkStatus(networksCaptor.capture(), |
| any(List.class), eq(defaultIface), vpnInfosCaptor.capture()); |
| |
| assertSameElements(networks, networksCaptor.getValue()); |
| |
| List<UnderlyingNetworkInfo> infos = vpnInfosCaptor.getValue(); |
| if (vpnUid != null) { |
| assertEquals("Should have exactly one VPN:", 1, infos.size()); |
| UnderlyingNetworkInfo info = infos.get(0); |
| assertEquals("Unexpected VPN owner:", (int) vpnUid, info.getOwnerUid()); |
| assertEquals("Unexpected VPN interface:", vpnIfname, info.getInterface()); |
| assertSameElements(underlyingIfaces, info.getUnderlyingInterfaces()); |
| } else { |
| assertEquals(0, infos.size()); |
| return; |
| } |
| } |
| |
| private void expectNotifyNetworkStatus( |
| List<Network> networks, String defaultIface) throws Exception { |
| expectNotifyNetworkStatus(networks, defaultIface, null, null, List.of()); |
| } |
| |
| @Test |
| public void testStatsIfacesChanged() throws Exception { |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| |
| final List<Network> onlyCell = List.of(mCellNetworkAgent.getNetwork()); |
| final List<Network> onlyWifi = List.of(mWiFiNetworkAgent.getNetwork()); |
| |
| LinkProperties cellLp = new LinkProperties(); |
| cellLp.setInterfaceName(MOBILE_IFNAME); |
| LinkProperties wifiLp = new LinkProperties(); |
| wifiLp.setInterfaceName(WIFI_IFNAME); |
| |
| // Simple connection should have updated ifaces |
| mCellNetworkAgent.connect(false); |
| mCellNetworkAgent.sendLinkProperties(cellLp); |
| waitForIdle(); |
| expectNotifyNetworkStatus(onlyCell, MOBILE_IFNAME); |
| reset(mStatsManager); |
| |
| // Default network switch should update ifaces. |
| mWiFiNetworkAgent.connect(false); |
| mWiFiNetworkAgent.sendLinkProperties(wifiLp); |
| waitForIdle(); |
| assertEquals(wifiLp, mService.getActiveLinkProperties()); |
| expectNotifyNetworkStatus(onlyWifi, WIFI_IFNAME); |
| reset(mStatsManager); |
| |
| // Disconnect should update ifaces. |
| mWiFiNetworkAgent.disconnect(); |
| waitForIdle(); |
| expectNotifyNetworkStatus(onlyCell, MOBILE_IFNAME); |
| reset(mStatsManager); |
| |
| // Metered change should update ifaces |
| mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); |
| waitForIdle(); |
| expectNotifyNetworkStatus(onlyCell, MOBILE_IFNAME); |
| reset(mStatsManager); |
| |
| mCellNetworkAgent.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); |
| waitForIdle(); |
| expectNotifyNetworkStatus(onlyCell, MOBILE_IFNAME); |
| reset(mStatsManager); |
| |
| // Temp metered change shouldn't update ifaces |
| mCellNetworkAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED); |
| waitForIdle(); |
| verify(mStatsManager, never()).notifyNetworkStatus(eq(onlyCell), |
| any(List.class), eq(MOBILE_IFNAME), any(List.class)); |
| reset(mStatsManager); |
| |
| // Roaming change should update ifaces |
| mCellNetworkAgent.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING); |
| waitForIdle(); |
| expectNotifyNetworkStatus(onlyCell, MOBILE_IFNAME); |
| reset(mStatsManager); |
| |
| // Test VPNs. |
| final LinkProperties lp = new LinkProperties(); |
| lp.setInterfaceName(VPN_IFNAME); |
| |
| mMockVpn.establishForMyUid(lp); |
| assertUidRangesUpdatedForMyUid(true); |
| |
| final List<Network> cellAndVpn = |
| List.of(mCellNetworkAgent.getNetwork(), mMockVpn.getNetwork()); |
| |
| // A VPN with default (null) underlying networks sets the underlying network's interfaces... |
| expectNotifyNetworkStatus(cellAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, |
| List.of(MOBILE_IFNAME)); |
| |
| // ...and updates them as the default network switches. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(false); |
| mWiFiNetworkAgent.sendLinkProperties(wifiLp); |
| final Network[] onlyNull = new Network[]{null}; |
| final List<Network> wifiAndVpn = |
| List.of(mWiFiNetworkAgent.getNetwork(), mMockVpn.getNetwork()); |
| final List<Network> cellAndWifi = |
| List.of(mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork()); |
| final Network[] cellNullAndWifi = |
| new Network[]{mCellNetworkAgent.getNetwork(), null, mWiFiNetworkAgent.getNetwork()}; |
| |
| waitForIdle(); |
| assertEquals(wifiLp, mService.getActiveLinkProperties()); |
| expectNotifyNetworkStatus(wifiAndVpn, WIFI_IFNAME, Process.myUid(), VPN_IFNAME, |
| List.of(WIFI_IFNAME)); |
| reset(mStatsManager); |
| |
| // A VPN that sets its underlying networks passes the underlying interfaces, and influences |
| // the default interface sent to NetworkStatsService by virtue of applying to the system |
| // server UID (or, in this test, to the test's UID). This is the reason for sending |
| // MOBILE_IFNAME even though the default network is wifi. |
| // TODO: fix this to pass in the actual default network interface. Whether or not the VPN |
| // applies to the system server UID should not have any bearing on network stats. |
| mMockVpn.setUnderlyingNetworks(onlyCell.toArray(new Network[0])); |
| waitForIdle(); |
| expectNotifyNetworkStatus(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, |
| List.of(MOBILE_IFNAME)); |
| reset(mStatsManager); |
| |
| mMockVpn.setUnderlyingNetworks(cellAndWifi.toArray(new Network[0])); |
| waitForIdle(); |
| expectNotifyNetworkStatus(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, |
| List.of(MOBILE_IFNAME, WIFI_IFNAME)); |
| reset(mStatsManager); |
| |
| // Null underlying networks are ignored. |
| mMockVpn.setUnderlyingNetworks(cellNullAndWifi); |
| waitForIdle(); |
| expectNotifyNetworkStatus(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, |
| List.of(MOBILE_IFNAME, WIFI_IFNAME)); |
| reset(mStatsManager); |
| |
| // If an underlying network disconnects, that interface should no longer be underlying. |
| // This doesn't actually work because disconnectAndDestroyNetwork only notifies |
| // NetworkStatsService before the underlying network is actually removed. So the underlying |
| // network will only be removed if notifyIfacesChangedForNetworkStats is called again. This |
| // could result in incorrect data usage measurements if the interface used by the |
| // disconnected network is reused by a system component that does not register an agent for |
| // it (e.g., tethering). |
| mCellNetworkAgent.disconnect(); |
| waitForIdle(); |
| assertNull(mService.getLinkProperties(mCellNetworkAgent.getNetwork())); |
| expectNotifyNetworkStatus(wifiAndVpn, MOBILE_IFNAME, Process.myUid(), VPN_IFNAME, |
| List.of(MOBILE_IFNAME, WIFI_IFNAME)); |
| |
| // Confirm that we never tell NetworkStatsService that cell is no longer the underlying |
| // network for the VPN... |
| verify(mStatsManager, never()).notifyNetworkStatus(any(List.class), |
| any(List.class), any() /* anyString() doesn't match null */, |
| argThat(infos -> infos.get(0).getUnderlyingInterfaces().size() == 1 |
| && WIFI_IFNAME.equals(infos.get(0).getUnderlyingInterfaces().get(0)))); |
| verifyNoMoreInteractions(mStatsManager); |
| reset(mStatsManager); |
| |
| // ... but if something else happens that causes notifyIfacesChangedForNetworkStats to be |
| // called again, it does. For example, connect Ethernet, but with a low score, such that it |
| // does not become the default network. |
| mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET); |
| mEthernetNetworkAgent.setScore( |
| new NetworkScore.Builder().setLegacyInt(30).setExiting(true).build()); |
| mEthernetNetworkAgent.connect(false); |
| waitForIdle(); |
| verify(mStatsManager).notifyNetworkStatus(any(List.class), |
| any(List.class), any() /* anyString() doesn't match null */, |
| argThat(vpnInfos -> vpnInfos.get(0).getUnderlyingInterfaces().size() == 1 |
| && WIFI_IFNAME.equals(vpnInfos.get(0).getUnderlyingInterfaces().get(0)))); |
| mEthernetNetworkAgent.disconnect(); |
| waitForIdle(); |
| reset(mStatsManager); |
| |
| // When a VPN declares no underlying networks (i.e., no connectivity), getAllVpnInfo |
| // does not return the VPN, so CS does not pass it to NetworkStatsService. This causes |
| // NetworkStatsFactory#adjustForTunAnd464Xlat not to attempt any VPN data migration, which |
| // is probably a performance improvement (though it's very unlikely that a VPN would declare |
| // no underlying networks). |
| // Also, for the same reason as above, the active interface passed in is null. |
| mMockVpn.setUnderlyingNetworks(new Network[0]); |
| waitForIdle(); |
| expectNotifyNetworkStatus(wifiAndVpn, null); |
| reset(mStatsManager); |
| |
| // Specifying only a null underlying network is the same as no networks. |
| mMockVpn.setUnderlyingNetworks(onlyNull); |
| waitForIdle(); |
| expectNotifyNetworkStatus(wifiAndVpn, null); |
| reset(mStatsManager); |
| |
| // Specifying networks that are all disconnected is the same as specifying no networks. |
| mMockVpn.setUnderlyingNetworks(onlyCell.toArray(new Network[0])); |
| waitForIdle(); |
| expectNotifyNetworkStatus(wifiAndVpn, null); |
| reset(mStatsManager); |
| |
| // Passing in null again means follow the default network again. |
| mMockVpn.setUnderlyingNetworks(null); |
| waitForIdle(); |
| expectNotifyNetworkStatus(wifiAndVpn, WIFI_IFNAME, Process.myUid(), VPN_IFNAME, |
| List.of(WIFI_IFNAME)); |
| reset(mStatsManager); |
| } |
| |
| @Test |
| public void testNonVpnUnderlyingNetworks() throws Exception { |
| // Ensure wifi and cellular are not torn down. |
| for (int transport : new int[]{TRANSPORT_CELLULAR, TRANSPORT_WIFI}) { |
| final NetworkRequest request = new NetworkRequest.Builder() |
| .addTransportType(transport) |
| .removeCapability(NET_CAPABILITY_NOT_VCN_MANAGED) |
| .build(); |
| mCm.requestNetwork(request, new NetworkCallback()); |
| } |
| |
| // Connect a VCN-managed wifi network. |
| final LinkProperties wifiLp = new LinkProperties(); |
| wifiLp.setInterfaceName(WIFI_IFNAME); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp); |
| mWiFiNetworkAgent.removeCapability(NET_CAPABILITY_NOT_VCN_MANAGED); |
| mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); |
| mWiFiNetworkAgent.connect(true /* validated */); |
| |
| final List<Network> none = List.of(); |
| expectNotifyNetworkStatus(none, null); // Wifi is not the default network |
| |
| // Create a virtual network based on the wifi network. |
| final int ownerUid = 10042; |
| NetworkCapabilities nc = new NetworkCapabilities.Builder() |
| .setOwnerUid(ownerUid) |
| .setAdministratorUids(new int[]{ownerUid}) |
| .build(); |
| final String vcnIface = "ipsec42"; |
| final LinkProperties lp = new LinkProperties(); |
| lp.setInterfaceName(vcnIface); |
| final TestNetworkAgentWrapper vcn = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, lp, nc); |
| vcn.setUnderlyingNetworks(List.of(mWiFiNetworkAgent.getNetwork())); |
| vcn.connect(false /* validated */); |
| |
| final TestNetworkCallback callback = new TestNetworkCallback(); |
| mCm.registerDefaultNetworkCallback(callback); |
| callback.expectAvailableCallbacksUnvalidated(vcn); |
| |
| // The underlying wifi network's capabilities are not propagated to the virtual network, |
| // but NetworkStatsService is informed of the underlying interface. |
| nc = mCm.getNetworkCapabilities(vcn.getNetwork()); |
| assertFalse(nc.hasTransport(TRANSPORT_WIFI)); |
| assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED)); |
| final List<Network> onlyVcn = List.of(vcn.getNetwork()); |
| expectNotifyNetworkStatus(onlyVcn, vcnIface, ownerUid, vcnIface, List.of(WIFI_IFNAME)); |
| |
| // Add NOT_METERED to the underlying network, check that it is not propagated. |
| mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); |
| callback.assertNoCallback(); |
| nc = mCm.getNetworkCapabilities(vcn.getNetwork()); |
| assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED)); |
| |
| // Switch underlying networks. |
| final LinkProperties cellLp = new LinkProperties(); |
| cellLp.setInterfaceName(MOBILE_IFNAME); |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp); |
| mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_VCN_MANAGED); |
| mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_ROAMING); |
| mCellNetworkAgent.connect(false /* validated */); |
| vcn.setUnderlyingNetworks(List.of(mCellNetworkAgent.getNetwork())); |
| |
| // The underlying capability changes do not propagate to the virtual network, but |
| // NetworkStatsService is informed of the new underlying interface. |
| callback.assertNoCallback(); |
| nc = mCm.getNetworkCapabilities(vcn.getNetwork()); |
| assertFalse(nc.hasTransport(TRANSPORT_WIFI)); |
| assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_ROAMING)); |
| expectNotifyNetworkStatus(onlyVcn, vcnIface, ownerUid, vcnIface, List.of(MOBILE_IFNAME)); |
| } |
| |
| @Test |
| public void testBasicDnsConfigurationPushed() throws Exception { |
| setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com"); |
| |
| // Clear any interactions that occur as a result of CS starting up. |
| reset(mMockDnsResolver); |
| |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| waitForIdle(); |
| verify(mMockDnsResolver, never()).setResolverConfiguration(any()); |
| verifyNoMoreInteractions(mMockDnsResolver); |
| |
| final LinkProperties cellLp = new LinkProperties(); |
| cellLp.setInterfaceName(MOBILE_IFNAME); |
| // Add IPv4 and IPv6 default routes, because DNS-over-TLS code does |
| // "is-reachable" testing in order to not program netd with unreachable |
| // nameservers that it might try repeated to validate. |
| cellLp.addLinkAddress(new LinkAddress("192.0.2.4/24")); |
| cellLp.addRoute(new RouteInfo((IpPrefix) null, InetAddress.getByName("192.0.2.4"), |
| MOBILE_IFNAME)); |
| cellLp.addLinkAddress(new LinkAddress("2001:db8:1::1/64")); |
| cellLp.addRoute(new RouteInfo((IpPrefix) null, InetAddress.getByName("2001:db8:1::1"), |
| MOBILE_IFNAME)); |
| mCellNetworkAgent.sendLinkProperties(cellLp); |
| mCellNetworkAgent.connect(false); |
| waitForIdle(); |
| |
| verify(mMockDnsResolver, times(1)).createNetworkCache( |
| eq(mCellNetworkAgent.getNetwork().netId)); |
| // CS tells dnsresolver about the empty DNS config for this network. |
| verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(any()); |
| reset(mMockDnsResolver); |
| |
| cellLp.addDnsServer(InetAddress.getByName("2001:db8::1")); |
| mCellNetworkAgent.sendLinkProperties(cellLp); |
| waitForIdle(); |
| verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration( |
| mResolverParamsParcelCaptor.capture()); |
| ResolverParamsParcel resolvrParams = mResolverParamsParcelCaptor.getValue(); |
| assertEquals(1, resolvrParams.servers.length); |
| assertTrue(ArrayUtils.contains(resolvrParams.servers, "2001:db8::1")); |
| // Opportunistic mode. |
| assertTrue(ArrayUtils.contains(resolvrParams.tlsServers, "2001:db8::1")); |
| reset(mMockDnsResolver); |
| |
| cellLp.addDnsServer(InetAddress.getByName("192.0.2.1")); |
| mCellNetworkAgent.sendLinkProperties(cellLp); |
| waitForIdle(); |
| verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration( |
| mResolverParamsParcelCaptor.capture()); |
| resolvrParams = mResolverParamsParcelCaptor.getValue(); |
| assertEquals(2, resolvrParams.servers.length); |
| assertTrue(ArrayUtils.containsAll(resolvrParams.servers, |
| new String[]{"2001:db8::1", "192.0.2.1"})); |
| // Opportunistic mode. |
| assertEquals(2, resolvrParams.tlsServers.length); |
| assertTrue(ArrayUtils.containsAll(resolvrParams.tlsServers, |
| new String[]{"2001:db8::1", "192.0.2.1"})); |
| reset(mMockDnsResolver); |
| |
| final String TLS_SPECIFIER = "tls.example.com"; |
| final String TLS_SERVER6 = "2001:db8:53::53"; |
| final InetAddress[] TLS_IPS = new InetAddress[]{ InetAddress.getByName(TLS_SERVER6) }; |
| final String[] TLS_SERVERS = new String[]{ TLS_SERVER6 }; |
| mCellNetworkAgent.mNmCallbacks.notifyPrivateDnsConfigResolved( |
| new PrivateDnsConfig(TLS_SPECIFIER, TLS_IPS).toParcel()); |
| |
| waitForIdle(); |
| verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration( |
| mResolverParamsParcelCaptor.capture()); |
| resolvrParams = mResolverParamsParcelCaptor.getValue(); |
| assertEquals(2, resolvrParams.servers.length); |
| assertTrue(ArrayUtils.containsAll(resolvrParams.servers, |
| new String[]{"2001:db8::1", "192.0.2.1"})); |
| reset(mMockDnsResolver); |
| } |
| |
| @Test |
| public void testDnsConfigurationTransTypesPushed() throws Exception { |
| // Clear any interactions that occur as a result of CS starting up. |
| reset(mMockDnsResolver); |
| |
| final NetworkRequest request = new NetworkRequest.Builder() |
| .clearCapabilities().addCapability(NET_CAPABILITY_INTERNET) |
| .build(); |
| final TestNetworkCallback callback = new TestNetworkCallback(); |
| mCm.registerNetworkCallback(request, callback); |
| |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(false); |
| callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| verify(mMockDnsResolver, times(1)).createNetworkCache( |
| eq(mWiFiNetworkAgent.getNetwork().netId)); |
| verify(mMockDnsResolver, times(2)).setResolverConfiguration( |
| mResolverParamsParcelCaptor.capture()); |
| final ResolverParamsParcel resolverParams = mResolverParamsParcelCaptor.getValue(); |
| assertContainsExactly(resolverParams.transportTypes, TRANSPORT_WIFI); |
| reset(mMockDnsResolver); |
| } |
| |
| @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)).notify(anyString(), |
| eq(NotificationType.PRIVATE_DNS_BROKEN.eventId), any()); |
| // 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)).cancel(anyString(), |
| eq(NotificationType.PRIVATE_DNS_BROKEN.eventId)); |
| // 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)).notify(anyString(), |
| eq(NotificationType.PRIVATE_DNS_BROKEN.eventId), any()); |
| } |
| |
| @Test |
| public void testPrivateDnsSettingsChange() throws Exception { |
| // Clear any interactions that occur as a result of CS starting up. |
| reset(mMockDnsResolver); |
| |
| // The default on Android is opportunistic mode ("Automatic"). |
| setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com"); |
| |
| final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); |
| final NetworkRequest cellRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_CELLULAR).build(); |
| mCm.requestNetwork(cellRequest, cellNetworkCallback); |
| |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| waitForIdle(); |
| // CS tells netd about the empty DNS config for this network. |
| verify(mMockDnsResolver, never()).setResolverConfiguration(any()); |
| verifyNoMoreInteractions(mMockDnsResolver); |
| |
| final LinkProperties cellLp = new LinkProperties(); |
| cellLp.setInterfaceName(MOBILE_IFNAME); |
| // Add IPv4 and IPv6 default routes, because DNS-over-TLS code does |
| // "is-reachable" testing in order to not program netd with unreachable |
| // nameservers that it might try repeated to validate. |
| cellLp.addLinkAddress(new LinkAddress("192.0.2.4/24")); |
| cellLp.addRoute(new RouteInfo((IpPrefix) null, InetAddress.getByName("192.0.2.4"), |
| MOBILE_IFNAME)); |
| cellLp.addLinkAddress(new LinkAddress("2001:db8:1::1/64")); |
| cellLp.addRoute(new RouteInfo((IpPrefix) null, InetAddress.getByName("2001:db8:1::1"), |
| MOBILE_IFNAME)); |
| cellLp.addDnsServer(InetAddress.getByName("2001:db8::1")); |
| cellLp.addDnsServer(InetAddress.getByName("192.0.2.1")); |
| |
| mCellNetworkAgent.sendLinkProperties(cellLp); |
| mCellNetworkAgent.connect(false); |
| waitForIdle(); |
| verify(mMockDnsResolver, times(1)).createNetworkCache( |
| eq(mCellNetworkAgent.getNetwork().netId)); |
| verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration( |
| mResolverParamsParcelCaptor.capture()); |
| ResolverParamsParcel resolvrParams = mResolverParamsParcelCaptor.getValue(); |
| assertEquals(2, resolvrParams.tlsServers.length); |
| assertTrue(ArrayUtils.containsAll(resolvrParams.tlsServers, |
| new String[] { "2001:db8::1", "192.0.2.1" })); |
| // Opportunistic mode. |
| assertEquals(2, resolvrParams.tlsServers.length); |
| assertTrue(ArrayUtils.containsAll(resolvrParams.tlsServers, |
| new String[] { "2001:db8::1", "192.0.2.1" })); |
| reset(mMockDnsResolver); |
| cellNetworkCallback.expectCallback(CallbackEntry.AVAILABLE, mCellNetworkAgent); |
| cellNetworkCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, |
| mCellNetworkAgent); |
| CallbackEntry.LinkPropertiesChanged cbi = cellNetworkCallback.expectCallback( |
| CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); |
| cellNetworkCallback.expectCallback(CallbackEntry.BLOCKED_STATUS, mCellNetworkAgent); |
| cellNetworkCallback.assertNoCallback(); |
| assertFalse(cbi.getLp().isPrivateDnsActive()); |
| assertNull(cbi.getLp().getPrivateDnsServerName()); |
| |
| setPrivateDnsSettings(PRIVATE_DNS_MODE_OFF, "ignored.example.com"); |
| verify(mMockDnsResolver, times(1)).setResolverConfiguration( |
| mResolverParamsParcelCaptor.capture()); |
| resolvrParams = mResolverParamsParcelCaptor.getValue(); |
| assertEquals(2, resolvrParams.servers.length); |
| assertTrue(ArrayUtils.containsAll(resolvrParams.servers, |
| new String[] { "2001:db8::1", "192.0.2.1" })); |
| reset(mMockDnsResolver); |
| cellNetworkCallback.assertNoCallback(); |
| |
| setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com"); |
| verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration( |
| mResolverParamsParcelCaptor.capture()); |
| resolvrParams = mResolverParamsParcelCaptor.getValue(); |
| assertEquals(2, resolvrParams.servers.length); |
| assertTrue(ArrayUtils.containsAll(resolvrParams.servers, |
| new String[] { "2001:db8::1", "192.0.2.1" })); |
| assertEquals(2, resolvrParams.tlsServers.length); |
| assertTrue(ArrayUtils.containsAll(resolvrParams.tlsServers, |
| new String[] { "2001:db8::1", "192.0.2.1" })); |
| reset(mMockDnsResolver); |
| cellNetworkCallback.assertNoCallback(); |
| |
| setPrivateDnsSettings(PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, "strict.example.com"); |
| // Can't test dns configuration for strict mode without properly mocking |
| // out the DNS lookups, but can test that LinkProperties is updated. |
| cbi = cellNetworkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, |
| mCellNetworkAgent); |
| cellNetworkCallback.assertNoCallback(); |
| assertTrue(cbi.getLp().isPrivateDnsActive()); |
| assertEquals("strict.example.com", cbi.getLp().getPrivateDnsServerName()); |
| } |
| |
| private PrivateDnsValidationEventParcel makePrivateDnsValidationEvent( |
| final int netId, final String ipAddress, final String hostname, final int validation) { |
| final PrivateDnsValidationEventParcel event = new PrivateDnsValidationEventParcel(); |
| event.netId = netId; |
| event.ipAddress = ipAddress; |
| event.hostname = hostname; |
| event.validation = validation; |
| return event; |
| } |
| |
| @Test |
| public void testLinkPropertiesWithPrivateDnsValidationEvents() throws Exception { |
| // The default on Android is opportunistic mode ("Automatic"). |
| setPrivateDnsSettings(PRIVATE_DNS_MODE_OPPORTUNISTIC, "ignored.example.com"); |
| |
| final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); |
| final NetworkRequest cellRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_CELLULAR).build(); |
| mCm.requestNetwork(cellRequest, cellNetworkCallback); |
| |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| waitForIdle(); |
| LinkProperties lp = new LinkProperties(); |
| mCellNetworkAgent.sendLinkProperties(lp); |
| mCellNetworkAgent.connect(false); |
| waitForIdle(); |
| cellNetworkCallback.expectCallback(CallbackEntry.AVAILABLE, mCellNetworkAgent); |
| cellNetworkCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, |
| mCellNetworkAgent); |
| CallbackEntry.LinkPropertiesChanged cbi = cellNetworkCallback.expectCallback( |
| CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); |
| cellNetworkCallback.expectCallback(CallbackEntry.BLOCKED_STATUS, mCellNetworkAgent); |
| cellNetworkCallback.assertNoCallback(); |
| assertFalse(cbi.getLp().isPrivateDnsActive()); |
| assertNull(cbi.getLp().getPrivateDnsServerName()); |
| Set<InetAddress> dnsServers = new HashSet<>(); |
| checkDnsServers(cbi.getLp(), dnsServers); |
| |
| // Send a validation event for a server that is not part of the current |
| // resolver config. The validation event should be ignored. |
| mService.mResolverUnsolEventCallback.onPrivateDnsValidationEvent( |
| makePrivateDnsValidationEvent(mCellNetworkAgent.getNetwork().netId, "", |
| "145.100.185.18", VALIDATION_RESULT_SUCCESS)); |
| cellNetworkCallback.assertNoCallback(); |
| |
| // Add a dns server to the LinkProperties. |
| LinkProperties lp2 = new LinkProperties(lp); |
| lp2.addDnsServer(InetAddress.getByName("145.100.185.16")); |
| mCellNetworkAgent.sendLinkProperties(lp2); |
| cbi = cellNetworkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, |
| mCellNetworkAgent); |
| cellNetworkCallback.assertNoCallback(); |
| assertFalse(cbi.getLp().isPrivateDnsActive()); |
| assertNull(cbi.getLp().getPrivateDnsServerName()); |
| dnsServers.add(InetAddress.getByName("145.100.185.16")); |
| checkDnsServers(cbi.getLp(), dnsServers); |
| |
| // Send a validation event containing a hostname that is not part of |
| // the current resolver config. The validation event should be ignored. |
| mService.mResolverUnsolEventCallback.onPrivateDnsValidationEvent( |
| makePrivateDnsValidationEvent(mCellNetworkAgent.getNetwork().netId, |
| "145.100.185.16", "hostname", VALIDATION_RESULT_SUCCESS)); |
| cellNetworkCallback.assertNoCallback(); |
| |
| // Send a validation event where validation failed. |
| mService.mResolverUnsolEventCallback.onPrivateDnsValidationEvent( |
| makePrivateDnsValidationEvent(mCellNetworkAgent.getNetwork().netId, |
| "145.100.185.16", "", VALIDATION_RESULT_FAILURE)); |
| cellNetworkCallback.assertNoCallback(); |
| |
| // Send a validation event where validation succeeded for a server in |
| // the current resolver config. A LinkProperties callback with updated |
| // private dns fields should be sent. |
| mService.mResolverUnsolEventCallback.onPrivateDnsValidationEvent( |
| makePrivateDnsValidationEvent(mCellNetworkAgent.getNetwork().netId, |
| "145.100.185.16", "", VALIDATION_RESULT_SUCCESS)); |
| cbi = cellNetworkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, |
| mCellNetworkAgent); |
| cellNetworkCallback.assertNoCallback(); |
| assertTrue(cbi.getLp().isPrivateDnsActive()); |
| assertNull(cbi.getLp().getPrivateDnsServerName()); |
| checkDnsServers(cbi.getLp(), dnsServers); |
| |
| // The private dns fields in LinkProperties should be preserved when |
| // the network agent sends unrelated changes. |
| LinkProperties lp3 = new LinkProperties(lp2); |
| lp3.setMtu(1300); |
| mCellNetworkAgent.sendLinkProperties(lp3); |
| cbi = cellNetworkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, |
| mCellNetworkAgent); |
| cellNetworkCallback.assertNoCallback(); |
| assertTrue(cbi.getLp().isPrivateDnsActive()); |
| assertNull(cbi.getLp().getPrivateDnsServerName()); |
| checkDnsServers(cbi.getLp(), dnsServers); |
| assertEquals(1300, cbi.getLp().getMtu()); |
| |
| // Removing the only validated server should affect the private dns |
| // fields in LinkProperties. |
| LinkProperties lp4 = new LinkProperties(lp3); |
| lp4.removeDnsServer(InetAddress.getByName("145.100.185.16")); |
| mCellNetworkAgent.sendLinkProperties(lp4); |
| cbi = cellNetworkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, |
| mCellNetworkAgent); |
| cellNetworkCallback.assertNoCallback(); |
| assertFalse(cbi.getLp().isPrivateDnsActive()); |
| assertNull(cbi.getLp().getPrivateDnsServerName()); |
| dnsServers.remove(InetAddress.getByName("145.100.185.16")); |
| checkDnsServers(cbi.getLp(), dnsServers); |
| assertEquals(1300, cbi.getLp().getMtu()); |
| } |
| |
| private void checkDirectlyConnectedRoutes(Object callbackObj, |
| Collection<LinkAddress> linkAddresses, Collection<RouteInfo> otherRoutes) { |
| assertTrue(callbackObj instanceof LinkProperties); |
| LinkProperties lp = (LinkProperties) callbackObj; |
| |
| Set<RouteInfo> expectedRoutes = new ArraySet<>(); |
| expectedRoutes.addAll(otherRoutes); |
| for (LinkAddress address : linkAddresses) { |
| RouteInfo localRoute = new RouteInfo(address, null, lp.getInterfaceName()); |
| // Duplicates in linkAddresses are considered failures |
| assertTrue(expectedRoutes.add(localRoute)); |
| } |
| List<RouteInfo> observedRoutes = lp.getRoutes(); |
| assertEquals(expectedRoutes.size(), observedRoutes.size()); |
| assertTrue(observedRoutes.containsAll(expectedRoutes)); |
| } |
| |
| private static void checkDnsServers(Object callbackObj, Set<InetAddress> dnsServers) { |
| assertTrue(callbackObj instanceof LinkProperties); |
| LinkProperties lp = (LinkProperties) callbackObj; |
| assertEquals(dnsServers.size(), lp.getDnsServers().size()); |
| assertTrue(lp.getDnsServers().containsAll(dnsServers)); |
| } |
| |
| @Test |
| public void testApplyUnderlyingCapabilities() throws Exception { |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mCellNetworkAgent.connect(false /* validated */); |
| mWiFiNetworkAgent.connect(false /* validated */); |
| |
| final NetworkCapabilities cellNc = new NetworkCapabilities() |
| .addTransportType(TRANSPORT_CELLULAR) |
| .addCapability(NET_CAPABILITY_INTERNET) |
| .addCapability(NET_CAPABILITY_NOT_CONGESTED) |
| .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) |
| .setLinkDownstreamBandwidthKbps(10); |
| final NetworkCapabilities wifiNc = new NetworkCapabilities() |
| .addTransportType(TRANSPORT_WIFI) |
| .addCapability(NET_CAPABILITY_INTERNET) |
| .addCapability(NET_CAPABILITY_NOT_METERED) |
| .addCapability(NET_CAPABILITY_NOT_ROAMING) |
| .addCapability(NET_CAPABILITY_NOT_CONGESTED) |
| .addCapability(NET_CAPABILITY_NOT_SUSPENDED) |
| .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) |
| .setLinkUpstreamBandwidthKbps(20); |
| mCellNetworkAgent.setNetworkCapabilities(cellNc, true /* sendToConnectivityService */); |
| mWiFiNetworkAgent.setNetworkCapabilities(wifiNc, true /* sendToConnectivityService */); |
| waitForIdle(); |
| |
| final Network mobile = mCellNetworkAgent.getNetwork(); |
| final Network wifi = mWiFiNetworkAgent.getNetwork(); |
| |
| final NetworkCapabilities initialCaps = new NetworkCapabilities(); |
| initialCaps.addTransportType(TRANSPORT_VPN); |
| initialCaps.addCapability(NET_CAPABILITY_INTERNET); |
| initialCaps.removeCapability(NET_CAPABILITY_NOT_VPN); |
| |
| final NetworkCapabilities withNoUnderlying = new NetworkCapabilities(); |
| withNoUnderlying.addCapability(NET_CAPABILITY_INTERNET); |
| withNoUnderlying.addCapability(NET_CAPABILITY_NOT_CONGESTED); |
| withNoUnderlying.addCapability(NET_CAPABILITY_NOT_ROAMING); |
| withNoUnderlying.addCapability(NET_CAPABILITY_NOT_SUSPENDED); |
| withNoUnderlying.addTransportType(TRANSPORT_VPN); |
| withNoUnderlying.removeCapability(NET_CAPABILITY_NOT_VPN); |
| |
| final NetworkCapabilities withMobileUnderlying = new NetworkCapabilities(withNoUnderlying); |
| withMobileUnderlying.addTransportType(TRANSPORT_CELLULAR); |
| withMobileUnderlying.removeCapability(NET_CAPABILITY_NOT_ROAMING); |
| withMobileUnderlying.removeCapability(NET_CAPABILITY_NOT_SUSPENDED); |
| withMobileUnderlying.setLinkDownstreamBandwidthKbps(10); |
| |
| final NetworkCapabilities withWifiUnderlying = new NetworkCapabilities(withNoUnderlying); |
| withWifiUnderlying.addTransportType(TRANSPORT_WIFI); |
| withWifiUnderlying.addCapability(NET_CAPABILITY_NOT_METERED); |
| withWifiUnderlying.setLinkUpstreamBandwidthKbps(20); |
| |
| final NetworkCapabilities withWifiAndMobileUnderlying = |
| new NetworkCapabilities(withNoUnderlying); |
| withWifiAndMobileUnderlying.addTransportType(TRANSPORT_CELLULAR); |
| withWifiAndMobileUnderlying.addTransportType(TRANSPORT_WIFI); |
| withWifiAndMobileUnderlying.removeCapability(NET_CAPABILITY_NOT_METERED); |
| withWifiAndMobileUnderlying.removeCapability(NET_CAPABILITY_NOT_ROAMING); |
| withWifiAndMobileUnderlying.setLinkDownstreamBandwidthKbps(10); |
| withWifiAndMobileUnderlying.setLinkUpstreamBandwidthKbps(20); |
| |
| final NetworkCapabilities initialCapsNotMetered = new NetworkCapabilities(initialCaps); |
| initialCapsNotMetered.addCapability(NET_CAPABILITY_NOT_METERED); |
| |
| NetworkCapabilities caps = new NetworkCapabilities(initialCaps); |
| mService.applyUnderlyingCapabilities(new Network[]{}, initialCapsNotMetered, caps); |
| assertEquals(withNoUnderlying, caps); |
| |
| caps = new NetworkCapabilities(initialCaps); |
| mService.applyUnderlyingCapabilities(new Network[]{null}, initialCapsNotMetered, caps); |
| assertEquals(withNoUnderlying, caps); |
| |
| caps = new NetworkCapabilities(initialCaps); |
| mService.applyUnderlyingCapabilities(new Network[]{mobile}, initialCapsNotMetered, caps); |
| assertEquals(withMobileUnderlying, caps); |
| |
| mService.applyUnderlyingCapabilities(new Network[]{wifi}, initialCapsNotMetered, caps); |
| assertEquals(withWifiUnderlying, caps); |
| |
| withWifiUnderlying.removeCapability(NET_CAPABILITY_NOT_METERED); |
| caps = new NetworkCapabilities(initialCaps); |
| mService.applyUnderlyingCapabilities(new Network[]{wifi}, initialCaps, caps); |
| assertEquals(withWifiUnderlying, caps); |
| |
| caps = new NetworkCapabilities(initialCaps); |
| mService.applyUnderlyingCapabilities(new Network[]{mobile, wifi}, initialCaps, caps); |
| assertEquals(withWifiAndMobileUnderlying, caps); |
| |
| withWifiUnderlying.addCapability(NET_CAPABILITY_NOT_METERED); |
| caps = new NetworkCapabilities(initialCaps); |
| mService.applyUnderlyingCapabilities(new Network[]{null, mobile, null, wifi}, |
| initialCapsNotMetered, caps); |
| assertEquals(withWifiAndMobileUnderlying, caps); |
| |
| caps = new NetworkCapabilities(initialCaps); |
| mService.applyUnderlyingCapabilities(new Network[]{null, mobile, null, wifi}, |
| initialCapsNotMetered, caps); |
| assertEquals(withWifiAndMobileUnderlying, caps); |
| |
| mService.applyUnderlyingCapabilities(null, initialCapsNotMetered, caps); |
| assertEquals(withWifiUnderlying, caps); |
| } |
| |
| @Test |
| public void testVpnConnectDisconnectUnderlyingNetwork() throws Exception { |
| final TestNetworkCallback callback = new TestNetworkCallback(); |
| final NetworkRequest request = new NetworkRequest.Builder() |
| .removeCapability(NET_CAPABILITY_NOT_VPN).build(); |
| |
| mCm.registerNetworkCallback(request, callback); |
| |
| // Bring up a VPN that specifies an underlying network that does not exist yet. |
| // Note: it's sort of meaningless for a VPN app to declare a network that doesn't exist yet, |
| // (and doing so is difficult without using reflection) but it's good to test that the code |
| // behaves approximately correctly. |
| mMockVpn.establishForMyUid(false, true, false); |
| assertUidRangesUpdatedForMyUid(true); |
| final Network wifiNetwork = new Network(mNetIdManager.peekNextNetId()); |
| mMockVpn.setUnderlyingNetworks(new Network[]{wifiNetwork}); |
| callback.expectAvailableCallbacksUnvalidated(mMockVpn); |
| assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) |
| .hasTransport(TRANSPORT_VPN)); |
| assertFalse(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) |
| .hasTransport(TRANSPORT_WIFI)); |
| |
| // Make that underlying network connect, and expect to see its capabilities immediately |
| // reflected in the VPN's capabilities. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| assertEquals(wifiNetwork, mWiFiNetworkAgent.getNetwork()); |
| mWiFiNetworkAgent.connect(false); |
| // TODO: the callback for the VPN happens before any callbacks are called for the wifi |
| // network that has just connected. There appear to be two issues here: |
| // 1. The VPN code will accept an underlying network as soon as getNetworkCapabilities() for |
| // it returns non-null (which happens very early, during handleRegisterNetworkAgent). |
| // This is not correct because that that point the network is not connected and cannot |
| // pass any traffic. |
| // 2. When a network connects, updateNetworkInfo propagates underlying network capabilities |
| // before rematching networks. |
| // Given that this scenario can't really happen, this is probably fine for now. |
| callback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn); |
| callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) |
| .hasTransport(TRANSPORT_VPN)); |
| assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) |
| .hasTransport(TRANSPORT_WIFI)); |
| |
| // Disconnect the network, and expect to see the VPN capabilities change accordingly. |
| mWiFiNetworkAgent.disconnect(); |
| callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| callback.expectCapabilitiesThat(mMockVpn, (nc) -> |
| nc.getTransportTypes().length == 1 && nc.hasTransport(TRANSPORT_VPN)); |
| |
| mMockVpn.disconnect(); |
| mCm.unregisterNetworkCallback(callback); |
| } |
| |
| private void assertGetNetworkInfoOfGetActiveNetworkIsConnected(boolean expectedConnectivity) { |
| // What Chromium used to do before https://chromium-review.googlesource.com/2605304 |
| assertEquals("Unexpected result for getActiveNetworkInfo(getActiveNetwork())", |
| expectedConnectivity, mCm.getNetworkInfo(mCm.getActiveNetwork()).isConnected()); |
| } |
| |
| @Test |
| public void testVpnUnderlyingNetworkSuspended() throws Exception { |
| final TestNetworkCallback callback = new TestNetworkCallback(); |
| mCm.registerDefaultNetworkCallback(callback); |
| |
| // Connect a VPN. |
| mMockVpn.establishForMyUid(false /* validated */, true /* hasInternet */, |
| false /* isStrictMode */); |
| callback.expectAvailableCallbacksUnvalidated(mMockVpn); |
| |
| // Connect cellular data. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(false /* validated */); |
| callback.expectCapabilitiesThat(mMockVpn, |
| nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) |
| && nc.hasTransport(TRANSPORT_CELLULAR)); |
| callback.assertNoCallback(); |
| |
| assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) |
| .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); |
| assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); |
| assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); |
| assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); |
| assertGetNetworkInfoOfGetActiveNetworkIsConnected(true); |
| |
| // Suspend the cellular network and expect the VPN to be suspended. |
| mCellNetworkAgent.suspend(); |
| callback.expectCapabilitiesThat(mMockVpn, |
| nc -> !nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) |
| && nc.hasTransport(TRANSPORT_CELLULAR)); |
| callback.expectCallback(CallbackEntry.SUSPENDED, mMockVpn); |
| callback.assertNoCallback(); |
| |
| assertFalse(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) |
| .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.SUSPENDED); |
| assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); |
| assertNetworkInfo(TYPE_VPN, DetailedState.SUSPENDED); |
| assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.SUSPENDED); |
| // VPN's main underlying network is suspended, so no connectivity. |
| assertGetNetworkInfoOfGetActiveNetworkIsConnected(false); |
| |
| // Switch to another network. The VPN should no longer be suspended. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(false /* validated */); |
| callback.expectCapabilitiesThat(mMockVpn, |
| nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) |
| && nc.hasTransport(TRANSPORT_WIFI)); |
| callback.expectCallback(CallbackEntry.RESUMED, mMockVpn); |
| callback.assertNoCallback(); |
| |
| assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) |
| .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); |
| assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); |
| assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); |
| assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); |
| assertGetNetworkInfoOfGetActiveNetworkIsConnected(true); |
| |
| // Unsuspend cellular and then switch back to it. The VPN remains not suspended. |
| mCellNetworkAgent.resume(); |
| callback.assertNoCallback(); |
| mWiFiNetworkAgent.disconnect(); |
| callback.expectCapabilitiesThat(mMockVpn, |
| nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) |
| && nc.hasTransport(TRANSPORT_CELLULAR)); |
| // Spurious double callback? |
| callback.expectCapabilitiesThat(mMockVpn, |
| nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) |
| && nc.hasTransport(TRANSPORT_CELLULAR)); |
| callback.assertNoCallback(); |
| |
| assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) |
| .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); |
| assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); |
| assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); |
| assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); |
| assertGetNetworkInfoOfGetActiveNetworkIsConnected(true); |
| |
| // Suspend cellular and expect no connectivity. |
| mCellNetworkAgent.suspend(); |
| callback.expectCapabilitiesThat(mMockVpn, |
| nc -> !nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) |
| && nc.hasTransport(TRANSPORT_CELLULAR)); |
| callback.expectCallback(CallbackEntry.SUSPENDED, mMockVpn); |
| callback.assertNoCallback(); |
| |
| assertFalse(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) |
| .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.SUSPENDED); |
| assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); |
| assertNetworkInfo(TYPE_VPN, DetailedState.SUSPENDED); |
| assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.SUSPENDED); |
| assertGetNetworkInfoOfGetActiveNetworkIsConnected(false); |
| |
| // Resume cellular and expect that connectivity comes back. |
| mCellNetworkAgent.resume(); |
| callback.expectCapabilitiesThat(mMockVpn, |
| nc -> nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED) |
| && nc.hasTransport(TRANSPORT_CELLULAR)); |
| callback.expectCallback(CallbackEntry.RESUMED, mMockVpn); |
| callback.assertNoCallback(); |
| |
| assertTrue(mCm.getNetworkCapabilities(mMockVpn.getNetwork()) |
| .hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); |
| assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); |
| assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); |
| assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); |
| assertGetNetworkInfoOfGetActiveNetworkIsConnected(true); |
| } |
| |
| @Test |
| public void testVpnNetworkActive() throws Exception { |
| // NETWORK_SETTINGS is necessary to call registerSystemDefaultNetworkCallback. |
| mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED); |
| |
| final int uid = Process.myUid(); |
| |
| final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback(); |
| final TestNetworkCallback genericNotVpnNetworkCallback = new TestNetworkCallback(); |
| final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback(); |
| final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback(); |
| final TestNetworkCallback defaultCallback = new TestNetworkCallback(); |
| final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback(); |
| final NetworkRequest genericNotVpnRequest = new NetworkRequest.Builder().build(); |
| final NetworkRequest genericRequest = new NetworkRequest.Builder() |
| .removeCapability(NET_CAPABILITY_NOT_VPN).build(); |
| final NetworkRequest wifiRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_WIFI).build(); |
| final NetworkRequest vpnNetworkRequest = new NetworkRequest.Builder() |
| .removeCapability(NET_CAPABILITY_NOT_VPN) |
| .addTransportType(TRANSPORT_VPN).build(); |
| mCm.registerNetworkCallback(genericRequest, genericNetworkCallback); |
| mCm.registerNetworkCallback(genericNotVpnRequest, genericNotVpnNetworkCallback); |
| mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback); |
| mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback); |
| mCm.registerDefaultNetworkCallback(defaultCallback); |
| mCm.registerSystemDefaultNetworkCallback(systemDefaultCallback, |
| new Handler(ConnectivityThread.getInstanceLooper())); |
| defaultCallback.assertNoCallback(); |
| |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(false); |
| |
| genericNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| genericNotVpnNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| defaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| systemDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| vpnNetworkCallback.assertNoCallback(); |
| assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| |
| final Set<UidRange> ranges = uidRangesForUids(uid); |
| mMockVpn.registerAgent(ranges); |
| mMockVpn.setUnderlyingNetworks(new Network[0]); |
| |
| // VPN networks do not satisfy the default request and are automatically validated |
| // by NetworkMonitor |
| assertFalse(NetworkMonitorUtils.isValidationRequired( |
| mMockVpn.getAgent().getNetworkCapabilities())); |
| mMockVpn.getAgent().setNetworkValid(false /* isStrictMode */); |
| |
| mMockVpn.connect(false); |
| |
| genericNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn); |
| genericNotVpnNetworkCallback.assertNoCallback(); |
| wifiNetworkCallback.assertNoCallback(); |
| vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn); |
| defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); |
| systemDefaultCallback.assertNoCallback(); |
| assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), |
| systemDefaultCallback.getLastAvailableNetwork()); |
| |
| ranges.clear(); |
| mMockVpn.setUids(ranges); |
| |
| genericNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn); |
| genericNotVpnNetworkCallback.assertNoCallback(); |
| wifiNetworkCallback.assertNoCallback(); |
| vpnNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn); |
| |
| // TODO : The default network callback should actually get a LOST call here (also see the |
| // comment below for AVAILABLE). This is because ConnectivityService does not look at UID |
| // ranges at all when determining whether a network should be rematched. In practice, VPNs |
| // can't currently update their UIDs without disconnecting, so this does not matter too |
| // much, but that is the reason the test here has to check for an update to the |
| // capabilities instead of the expected LOST then AVAILABLE. |
| defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn); |
| systemDefaultCallback.assertNoCallback(); |
| |
| ranges.add(new UidRange(uid, uid)); |
| mMockVpn.setUids(ranges); |
| |
| genericNetworkCallback.expectAvailableCallbacksValidated(mMockVpn); |
| genericNotVpnNetworkCallback.assertNoCallback(); |
| wifiNetworkCallback.assertNoCallback(); |
| vpnNetworkCallback.expectAvailableCallbacksValidated(mMockVpn); |
| // TODO : Here like above, AVAILABLE would be correct, but because this can't actually |
| // happen outside of the test, ConnectivityService does not rematch callbacks. |
| defaultCallback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn); |
| systemDefaultCallback.assertNoCallback(); |
| |
| mWiFiNetworkAgent.disconnect(); |
| |
| genericNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| genericNotVpnNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| wifiNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| vpnNetworkCallback.assertNoCallback(); |
| defaultCallback.assertNoCallback(); |
| systemDefaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| |
| mMockVpn.disconnect(); |
| |
| genericNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn); |
| genericNotVpnNetworkCallback.assertNoCallback(); |
| wifiNetworkCallback.assertNoCallback(); |
| vpnNetworkCallback.expectCallback(CallbackEntry.LOST, mMockVpn); |
| defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn); |
| systemDefaultCallback.assertNoCallback(); |
| assertEquals(null, mCm.getActiveNetwork()); |
| |
| mCm.unregisterNetworkCallback(genericNetworkCallback); |
| mCm.unregisterNetworkCallback(wifiNetworkCallback); |
| mCm.unregisterNetworkCallback(vpnNetworkCallback); |
| mCm.unregisterNetworkCallback(defaultCallback); |
| mCm.unregisterNetworkCallback(systemDefaultCallback); |
| } |
| |
| @Test |
| public void testVpnWithoutInternet() throws Exception { |
| final int uid = Process.myUid(); |
| |
| final TestNetworkCallback defaultCallback = new TestNetworkCallback(); |
| mCm.registerDefaultNetworkCallback(defaultCallback); |
| |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(true); |
| |
| defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); |
| assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| |
| mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */, |
| false /* isStrictMode */); |
| assertUidRangesUpdatedForMyUid(true); |
| |
| defaultCallback.assertNoCallback(); |
| assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| |
| mMockVpn.disconnect(); |
| defaultCallback.assertNoCallback(); |
| |
| mCm.unregisterNetworkCallback(defaultCallback); |
| } |
| |
| @Test |
| public void testVpnWithInternet() throws Exception { |
| final int uid = Process.myUid(); |
| |
| final TestNetworkCallback defaultCallback = new TestNetworkCallback(); |
| mCm.registerDefaultNetworkCallback(defaultCallback); |
| |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(true); |
| |
| defaultCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); |
| assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| |
| mMockVpn.establishForMyUid(true /* validated */, true /* hasInternet */, |
| false /* isStrictMode */); |
| assertUidRangesUpdatedForMyUid(true); |
| |
| defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); |
| assertEquals(defaultCallback.getLastAvailableNetwork(), mCm.getActiveNetwork()); |
| |
| mMockVpn.disconnect(); |
| defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn); |
| defaultCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); |
| |
| mCm.unregisterNetworkCallback(defaultCallback); |
| } |
| |
| @Test |
| public void testVpnUnvalidated() throws Exception { |
| final TestNetworkCallback callback = new TestNetworkCallback(); |
| mCm.registerDefaultNetworkCallback(callback); |
| |
| // Bring up Ethernet. |
| mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET); |
| mEthernetNetworkAgent.connect(true); |
| callback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent); |
| callback.assertNoCallback(); |
| |
| // Bring up a VPN that has the INTERNET capability, initially unvalidated. |
| mMockVpn.establishForMyUid(false /* validated */, true /* hasInternet */, |
| false /* isStrictMode */); |
| assertUidRangesUpdatedForMyUid(true); |
| |
| // Even though the VPN is unvalidated, it becomes the default network for our app. |
| callback.expectAvailableCallbacksUnvalidated(mMockVpn); |
| callback.assertNoCallback(); |
| |
| assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); |
| |
| NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); |
| assertFalse(nc.hasCapability(NET_CAPABILITY_VALIDATED)); |
| assertTrue(nc.hasCapability(NET_CAPABILITY_INTERNET)); |
| |
| assertFalse(NetworkMonitorUtils.isValidationRequired( |
| mMockVpn.getAgent().getNetworkCapabilities())); |
| assertTrue(NetworkMonitorUtils.isPrivateDnsValidationRequired( |
| mMockVpn.getAgent().getNetworkCapabilities())); |
| |
| // Pretend that the VPN network validates. |
| mMockVpn.getAgent().setNetworkValid(false /* isStrictMode */); |
| mMockVpn.getAgent().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. |
| callback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mMockVpn); |
| callback.assertNoCallback(); |
| |
| mMockVpn.disconnect(); |
| callback.expectCallback(CallbackEntry.LOST, mMockVpn); |
| callback.expectAvailableCallbacksValidated(mEthernetNetworkAgent); |
| } |
| |
| @Test |
| public void testVpnStartsWithUnderlyingCaps() throws Exception { |
| final int uid = Process.myUid(); |
| |
| final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback(); |
| final NetworkRequest vpnNetworkRequest = new NetworkRequest.Builder() |
| .removeCapability(NET_CAPABILITY_NOT_VPN) |
| .addTransportType(TRANSPORT_VPN) |
| .build(); |
| mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback); |
| vpnNetworkCallback.assertNoCallback(); |
| |
| // Connect cell. It will become the default network, and in the absence of setting |
| // underlying networks explicitly it will become the sole underlying network for the vpn. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED); |
| mCellNetworkAgent.connect(true); |
| |
| mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */, |
| false /* isStrictMode */); |
| assertUidRangesUpdatedForMyUid(true); |
| |
| vpnNetworkCallback.expectAvailableCallbacks(mMockVpn.getNetwork(), |
| false /* suspended */, false /* validated */, false /* blocked */, TIMEOUT_MS); |
| vpnNetworkCallback.expectCapabilitiesThat(mMockVpn.getNetwork(), TIMEOUT_MS, |
| nc -> nc.hasCapability(NET_CAPABILITY_VALIDATED)); |
| |
| final NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); |
| assertTrue(nc.hasTransport(TRANSPORT_VPN)); |
| assertTrue(nc.hasTransport(TRANSPORT_CELLULAR)); |
| assertFalse(nc.hasTransport(TRANSPORT_WIFI)); |
| assertTrue(nc.hasCapability(NET_CAPABILITY_VALIDATED)); |
| assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED)); |
| assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); |
| |
| assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE); |
| } |
| |
| private void assertDefaultNetworkCapabilities(int userId, NetworkAgentWrapper... networks) { |
| final NetworkCapabilities[] defaultCaps = mService.getDefaultNetworkCapabilitiesForUser( |
| userId, "com.android.calling.package", "com.test"); |
| final String defaultCapsString = Arrays.toString(defaultCaps); |
| assertEquals(defaultCapsString, defaultCaps.length, networks.length); |
| final Set<NetworkCapabilities> defaultCapsSet = new ArraySet<>(defaultCaps); |
| for (NetworkAgentWrapper network : networks) { |
| final NetworkCapabilities nc = mCm.getNetworkCapabilities(network.getNetwork()); |
| final String msg = "Did not find " + nc + " in " + Arrays.toString(defaultCaps); |
| assertTrue(msg, defaultCapsSet.contains(nc)); |
| } |
| } |
| |
| @Test |
| public void testVpnSetUnderlyingNetworks() throws Exception { |
| final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback(); |
| final NetworkRequest vpnNetworkRequest = new NetworkRequest.Builder() |
| .removeCapability(NET_CAPABILITY_NOT_VPN) |
| .addTransportType(TRANSPORT_VPN) |
| .build(); |
| NetworkCapabilities nc; |
| mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback); |
| vpnNetworkCallback.assertNoCallback(); |
| |
| mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */, |
| false /* isStrictMode */); |
| assertUidRangesUpdatedForMyUid(true); |
| |
| vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn); |
| nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); |
| assertTrue(nc.hasTransport(TRANSPORT_VPN)); |
| assertFalse(nc.hasTransport(TRANSPORT_CELLULAR)); |
| assertFalse(nc.hasTransport(TRANSPORT_WIFI)); |
| // For safety reasons a VPN without underlying networks is considered metered. |
| assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED)); |
| // A VPN without underlying networks is not suspended. |
| assertTrue(nc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); |
| assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE); |
| |
| final int userId = UserHandle.getUserId(Process.myUid()); |
| assertDefaultNetworkCapabilities(userId /* no networks */); |
| |
| // Connect cell and use it as an underlying network. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED); |
| mCellNetworkAgent.connect(true); |
| |
| mMockVpn.setUnderlyingNetworks( |
| new Network[] { mCellNetworkAgent.getNetwork() }); |
| |
| vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, |
| (caps) -> caps.hasTransport(TRANSPORT_VPN) |
| && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) |
| && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) |
| && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); |
| assertDefaultNetworkCapabilities(userId, mCellNetworkAgent); |
| |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); |
| mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED); |
| mWiFiNetworkAgent.connect(true); |
| |
| mMockVpn.setUnderlyingNetworks( |
| new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() }); |
| |
| vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, |
| (caps) -> caps.hasTransport(TRANSPORT_VPN) |
| && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) |
| && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) |
| && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); |
| assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent); |
| |
| // Don't disconnect, but note the VPN is not using wifi any more. |
| mMockVpn.setUnderlyingNetworks( |
| new Network[] { mCellNetworkAgent.getNetwork() }); |
| |
| vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, |
| (caps) -> caps.hasTransport(TRANSPORT_VPN) |
| && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) |
| && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) |
| && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); |
| // The return value of getDefaultNetworkCapabilitiesForUser always includes the default |
| // network (wifi) as well as the underlying networks (cell). |
| assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent); |
| |
| // Remove NOT_SUSPENDED from the only network and observe VPN is now suspended. |
| mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_SUSPENDED); |
| vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, |
| (caps) -> caps.hasTransport(TRANSPORT_VPN) |
| && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) |
| && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) |
| && !caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); |
| vpnNetworkCallback.expectCallback(CallbackEntry.SUSPENDED, mMockVpn); |
| |
| // Add NOT_SUSPENDED again and observe VPN is no longer suspended. |
| mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_SUSPENDED); |
| vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, |
| (caps) -> caps.hasTransport(TRANSPORT_VPN) |
| && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) |
| && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) |
| && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); |
| vpnNetworkCallback.expectCallback(CallbackEntry.RESUMED, mMockVpn); |
| |
| // Use Wifi but not cell. Note the VPN is now unmetered and not suspended. |
| mMockVpn.setUnderlyingNetworks( |
| new Network[] { mWiFiNetworkAgent.getNetwork() }); |
| |
| vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, |
| (caps) -> caps.hasTransport(TRANSPORT_VPN) |
| && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) |
| && caps.hasCapability(NET_CAPABILITY_NOT_METERED) |
| && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); |
| assertDefaultNetworkCapabilities(userId, mWiFiNetworkAgent); |
| |
| // Use both again. |
| mMockVpn.setUnderlyingNetworks( |
| new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() }); |
| |
| vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, |
| (caps) -> caps.hasTransport(TRANSPORT_VPN) |
| && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) |
| && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) |
| && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); |
| assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent); |
| |
| // Cell is suspended again. As WiFi is not, this should not cause a callback. |
| mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_SUSPENDED); |
| vpnNetworkCallback.assertNoCallback(); |
| |
| // Stop using WiFi. The VPN is suspended again. |
| mMockVpn.setUnderlyingNetworks( |
| new Network[] { mCellNetworkAgent.getNetwork() }); |
| vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, |
| (caps) -> caps.hasTransport(TRANSPORT_VPN) |
| && caps.hasTransport(TRANSPORT_CELLULAR) |
| && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) |
| && !caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); |
| vpnNetworkCallback.expectCallback(CallbackEntry.SUSPENDED, mMockVpn); |
| assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent); |
| |
| // Use both again. |
| mMockVpn.setUnderlyingNetworks( |
| new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() }); |
| |
| vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, |
| (caps) -> caps.hasTransport(TRANSPORT_VPN) |
| && caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) |
| && !caps.hasCapability(NET_CAPABILITY_NOT_METERED) |
| && caps.hasCapability(NET_CAPABILITY_NOT_SUSPENDED)); |
| vpnNetworkCallback.expectCallback(CallbackEntry.RESUMED, mMockVpn); |
| assertDefaultNetworkCapabilities(userId, mCellNetworkAgent, mWiFiNetworkAgent); |
| |
| // Disconnect cell. Receive update without even removing the dead network from the |
| // underlying networks – it's dead anyway. Not metered any more. |
| mCellNetworkAgent.disconnect(); |
| vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, |
| (caps) -> caps.hasTransport(TRANSPORT_VPN) |
| && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) |
| && caps.hasCapability(NET_CAPABILITY_NOT_METERED)); |
| assertDefaultNetworkCapabilities(userId, mWiFiNetworkAgent); |
| |
| // Disconnect wifi too. No underlying networks means this is now metered. |
| mWiFiNetworkAgent.disconnect(); |
| vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, |
| (caps) -> caps.hasTransport(TRANSPORT_VPN) |
| && !caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) |
| && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)); |
| // When a network disconnects, the callbacks are fired before all state is updated, so for a |
| // short time, synchronous calls will behave as if the network is still connected. Wait for |
| // things to settle. |
| waitForIdle(); |
| assertDefaultNetworkCapabilities(userId /* no networks */); |
| |
| mMockVpn.disconnect(); |
| } |
| |
| @Test |
| public void testNullUnderlyingNetworks() throws Exception { |
| final int uid = Process.myUid(); |
| |
| final TestNetworkCallback vpnNetworkCallback = new TestNetworkCallback(); |
| final NetworkRequest vpnNetworkRequest = new NetworkRequest.Builder() |
| .removeCapability(NET_CAPABILITY_NOT_VPN) |
| .addTransportType(TRANSPORT_VPN) |
| .build(); |
| NetworkCapabilities nc; |
| mCm.registerNetworkCallback(vpnNetworkRequest, vpnNetworkCallback); |
| vpnNetworkCallback.assertNoCallback(); |
| |
| mMockVpn.establishForMyUid(true /* validated */, false /* hasInternet */, |
| false /* isStrictMode */); |
| assertUidRangesUpdatedForMyUid(true); |
| |
| vpnNetworkCallback.expectAvailableThenValidatedCallbacks(mMockVpn); |
| nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); |
| assertTrue(nc.hasTransport(TRANSPORT_VPN)); |
| assertFalse(nc.hasTransport(TRANSPORT_CELLULAR)); |
| assertFalse(nc.hasTransport(TRANSPORT_WIFI)); |
| // By default, VPN is set to track default network (i.e. its underlying networks is null). |
| // In case of no default network, VPN is considered metered. |
| assertFalse(nc.hasCapability(NET_CAPABILITY_NOT_METERED)); |
| assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE); |
| |
| // Connect to Cell; Cell is the default network. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| |
| vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, |
| (caps) -> caps.hasTransport(TRANSPORT_VPN) |
| && caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) |
| && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)); |
| |
| // Connect to WiFi; WiFi is the new default. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); |
| mWiFiNetworkAgent.connect(true); |
| |
| vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, |
| (caps) -> caps.hasTransport(TRANSPORT_VPN) |
| && !caps.hasTransport(TRANSPORT_CELLULAR) && caps.hasTransport(TRANSPORT_WIFI) |
| && caps.hasCapability(NET_CAPABILITY_NOT_METERED)); |
| |
| // Disconnect Cell. The default network did not change, so there shouldn't be any changes in |
| // the capabilities. |
| mCellNetworkAgent.disconnect(); |
| |
| // Disconnect wifi too. Now we have no default network. |
| mWiFiNetworkAgent.disconnect(); |
| |
| vpnNetworkCallback.expectCapabilitiesThat(mMockVpn, |
| (caps) -> caps.hasTransport(TRANSPORT_VPN) |
| && !caps.hasTransport(TRANSPORT_CELLULAR) && !caps.hasTransport(TRANSPORT_WIFI) |
| && !caps.hasCapability(NET_CAPABILITY_NOT_METERED)); |
| |
| mMockVpn.disconnect(); |
| } |
| |
| @Test |
| public void testRestrictedProfileAffectsVpnUidRanges() throws Exception { |
| // NETWORK_SETTINGS is necessary to see the UID ranges in NetworkCapabilities. |
| mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED); |
| |
| final NetworkRequest request = new NetworkRequest.Builder() |
| .removeCapability(NET_CAPABILITY_NOT_VPN) |
| .build(); |
| final TestNetworkCallback callback = new TestNetworkCallback(); |
| mCm.registerNetworkCallback(request, callback); |
| |
| // Bring up a VPN |
| mMockVpn.establishForMyUid(); |
| assertUidRangesUpdatedForMyUid(true); |
| callback.expectAvailableThenValidatedCallbacks(mMockVpn); |
| callback.assertNoCallback(); |
| |
| final int uid = Process.myUid(); |
| NetworkCapabilities nc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); |
| assertNotNull("nc=" + nc, nc.getUids()); |
| assertEquals(nc.getUids(), UidRange.toIntRanges(uidRangesForUids(uid))); |
| assertVpnTransportInfo(nc, VpnManager.TYPE_VPN_SERVICE); |
| |
| // Set an underlying network and expect to see the VPN transports change. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(true); |
| callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| callback.expectCapabilitiesThat(mMockVpn, (caps) |
| -> caps.hasTransport(TRANSPORT_VPN) |
| && caps.hasTransport(TRANSPORT_WIFI)); |
| callback.expectCapabilitiesThat(mWiFiNetworkAgent, (caps) |
| -> caps.hasCapability(NET_CAPABILITY_VALIDATED)); |
| |
| when(mPackageManager.getPackageUidAsUser(ALWAYS_ON_PACKAGE, RESTRICTED_USER)) |
| .thenReturn(UserHandle.getUid(RESTRICTED_USER, VPN_UID)); |
| |
| final Intent addedIntent = new Intent(ACTION_USER_ADDED); |
| addedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(RESTRICTED_USER)); |
| addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, RESTRICTED_USER); |
| |
| // Send a USER_ADDED broadcast for it. |
| processBroadcast(addedIntent); |
| |
| // Expect that the VPN UID ranges contain both |uid| and the UID range for the newly-added |
| // restricted user. |
| final UidRange rRange = UidRange.createForUser(UserHandle.of(RESTRICTED_USER)); |
| final Range<Integer> restrictUidRange = new Range<Integer>(rRange.start, rRange.stop); |
| final Range<Integer> singleUidRange = new Range<Integer>(uid, uid); |
| callback.expectCapabilitiesThat(mMockVpn, (caps) |
| -> caps.getUids().size() == 2 |
| && caps.getUids().contains(singleUidRange) |
| && caps.getUids().contains(restrictUidRange) |
| && caps.hasTransport(TRANSPORT_VPN) |
| && caps.hasTransport(TRANSPORT_WIFI)); |
| |
| // Change the VPN's capabilities somehow (specifically, disconnect wifi). |
| mWiFiNetworkAgent.disconnect(); |
| callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| callback.expectCapabilitiesThat(mMockVpn, (caps) |
| -> caps.getUids().size() == 2 |
| && caps.getUids().contains(singleUidRange) |
| && caps.getUids().contains(restrictUidRange) |
| && caps.hasTransport(TRANSPORT_VPN) |
| && !caps.hasTransport(TRANSPORT_WIFI)); |
| |
| // Send a USER_REMOVED broadcast and expect to lose the UID range for the restricted user. |
| final Intent removedIntent = new Intent(ACTION_USER_REMOVED); |
| removedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(RESTRICTED_USER)); |
| removedIntent.putExtra(Intent.EXTRA_USER_HANDLE, RESTRICTED_USER); |
| processBroadcast(removedIntent); |
| |
| // Expect that the VPN gains the UID range for the restricted user, and that the capability |
| // change made just before that (i.e., loss of TRANSPORT_WIFI) is preserved. |
| callback.expectCapabilitiesThat(mMockVpn, (caps) |
| -> caps.getUids().size() == 1 |
| && caps.getUids().contains(singleUidRange) |
| && caps.hasTransport(TRANSPORT_VPN) |
| && !caps.hasTransport(TRANSPORT_WIFI)); |
| } |
| |
| @Test |
| public void testLockdownVpnWithRestrictedProfiles() throws Exception { |
| // For ConnectivityService#setAlwaysOnVpnPackage. |
| mServiceContext.setPermission( |
| Manifest.permission.CONTROL_ALWAYS_ON_VPN, PERMISSION_GRANTED); |
| // For call Vpn#setAlwaysOnPackage. |
| mServiceContext.setPermission( |
| Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED); |
| // Necessary to see the UID ranges in NetworkCapabilities. |
| mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED); |
| |
| final NetworkRequest request = new NetworkRequest.Builder() |
| .removeCapability(NET_CAPABILITY_NOT_VPN) |
| .build(); |
| final TestNetworkCallback callback = new TestNetworkCallback(); |
| mCm.registerNetworkCallback(request, callback); |
| |
| final int uid = Process.myUid(); |
| |
| // Connect wifi and check that UIDs in the main and restricted profiles have network access. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(true /* validated */); |
| final int restrictedUid = UserHandle.getUid(RESTRICTED_USER, 42 /* appId */); |
| assertNotNull(mCm.getActiveNetworkForUid(uid)); |
| assertNotNull(mCm.getActiveNetworkForUid(restrictedUid)); |
| |
| // Enable always-on VPN lockdown. The main user loses network access because no VPN is up. |
| final ArrayList<String> allowList = new ArrayList<>(); |
| mVpnManagerService.setAlwaysOnVpnPackage(PRIMARY_USER, ALWAYS_ON_PACKAGE, |
| true /* lockdown */, allowList); |
| waitForIdle(); |
| assertNull(mCm.getActiveNetworkForUid(uid)); |
| // This is arguably overspecified: a UID that is not running doesn't have an active network. |
| // But it's useful to check that non-default users do not lose network access, and to prove |
| // that the loss of connectivity below is indeed due to the restricted profile coming up. |
| assertNotNull(mCm.getActiveNetworkForUid(restrictedUid)); |
| |
| // Start the restricted profile, and check that the UID within it loses network access. |
| when(mPackageManager.getPackageUidAsUser(ALWAYS_ON_PACKAGE, RESTRICTED_USER)) |
| .thenReturn(UserHandle.getUid(RESTRICTED_USER, VPN_UID)); |
| when(mUserManager.getAliveUsers()).thenReturn(Arrays.asList(PRIMARY_USER_INFO, |
| RESTRICTED_USER_INFO)); |
| // TODO: check that VPN app within restricted profile still has access, etc. |
| final Intent addedIntent = new Intent(ACTION_USER_ADDED); |
| addedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(RESTRICTED_USER)); |
| addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, RESTRICTED_USER); |
| processBroadcast(addedIntent); |
| assertNull(mCm.getActiveNetworkForUid(uid)); |
| assertNull(mCm.getActiveNetworkForUid(restrictedUid)); |
| |
| // Stop the restricted profile, and check that the UID within it has network access again. |
| when(mUserManager.getAliveUsers()).thenReturn(Arrays.asList(PRIMARY_USER_INFO)); |
| |
| // Send a USER_REMOVED broadcast and expect to lose the UID range for the restricted user. |
| final Intent removedIntent = new Intent(ACTION_USER_REMOVED); |
| removedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(RESTRICTED_USER)); |
| removedIntent.putExtra(Intent.EXTRA_USER_HANDLE, RESTRICTED_USER); |
| processBroadcast(removedIntent); |
| assertNull(mCm.getActiveNetworkForUid(uid)); |
| assertNotNull(mCm.getActiveNetworkForUid(restrictedUid)); |
| |
| mVpnManagerService.setAlwaysOnVpnPackage(PRIMARY_USER, null, false /* lockdown */, |
| allowList); |
| waitForIdle(); |
| } |
| |
| @Test |
| public void testIsActiveNetworkMeteredOverWifi() throws Exception { |
| // Returns true by default when no network is available. |
| assertTrue(mCm.isActiveNetworkMetered()); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); |
| mWiFiNetworkAgent.connect(true); |
| waitForIdle(); |
| |
| assertFalse(mCm.isActiveNetworkMetered()); |
| } |
| |
| @Test |
| public void testIsActiveNetworkMeteredOverCell() throws Exception { |
| // Returns true by default when no network is available. |
| assertTrue(mCm.isActiveNetworkMetered()); |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED); |
| mCellNetworkAgent.connect(true); |
| waitForIdle(); |
| |
| assertTrue(mCm.isActiveNetworkMetered()); |
| } |
| |
| @Test |
| public void testIsActiveNetworkMeteredOverVpnTrackingPlatformDefault() throws Exception { |
| // Returns true by default when no network is available. |
| assertTrue(mCm.isActiveNetworkMetered()); |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED); |
| mCellNetworkAgent.connect(true); |
| waitForIdle(); |
| assertTrue(mCm.isActiveNetworkMetered()); |
| |
| // Connect VPN network. By default it is using current default network (Cell). |
| mMockVpn.establishForMyUid(); |
| assertUidRangesUpdatedForMyUid(true); |
| |
| // Ensure VPN is now the active network. |
| assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); |
| |
| // Expect VPN to be metered. |
| assertTrue(mCm.isActiveNetworkMetered()); |
| |
| // Connect WiFi. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); |
| mWiFiNetworkAgent.connect(true); |
| waitForIdle(); |
| // VPN should still be the active network. |
| assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); |
| |
| // Expect VPN to be unmetered as it should now be using WiFi (new default). |
| assertFalse(mCm.isActiveNetworkMetered()); |
| |
| // Disconnecting Cell should not affect VPN's meteredness. |
| mCellNetworkAgent.disconnect(); |
| waitForIdle(); |
| |
| assertFalse(mCm.isActiveNetworkMetered()); |
| |
| // Disconnect WiFi; Now there is no platform default network. |
| mWiFiNetworkAgent.disconnect(); |
| waitForIdle(); |
| |
| // VPN without any underlying networks is treated as metered. |
| assertTrue(mCm.isActiveNetworkMetered()); |
| |
| mMockVpn.disconnect(); |
| } |
| |
| @Test |
| public void testIsActiveNetworkMeteredOverVpnSpecifyingUnderlyingNetworks() throws Exception { |
| // Returns true by default when no network is available. |
| assertTrue(mCm.isActiveNetworkMetered()); |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED); |
| mCellNetworkAgent.connect(true); |
| waitForIdle(); |
| assertTrue(mCm.isActiveNetworkMetered()); |
| |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); |
| mWiFiNetworkAgent.connect(true); |
| waitForIdle(); |
| assertFalse(mCm.isActiveNetworkMetered()); |
| |
| // Connect VPN network. |
| mMockVpn.establishForMyUid(); |
| assertUidRangesUpdatedForMyUid(true); |
| |
| // Ensure VPN is now the active network. |
| assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); |
| // VPN is using Cell |
| mMockVpn.setUnderlyingNetworks( |
| new Network[] { mCellNetworkAgent.getNetwork() }); |
| waitForIdle(); |
| |
| // Expect VPN to be metered. |
| assertTrue(mCm.isActiveNetworkMetered()); |
| |
| // VPN is now using WiFi |
| mMockVpn.setUnderlyingNetworks( |
| new Network[] { mWiFiNetworkAgent.getNetwork() }); |
| waitForIdle(); |
| |
| // Expect VPN to be unmetered |
| assertFalse(mCm.isActiveNetworkMetered()); |
| |
| // VPN is using Cell | WiFi. |
| mMockVpn.setUnderlyingNetworks( |
| new Network[] { mCellNetworkAgent.getNetwork(), mWiFiNetworkAgent.getNetwork() }); |
| waitForIdle(); |
| |
| // Expect VPN to be metered. |
| assertTrue(mCm.isActiveNetworkMetered()); |
| |
| // VPN is using WiFi | Cell. |
| mMockVpn.setUnderlyingNetworks( |
| new Network[] { mWiFiNetworkAgent.getNetwork(), mCellNetworkAgent.getNetwork() }); |
| waitForIdle(); |
| |
| // Order should not matter and VPN should still be metered. |
| assertTrue(mCm.isActiveNetworkMetered()); |
| |
| // VPN is not using any underlying networks. |
| mMockVpn.setUnderlyingNetworks(new Network[0]); |
| waitForIdle(); |
| |
| // VPN without underlying networks is treated as metered. |
| assertTrue(mCm.isActiveNetworkMetered()); |
| |
| mMockVpn.disconnect(); |
| } |
| |
| @Test |
| public void testIsActiveNetworkMeteredOverAlwaysMeteredVpn() throws Exception { |
| // Returns true by default when no network is available. |
| assertTrue(mCm.isActiveNetworkMetered()); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); |
| mWiFiNetworkAgent.connect(true); |
| waitForIdle(); |
| assertFalse(mCm.isActiveNetworkMetered()); |
| |
| // Connect VPN network. |
| mMockVpn.registerAgent(true /* isAlwaysMetered */, uidRangesForUids(Process.myUid()), |
| new LinkProperties()); |
| mMockVpn.connect(true); |
| waitForIdle(); |
| assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); |
| |
| // VPN is tracking current platform default (WiFi). |
| mMockVpn.setUnderlyingNetworks(null); |
| waitForIdle(); |
| |
| // Despite VPN using WiFi (which is unmetered), VPN itself is marked as always metered. |
| assertTrue(mCm.isActiveNetworkMetered()); |
| |
| |
| // VPN explicitly declares WiFi as its underlying network. |
| mMockVpn.setUnderlyingNetworks( |
| new Network[] { mWiFiNetworkAgent.getNetwork() }); |
| waitForIdle(); |
| |
| // Doesn't really matter whether VPN declares its underlying networks explicitly. |
| assertTrue(mCm.isActiveNetworkMetered()); |
| |
| // With WiFi lost, VPN is basically without any underlying networks. And in that case it is |
| // anyways suppose to be metered. |
| mWiFiNetworkAgent.disconnect(); |
| waitForIdle(); |
| |
| assertTrue(mCm.isActiveNetworkMetered()); |
| |
| mMockVpn.disconnect(); |
| } |
| |
| private class DetailedBlockedStatusCallback extends TestNetworkCallback { |
| public void expectAvailableThenValidatedCallbacks(HasNetwork n, int blockedStatus) { |
| super.expectAvailableThenValidatedCallbacks(n.getNetwork(), blockedStatus, TIMEOUT_MS); |
| } |
| public void expectBlockedStatusCallback(HasNetwork n, int blockedStatus) { |
| // This doesn't work: |
| // super.expectBlockedStatusCallback(blockedStatus, n.getNetwork()); |
| super.expectBlockedStatusCallback(blockedStatus, n.getNetwork(), TIMEOUT_MS); |
| } |
| public void onBlockedStatusChanged(Network network, int blockedReasons) { |
| getHistory().add(new CallbackEntry.BlockedStatusInt(network, blockedReasons)); |
| } |
| } |
| |
| @Test |
| public void testNetworkBlockedStatus() throws Exception { |
| final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); |
| final NetworkRequest cellRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_CELLULAR) |
| .build(); |
| mCm.registerNetworkCallback(cellRequest, cellNetworkCallback); |
| final DetailedBlockedStatusCallback detailedCallback = new DetailedBlockedStatusCallback(); |
| mCm.registerNetworkCallback(cellRequest, detailedCallback); |
| |
| mockUidNetworkingBlocked(); |
| |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| detailedCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent, |
| BLOCKED_REASON_NONE); |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); |
| assertExtraInfoFromCmPresent(mCellNetworkAgent); |
| |
| setBlockedReasonChanged(BLOCKED_REASON_BATTERY_SAVER); |
| cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent); |
| detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent, |
| BLOCKED_REASON_BATTERY_SAVER); |
| assertNull(mCm.getActiveNetwork()); |
| assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); |
| assertExtraInfoFromCmBlocked(mCellNetworkAgent); |
| |
| // If blocked state does not change but blocked reason does, the boolean callback is called. |
| // TODO: investigate de-duplicating. |
| setBlockedReasonChanged(BLOCKED_METERED_REASON_USER_RESTRICTED); |
| cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent); |
| detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent, |
| BLOCKED_METERED_REASON_USER_RESTRICTED); |
| |
| setBlockedReasonChanged(BLOCKED_REASON_NONE); |
| cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); |
| detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent, BLOCKED_REASON_NONE); |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); |
| assertExtraInfoFromCmPresent(mCellNetworkAgent); |
| |
| setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER); |
| cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent); |
| detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent, |
| BLOCKED_METERED_REASON_DATA_SAVER); |
| assertNull(mCm.getActiveNetwork()); |
| assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); |
| assertExtraInfoFromCmBlocked(mCellNetworkAgent); |
| |
| // Restrict the network based on UID rule and NOT_METERED capability change. |
| mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); |
| cellNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mCellNetworkAgent); |
| cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); |
| detailedCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mCellNetworkAgent); |
| detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent, BLOCKED_REASON_NONE); |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); |
| assertExtraInfoFromCmPresent(mCellNetworkAgent); |
| |
| mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED); |
| cellNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED, |
| mCellNetworkAgent); |
| cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent); |
| detailedCallback.expectCapabilitiesWithout(NET_CAPABILITY_NOT_METERED, |
| mCellNetworkAgent); |
| detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent, |
| BLOCKED_METERED_REASON_DATA_SAVER); |
| assertNull(mCm.getActiveNetwork()); |
| assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); |
| assertExtraInfoFromCmBlocked(mCellNetworkAgent); |
| |
| setBlockedReasonChanged(BLOCKED_REASON_NONE); |
| cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); |
| detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent, BLOCKED_REASON_NONE); |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); |
| assertExtraInfoFromCmPresent(mCellNetworkAgent); |
| |
| setBlockedReasonChanged(BLOCKED_REASON_NONE); |
| cellNetworkCallback.assertNoCallback(); |
| detailedCallback.assertNoCallback(); |
| |
| // Restrict background data. Networking is not blocked because the network is unmetered. |
| setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER); |
| cellNetworkCallback.expectBlockedStatusCallback(true, mCellNetworkAgent); |
| detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent, |
| BLOCKED_METERED_REASON_DATA_SAVER); |
| assertNull(mCm.getActiveNetwork()); |
| assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); |
| assertExtraInfoFromCmBlocked(mCellNetworkAgent); |
| setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER); |
| cellNetworkCallback.assertNoCallback(); |
| |
| setBlockedReasonChanged(BLOCKED_REASON_NONE); |
| cellNetworkCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); |
| detailedCallback.expectBlockedStatusCallback(mCellNetworkAgent, BLOCKED_REASON_NONE); |
| assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); |
| assertExtraInfoFromCmPresent(mCellNetworkAgent); |
| |
| setBlockedReasonChanged(BLOCKED_REASON_NONE); |
| cellNetworkCallback.assertNoCallback(); |
| detailedCallback.assertNoCallback(); |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); |
| assertExtraInfoFromCmPresent(mCellNetworkAgent); |
| |
| mCm.unregisterNetworkCallback(cellNetworkCallback); |
| } |
| |
| @Test |
| public void testNetworkBlockedStatusBeforeAndAfterConnect() throws Exception { |
| final TestNetworkCallback defaultCallback = new TestNetworkCallback(); |
| mCm.registerDefaultNetworkCallback(defaultCallback); |
| mockUidNetworkingBlocked(); |
| |
| // No Networkcallbacks invoked before any network is active. |
| setBlockedReasonChanged(BLOCKED_REASON_BATTERY_SAVER); |
| setBlockedReasonChanged(BLOCKED_REASON_NONE); |
| setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER); |
| defaultCallback.assertNoCallback(); |
| |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); |
| defaultCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mCellNetworkAgent); |
| |
| // Allow to use the network after switching to NOT_METERED network. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); |
| mWiFiNetworkAgent.connect(true); |
| defaultCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); |
| |
| // Switch to METERED network. Restrict the use of the network. |
| mWiFiNetworkAgent.disconnect(); |
| defaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| defaultCallback.expectAvailableCallbacksValidatedAndBlocked(mCellNetworkAgent); |
| |
| // Network becomes NOT_METERED. |
| mCellNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); |
| defaultCallback.expectCapabilitiesWith(NET_CAPABILITY_NOT_METERED, mCellNetworkAgent); |
| defaultCallback.expectBlockedStatusCallback(false, mCellNetworkAgent); |
| |
| // Verify there's no Networkcallbacks invoked after data saver on/off. |
| setBlockedReasonChanged(BLOCKED_METERED_REASON_DATA_SAVER); |
| setBlockedReasonChanged(BLOCKED_REASON_NONE); |
| defaultCallback.assertNoCallback(); |
| |
| mCellNetworkAgent.disconnect(); |
| defaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| defaultCallback.assertNoCallback(); |
| |
| mCm.unregisterNetworkCallback(defaultCallback); |
| } |
| |
| private void expectNetworkRejectNonSecureVpn(InOrder inOrder, boolean add, |
| UidRangeParcel... expected) throws Exception { |
| inOrder.verify(mMockNetd).networkRejectNonSecureVpn(eq(add), aryEq(expected)); |
| } |
| |
| private void checkNetworkInfo(NetworkInfo ni, int type, DetailedState state) { |
| assertNotNull(ni); |
| assertEquals(type, ni.getType()); |
| assertEquals(ConnectivityManager.getNetworkTypeName(type), state, ni.getDetailedState()); |
| if (state == DetailedState.CONNECTED || state == DetailedState.SUSPENDED) { |
| assertNotNull(ni.getExtraInfo()); |
| } else { |
| // Technically speaking, a network that's in CONNECTING state will generally have a |
| // non-null extraInfo. This doesn't actually happen in this test because it never calls |
| // a legacy API while a network is connecting. When a network is in CONNECTING state |
| // because of legacy lockdown VPN, its extraInfo is always null. |
| assertNull(ni.getExtraInfo()); |
| } |
| } |
| |
| private void assertActiveNetworkInfo(int type, DetailedState state) { |
| checkNetworkInfo(mCm.getActiveNetworkInfo(), type, state); |
| } |
| private void assertNetworkInfo(int type, DetailedState state) { |
| checkNetworkInfo(mCm.getNetworkInfo(type), type, state); |
| } |
| |
| private void assertExtraInfoFromCm(TestNetworkAgentWrapper network, boolean present) { |
| final NetworkInfo niForNetwork = mCm.getNetworkInfo(network.getNetwork()); |
| final NetworkInfo niForType = mCm.getNetworkInfo(network.getLegacyType()); |
| if (present) { |
| assertEquals(network.getExtraInfo(), niForNetwork.getExtraInfo()); |
| assertEquals(network.getExtraInfo(), niForType.getExtraInfo()); |
| } else { |
| assertNull(niForNetwork.getExtraInfo()); |
| assertNull(niForType.getExtraInfo()); |
| } |
| } |
| |
| private void assertExtraInfoFromCmBlocked(TestNetworkAgentWrapper network) { |
| assertExtraInfoFromCm(network, false); |
| } |
| |
| private void assertExtraInfoFromCmPresent(TestNetworkAgentWrapper network) { |
| assertExtraInfoFromCm(network, true); |
| } |
| |
| // Checks that each of the |agents| receive a blocked status change callback with the specified |
| // |blocked| value, in any order. This is needed because when an event affects multiple |
| // networks, ConnectivityService does not guarantee the order in which callbacks are fired. |
| private void assertBlockedCallbackInAnyOrder(TestNetworkCallback callback, boolean blocked, |
| TestNetworkAgentWrapper... agents) { |
| final List<Network> expectedNetworks = Arrays.asList(agents).stream() |
| .map((agent) -> agent.getNetwork()) |
| .collect(Collectors.toList()); |
| |
| // Expect exactly one blocked callback for each agent. |
| for (int i = 0; i < agents.length; i++) { |
| CallbackEntry e = callback.expectCallbackThat(TIMEOUT_MS, (c) -> |
| c instanceof CallbackEntry.BlockedStatus |
| && ((CallbackEntry.BlockedStatus) c).getBlocked() == blocked); |
| Network network = e.getNetwork(); |
| assertTrue("Received unexpected blocked callback for network " + network, |
| expectedNetworks.remove(network)); |
| } |
| } |
| |
| @Test |
| public void testNetworkBlockedStatusAlwaysOnVpn() throws Exception { |
| mServiceContext.setPermission( |
| Manifest.permission.CONTROL_ALWAYS_ON_VPN, PERMISSION_GRANTED); |
| mServiceContext.setPermission( |
| Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED); |
| mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED); |
| |
| final TestNetworkCallback callback = new TestNetworkCallback(); |
| final NetworkRequest request = new NetworkRequest.Builder() |
| .removeCapability(NET_CAPABILITY_NOT_VPN) |
| .build(); |
| mCm.registerNetworkCallback(request, callback); |
| |
| final TestNetworkCallback defaultCallback = new TestNetworkCallback(); |
| mCm.registerDefaultNetworkCallback(defaultCallback); |
| |
| final TestNetworkCallback vpnUidCallback = new TestNetworkCallback(); |
| final NetworkRequest vpnUidRequest = new NetworkRequest.Builder().build(); |
| registerNetworkCallbackAsUid(vpnUidRequest, vpnUidCallback, VPN_UID); |
| |
| final TestNetworkCallback vpnUidDefaultCallback = new TestNetworkCallback(); |
| registerDefaultNetworkCallbackAsUid(vpnUidDefaultCallback, VPN_UID); |
| |
| final TestNetworkCallback vpnDefaultCallbackAsUid = new TestNetworkCallback(); |
| mCm.registerDefaultNetworkCallbackForUid(VPN_UID, vpnDefaultCallbackAsUid, |
| new Handler(ConnectivityThread.getInstanceLooper())); |
| |
| final int uid = Process.myUid(); |
| final int userId = UserHandle.getUserId(uid); |
| final ArrayList<String> allowList = new ArrayList<>(); |
| mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, |
| allowList); |
| waitForIdle(); |
| |
| UidRangeParcel firstHalf = new UidRangeParcel(1, VPN_UID - 1); |
| UidRangeParcel secondHalf = new UidRangeParcel(VPN_UID + 1, 99999); |
| InOrder inOrder = inOrder(mMockNetd); |
| expectNetworkRejectNonSecureVpn(inOrder, true, firstHalf, secondHalf); |
| |
| // Connect a network when lockdown is active, expect to see it blocked. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(false /* validated */); |
| callback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent); |
| defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent); |
| vpnUidCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| vpnUidDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| vpnDefaultCallbackAsUid.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); |
| assertNull(mCm.getActiveNetwork()); |
| assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); |
| // Mobile is BLOCKED even though it's not actually connected. |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); |
| assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); |
| |
| // Disable lockdown, expect to see the network unblocked. |
| mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); |
| callback.expectBlockedStatusCallback(false, mWiFiNetworkAgent); |
| defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent); |
| vpnUidCallback.assertNoCallback(); |
| vpnUidDefaultCallback.assertNoCallback(); |
| vpnDefaultCallbackAsUid.assertNoCallback(); |
| expectNetworkRejectNonSecureVpn(inOrder, false, firstHalf, secondHalf); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); |
| assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); |
| |
| // Add our UID to the allowlist and re-enable lockdown, expect network is not blocked. |
| allowList.add(TEST_PACKAGE_NAME); |
| mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, |
| allowList); |
| callback.assertNoCallback(); |
| defaultCallback.assertNoCallback(); |
| vpnUidCallback.assertNoCallback(); |
| vpnUidDefaultCallback.assertNoCallback(); |
| vpnDefaultCallbackAsUid.assertNoCallback(); |
| |
| // The following requires that the UID of this test package is greater than VPN_UID. This |
| // is always true in practice because a plain AOSP build with no apps installed has almost |
| // 200 packages installed. |
| final UidRangeParcel piece1 = new UidRangeParcel(1, VPN_UID - 1); |
| final UidRangeParcel piece2 = new UidRangeParcel(VPN_UID + 1, uid - 1); |
| final UidRangeParcel piece3 = new UidRangeParcel(uid + 1, 99999); |
| expectNetworkRejectNonSecureVpn(inOrder, true, piece1, piece2, piece3); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); |
| assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); |
| |
| // Connect a new network, expect it to be unblocked. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(false /* validated */); |
| callback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); |
| defaultCallback.assertNoCallback(); |
| vpnUidCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); |
| vpnUidDefaultCallback.assertNoCallback(); |
| vpnDefaultCallbackAsUid.assertNoCallback(); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); |
| // Cellular is DISCONNECTED because it's not the default and there are no requests for it. |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); |
| assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); |
| |
| // Disable lockdown, remove our UID from the allowlist, and re-enable lockdown. |
| // Everything should now be blocked. |
| mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); |
| waitForIdle(); |
| expectNetworkRejectNonSecureVpn(inOrder, false, piece1, piece2, piece3); |
| allowList.clear(); |
| mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, |
| allowList); |
| waitForIdle(); |
| expectNetworkRejectNonSecureVpn(inOrder, true, firstHalf, secondHalf); |
| defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent); |
| assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent); |
| vpnUidCallback.assertNoCallback(); |
| vpnUidDefaultCallback.assertNoCallback(); |
| vpnDefaultCallbackAsUid.assertNoCallback(); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); |
| assertNull(mCm.getActiveNetwork()); |
| assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); |
| assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); |
| |
| // Disable lockdown. Everything is unblocked. |
| mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); |
| defaultCallback.expectBlockedStatusCallback(false, mWiFiNetworkAgent); |
| assertBlockedCallbackInAnyOrder(callback, false, mWiFiNetworkAgent, mCellNetworkAgent); |
| vpnUidCallback.assertNoCallback(); |
| vpnUidDefaultCallback.assertNoCallback(); |
| vpnDefaultCallbackAsUid.assertNoCallback(); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); |
| assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); |
| |
| // Enable and disable an always-on VPN package without lockdown. Expect no changes. |
| reset(mMockNetd); |
| mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, false /* lockdown */, |
| allowList); |
| inOrder.verify(mMockNetd, never()).networkRejectNonSecureVpn(anyBoolean(), any()); |
| callback.assertNoCallback(); |
| defaultCallback.assertNoCallback(); |
| vpnUidCallback.assertNoCallback(); |
| vpnUidDefaultCallback.assertNoCallback(); |
| vpnDefaultCallbackAsUid.assertNoCallback(); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); |
| assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); |
| |
| mVpnManagerService.setAlwaysOnVpnPackage(userId, null, false /* lockdown */, allowList); |
| inOrder.verify(mMockNetd, never()).networkRejectNonSecureVpn(anyBoolean(), any()); |
| callback.assertNoCallback(); |
| defaultCallback.assertNoCallback(); |
| vpnUidCallback.assertNoCallback(); |
| vpnUidDefaultCallback.assertNoCallback(); |
| vpnDefaultCallbackAsUid.assertNoCallback(); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); |
| assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); |
| |
| // Enable lockdown and connect a VPN. The VPN is not blocked. |
| mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */, |
| allowList); |
| defaultCallback.expectBlockedStatusCallback(true, mWiFiNetworkAgent); |
| assertBlockedCallbackInAnyOrder(callback, true, mWiFiNetworkAgent, mCellNetworkAgent); |
| vpnUidCallback.assertNoCallback(); |
| vpnUidDefaultCallback.assertNoCallback(); |
| vpnDefaultCallbackAsUid.assertNoCallback(); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); |
| assertNull(mCm.getActiveNetwork()); |
| assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); |
| assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); |
| |
| mMockVpn.establishForMyUid(); |
| assertUidRangesUpdatedForMyUid(true); |
| defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); |
| vpnUidCallback.assertNoCallback(); // vpnUidCallback has NOT_VPN capability. |
| vpnUidDefaultCallback.assertNoCallback(); // VPN does not apply to VPN_UID |
| vpnDefaultCallbackAsUid.assertNoCallback(); |
| assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetwork()); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(VPN_UID)); |
| assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); |
| assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); |
| assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); |
| |
| mMockVpn.disconnect(); |
| defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn); |
| defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent); |
| vpnUidCallback.assertNoCallback(); |
| vpnUidDefaultCallback.assertNoCallback(); |
| vpnDefaultCallbackAsUid.assertNoCallback(); |
| assertNull(mCm.getActiveNetwork()); |
| |
| mCm.unregisterNetworkCallback(callback); |
| mCm.unregisterNetworkCallback(defaultCallback); |
| mCm.unregisterNetworkCallback(vpnUidCallback); |
| mCm.unregisterNetworkCallback(vpnUidDefaultCallback); |
| mCm.unregisterNetworkCallback(vpnDefaultCallbackAsUid); |
| } |
| |
| private void setupLegacyLockdownVpn() { |
| final String profileName = "testVpnProfile"; |
| final byte[] profileTag = profileName.getBytes(StandardCharsets.UTF_8); |
| when(mVpnProfileStore.get(Credentials.LOCKDOWN_VPN)).thenReturn(profileTag); |
| |
| final VpnProfile profile = new VpnProfile(profileName); |
| profile.name = "My VPN"; |
| profile.server = "192.0.2.1"; |
| profile.dnsServers = "8.8.8.8"; |
| profile.type = VpnProfile.TYPE_IPSEC_XAUTH_PSK; |
| final byte[] encodedProfile = profile.encode(); |
| when(mVpnProfileStore.get(Credentials.VPN + profileName)).thenReturn(encodedProfile); |
| } |
| |
| private void establishLegacyLockdownVpn(Network underlying) throws Exception { |
| // The legacy lockdown VPN only supports userId 0, and must have an underlying network. |
| assertNotNull(underlying); |
| mMockVpn.setVpnType(VpnManager.TYPE_VPN_LEGACY); |
| // The legacy lockdown VPN only supports userId 0. |
| final Set<UidRange> ranges = Collections.singleton(PRIMARY_UIDRANGE); |
| mMockVpn.registerAgent(ranges); |
| mMockVpn.setUnderlyingNetworks(new Network[]{underlying}); |
| mMockVpn.connect(true); |
| } |
| |
| @Test |
| public void testLegacyLockdownVpn() throws Exception { |
| mServiceContext.setPermission( |
| Manifest.permission.CONTROL_VPN, PERMISSION_GRANTED); |
| // For LockdownVpnTracker to call registerSystemDefaultNetworkCallback. |
| mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED); |
| |
| final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build(); |
| final TestNetworkCallback callback = new TestNetworkCallback(); |
| mCm.registerNetworkCallback(request, callback); |
| |
| final TestNetworkCallback defaultCallback = new TestNetworkCallback(); |
| mCm.registerDefaultNetworkCallback(defaultCallback); |
| |
| final TestNetworkCallback systemDefaultCallback = new TestNetworkCallback(); |
| mCm.registerSystemDefaultNetworkCallback(systemDefaultCallback, |
| new Handler(ConnectivityThread.getInstanceLooper())); |
| |
| // Pretend lockdown VPN was configured. |
| setupLegacyLockdownVpn(); |
| |
| // LockdownVpnTracker disables the Vpn teardown code and enables lockdown. |
| // Check the VPN's state before it does so. |
| assertTrue(mMockVpn.getEnableTeardown()); |
| assertFalse(mMockVpn.getLockdown()); |
| |
| // Send a USER_UNLOCKED broadcast so CS starts LockdownVpnTracker. |
| final int userId = UserHandle.getUserId(Process.myUid()); |
| final Intent addedIntent = new Intent(ACTION_USER_UNLOCKED); |
| addedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId)); |
| addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId); |
| processBroadcast(addedIntent); |
| |
| // Lockdown VPN disables teardown and enables lockdown. |
| assertFalse(mMockVpn.getEnableTeardown()); |
| assertTrue(mMockVpn.getLockdown()); |
| |
| // Bring up a network. |
| // Expect nothing to happen because the network does not have an IPv4 default route: legacy |
| // VPN only supports IPv4. |
| final LinkProperties cellLp = new LinkProperties(); |
| cellLp.setInterfaceName("rmnet0"); |
| cellLp.addLinkAddress(new LinkAddress("2001:db8::1/64")); |
| cellLp.addRoute(new RouteInfo(new IpPrefix("::/0"), null, "rmnet0")); |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp); |
| mCellNetworkAgent.connect(false /* validated */); |
| callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); |
| defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); |
| systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); |
| waitForIdle(); |
| assertNull(mMockVpn.getAgent()); |
| |
| // Add an IPv4 address. Ideally the VPN should start, but it doesn't because nothing calls |
| // LockdownVpnTracker#handleStateChangedLocked. This is a bug. |
| // TODO: consider fixing this. |
| cellLp.addLinkAddress(new LinkAddress("192.0.2.2/25")); |
| cellLp.addRoute(new RouteInfo(new IpPrefix("0.0.0.0/0"), null, "rmnet0")); |
| mCellNetworkAgent.sendLinkProperties(cellLp); |
| callback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); |
| defaultCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); |
| systemDefaultCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, |
| mCellNetworkAgent); |
| waitForIdle(); |
| assertNull(mMockVpn.getAgent()); |
| |
| // Disconnect, then try again with a network that supports IPv4 at connection time. |
| // Expect lockdown VPN to come up. |
| ExpectedBroadcast b1 = expectConnectivityAction(TYPE_MOBILE, DetailedState.DISCONNECTED); |
| mCellNetworkAgent.disconnect(); |
| callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| defaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| systemDefaultCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| b1.expectBroadcast(); |
| |
| // When lockdown VPN is active, the NetworkInfo state in CONNECTIVITY_ACTION is overwritten |
| // with the state of the VPN network. So expect a CONNECTING broadcast. |
| b1 = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTING); |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp); |
| mCellNetworkAgent.connect(false /* validated */); |
| callback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); |
| defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); |
| systemDefaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mCellNetworkAgent); |
| b1.expectBroadcast(); |
| assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); |
| assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); |
| assertNetworkInfo(TYPE_VPN, DetailedState.BLOCKED); |
| assertExtraInfoFromCmBlocked(mCellNetworkAgent); |
| |
| // TODO: it would be nice if we could simply rely on the production code here, and have |
| // LockdownVpnTracker start the VPN, have the VPN code register its NetworkAgent with |
| // ConnectivityService, etc. That would require duplicating a fair bit of code from the |
| // Vpn tests around how to mock out LegacyVpnRunner. But even if we did that, this does not |
| // work for at least two reasons: |
| // 1. In this test, calling registerNetworkAgent does not actually result in an agent being |
| // registered. This is because nothing calls onNetworkMonitorCreated, which is what |
| // actually ends up causing handleRegisterNetworkAgent to be called. Code in this test |
| // that wants to register an agent must use TestNetworkAgentWrapper. |
| // 2. Even if we exposed Vpn#agentConnect to the test, and made MockVpn#agentConnect call |
| // the TestNetworkAgentWrapper code, this would deadlock because the |
| // TestNetworkAgentWrapper code cannot be called on the handler thread since it calls |
| // waitForIdle(). |
| mMockVpn.expectStartLegacyVpnRunner(); |
| b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED); |
| ExpectedBroadcast b2 = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED); |
| establishLegacyLockdownVpn(mCellNetworkAgent.getNetwork()); |
| callback.expectAvailableThenValidatedCallbacks(mMockVpn); |
| defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); |
| systemDefaultCallback.assertNoCallback(); |
| NetworkCapabilities vpnNc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); |
| b1.expectBroadcast(); |
| b2.expectBroadcast(); |
| assertActiveNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.CONNECTED); |
| assertNetworkInfo(TYPE_WIFI, DetailedState.DISCONNECTED); |
| assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); |
| assertExtraInfoFromCmPresent(mCellNetworkAgent); |
| assertTrue(vpnNc.hasTransport(TRANSPORT_VPN)); |
| assertTrue(vpnNc.hasTransport(TRANSPORT_CELLULAR)); |
| assertFalse(vpnNc.hasTransport(TRANSPORT_WIFI)); |
| assertFalse(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED)); |
| assertVpnTransportInfo(vpnNc, VpnManager.TYPE_VPN_LEGACY); |
| |
| // Switch default network from cell to wifi. Expect VPN to disconnect and reconnect. |
| final LinkProperties wifiLp = new LinkProperties(); |
| wifiLp.setInterfaceName("wlan0"); |
| wifiLp.addLinkAddress(new LinkAddress("192.0.2.163/25")); |
| wifiLp.addRoute(new RouteInfo(new IpPrefix("0.0.0.0/0"), null, "wlan0")); |
| final NetworkCapabilities wifiNc = new NetworkCapabilities(); |
| wifiNc.addTransportType(TRANSPORT_WIFI); |
| wifiNc.addCapability(NET_CAPABILITY_NOT_METERED); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp, wifiNc); |
| |
| b1 = expectConnectivityAction(TYPE_MOBILE, DetailedState.DISCONNECTED); |
| // Wifi is CONNECTING because the VPN isn't up yet. |
| b2 = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTING); |
| ExpectedBroadcast b3 = expectConnectivityAction(TYPE_VPN, DetailedState.DISCONNECTED); |
| mWiFiNetworkAgent.connect(false /* validated */); |
| b1.expectBroadcast(); |
| b2.expectBroadcast(); |
| b3.expectBroadcast(); |
| mMockVpn.expectStopVpnRunnerPrivileged(); |
| mMockVpn.expectStartLegacyVpnRunner(); |
| |
| // TODO: why is wifi not blocked? Is it because when this callback is sent, the VPN is still |
| // connected, so the network is not considered blocked by the lockdown UID ranges? But the |
| // fact that a VPN is connected should only result in the VPN itself being unblocked, not |
| // any other network. Bug in isUidBlockedByVpn? |
| callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| callback.expectCallback(CallbackEntry.LOST, mMockVpn); |
| defaultCallback.expectCallback(CallbackEntry.LOST, mMockVpn); |
| defaultCallback.expectAvailableCallbacksUnvalidatedAndBlocked(mWiFiNetworkAgent); |
| systemDefaultCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| |
| // While the VPN is reconnecting on the new network, everything is blocked. |
| assertActiveNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.BLOCKED); |
| assertNetworkInfo(TYPE_WIFI, DetailedState.BLOCKED); |
| assertNetworkInfo(TYPE_VPN, DetailedState.BLOCKED); |
| assertExtraInfoFromCmBlocked(mWiFiNetworkAgent); |
| |
| // The VPN comes up again on wifi. |
| b1 = expectConnectivityAction(TYPE_VPN, DetailedState.CONNECTED); |
| b2 = expectConnectivityAction(TYPE_WIFI, DetailedState.CONNECTED); |
| establishLegacyLockdownVpn(mWiFiNetworkAgent.getNetwork()); |
| callback.expectAvailableThenValidatedCallbacks(mMockVpn); |
| defaultCallback.expectAvailableThenValidatedCallbacks(mMockVpn); |
| systemDefaultCallback.assertNoCallback(); |
| b1.expectBroadcast(); |
| b2.expectBroadcast(); |
| assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); |
| assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); |
| assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); |
| assertExtraInfoFromCmPresent(mWiFiNetworkAgent); |
| vpnNc = mCm.getNetworkCapabilities(mMockVpn.getNetwork()); |
| assertTrue(vpnNc.hasTransport(TRANSPORT_VPN)); |
| assertTrue(vpnNc.hasTransport(TRANSPORT_WIFI)); |
| assertFalse(vpnNc.hasTransport(TRANSPORT_CELLULAR)); |
| assertTrue(vpnNc.hasCapability(NET_CAPABILITY_NOT_METERED)); |
| |
| // Disconnect cell. Nothing much happens since it's not the default network. |
| mCellNetworkAgent.disconnect(); |
| callback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| defaultCallback.assertNoCallback(); |
| systemDefaultCallback.assertNoCallback(); |
| |
| assertActiveNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); |
| assertNetworkInfo(TYPE_MOBILE, DetailedState.DISCONNECTED); |
| assertNetworkInfo(TYPE_WIFI, DetailedState.CONNECTED); |
| assertNetworkInfo(TYPE_VPN, DetailedState.CONNECTED); |
| assertExtraInfoFromCmPresent(mWiFiNetworkAgent); |
| |
| b1 = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED); |
| b2 = expectConnectivityAction(TYPE_VPN, DetailedState.DISCONNECTED); |
| mWiFiNetworkAgent.disconnect(); |
| callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| systemDefaultCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| b1.expectBroadcast(); |
| callback.expectCapabilitiesThat(mMockVpn, nc -> !nc.hasTransport(TRANSPORT_WIFI)); |
| mMockVpn.expectStopVpnRunnerPrivileged(); |
| callback.expectCallback(CallbackEntry.LOST, mMockVpn); |
| b2.expectBroadcast(); |
| } |
| |
| /** |
| * Test mutable and requestable network capabilities such as |
| * {@link NetworkCapabilities#NET_CAPABILITY_TRUSTED} and |
| * {@link NetworkCapabilities#NET_CAPABILITY_NOT_VCN_MANAGED}. Verify that the |
| * {@code ConnectivityService} re-assign the networks accordingly. |
| */ |
| @Test |
| public final void testLoseMutableAndRequestableCaps() throws Exception { |
| final int[] testCaps = new int [] { |
| NET_CAPABILITY_TRUSTED, |
| NET_CAPABILITY_NOT_VCN_MANAGED |
| }; |
| for (final int testCap : testCaps) { |
| // Create requests with and without the testing capability. |
| final TestNetworkCallback callbackWithCap = new TestNetworkCallback(); |
| final TestNetworkCallback callbackWithoutCap = new TestNetworkCallback(); |
| mCm.requestNetwork(new NetworkRequest.Builder().addCapability(testCap).build(), |
| callbackWithCap); |
| mCm.requestNetwork(new NetworkRequest.Builder().removeCapability(testCap).build(), |
| callbackWithoutCap); |
| |
| // Setup networks with testing capability and verify the default network changes. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.addCapability(testCap); |
| mCellNetworkAgent.connect(true); |
| callbackWithCap.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| callbackWithoutCap.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId)); |
| reset(mMockNetd); |
| |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.addCapability(testCap); |
| mWiFiNetworkAgent.connect(true); |
| callbackWithCap.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); |
| callbackWithoutCap.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); |
| verify(mMockNetd).networkSetDefault(eq(mWiFiNetworkAgent.getNetwork().netId)); |
| reset(mMockNetd); |
| |
| // Remove the testing capability on wifi, verify the callback and default network |
| // changes back to cellular. |
| mWiFiNetworkAgent.removeCapability(testCap); |
| callbackWithCap.expectAvailableCallbacksValidated(mCellNetworkAgent); |
| callbackWithoutCap.expectCapabilitiesWithout(testCap, mWiFiNetworkAgent); |
| verify(mMockNetd).networkSetDefault(eq(mCellNetworkAgent.getNetwork().netId)); |
| reset(mMockNetd); |
| |
| mCellNetworkAgent.removeCapability(testCap); |
| callbackWithCap.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| callbackWithoutCap.assertNoCallback(); |
| verify(mMockNetd).networkClearDefault(); |
| |
| mCm.unregisterNetworkCallback(callbackWithCap); |
| mCm.unregisterNetworkCallback(callbackWithoutCap); |
| } |
| } |
| |
| @Test |
| public final void testBatteryStatsNetworkType() throws Exception { |
| final LinkProperties cellLp = new LinkProperties(); |
| cellLp.setInterfaceName("cell0"); |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp); |
| mCellNetworkAgent.connect(true); |
| waitForIdle(); |
| verify(mDeps).reportNetworkInterfaceForTransports(mServiceContext, |
| cellLp.getInterfaceName(), |
| new int[] { TRANSPORT_CELLULAR }); |
| |
| final LinkProperties wifiLp = new LinkProperties(); |
| wifiLp.setInterfaceName("wifi0"); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp); |
| mWiFiNetworkAgent.connect(true); |
| waitForIdle(); |
| verify(mDeps).reportNetworkInterfaceForTransports(mServiceContext, |
| wifiLp.getInterfaceName(), |
| new int[] { TRANSPORT_WIFI }); |
| |
| mCellNetworkAgent.disconnect(); |
| mWiFiNetworkAgent.disconnect(); |
| |
| cellLp.setInterfaceName("wifi0"); |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp); |
| mCellNetworkAgent.connect(true); |
| waitForIdle(); |
| verify(mDeps).reportNetworkInterfaceForTransports(mServiceContext, |
| cellLp.getInterfaceName(), |
| new int[] { TRANSPORT_CELLULAR }); |
| mCellNetworkAgent.disconnect(); |
| } |
| |
| /** |
| * Make simulated InterfaceConfigParcel for Nat464Xlat to query clat lower layer info. |
| */ |
| private InterfaceConfigurationParcel getClatInterfaceConfigParcel(LinkAddress la) { |
| final InterfaceConfigurationParcel cfg = new InterfaceConfigurationParcel(); |
| cfg.hwAddr = "11:22:33:44:55:66"; |
| cfg.ipv4Addr = la.getAddress().getHostAddress(); |
| cfg.prefixLength = la.getPrefixLength(); |
| return cfg; |
| } |
| |
| /** |
| * Make expected stack link properties, copied from Nat464Xlat. |
| */ |
| private LinkProperties makeClatLinkProperties(LinkAddress la) { |
| LinkAddress clatAddress = la; |
| LinkProperties stacked = new LinkProperties(); |
| stacked.setInterfaceName(CLAT_MOBILE_IFNAME); |
| RouteInfo ipv4Default = new RouteInfo( |
| new LinkAddress(Inet4Address.ANY, 0), |
| clatAddress.getAddress(), CLAT_MOBILE_IFNAME); |
| stacked.addRoute(ipv4Default); |
| stacked.addLinkAddress(clatAddress); |
| return stacked; |
| } |
| |
| private Nat64PrefixEventParcel makeNat64PrefixEvent(final int netId, final int prefixOperation, |
| final String prefixAddress, final int prefixLength) { |
| final Nat64PrefixEventParcel event = new Nat64PrefixEventParcel(); |
| event.netId = netId; |
| event.prefixOperation = prefixOperation; |
| event.prefixAddress = prefixAddress; |
| event.prefixLength = prefixLength; |
| return event; |
| } |
| |
| @Test |
| public void testStackedLinkProperties() throws Exception { |
| final LinkAddress myIpv4 = new LinkAddress("1.2.3.4/24"); |
| final LinkAddress myIpv6 = new LinkAddress("2001:db8:1::1/64"); |
| final String kNat64PrefixString = "2001:db8:64:64:64:64::"; |
| final IpPrefix kNat64Prefix = new IpPrefix(InetAddress.getByName(kNat64PrefixString), 96); |
| final String kOtherNat64PrefixString = "64:ff9b::"; |
| final IpPrefix kOtherNat64Prefix = new IpPrefix( |
| InetAddress.getByName(kOtherNat64PrefixString), 96); |
| final RouteInfo ipv6Default = |
| new RouteInfo((IpPrefix) null, myIpv6.getAddress(), MOBILE_IFNAME); |
| final RouteInfo ipv6Subnet = new RouteInfo(myIpv6, null, MOBILE_IFNAME); |
| final RouteInfo ipv4Subnet = new RouteInfo(myIpv4, null, MOBILE_IFNAME); |
| final RouteInfo stackedDefault = |
| new RouteInfo((IpPrefix) null, myIpv4.getAddress(), CLAT_MOBILE_IFNAME); |
| |
| final NetworkRequest networkRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_CELLULAR) |
| .addCapability(NET_CAPABILITY_INTERNET) |
| .build(); |
| final TestNetworkCallback networkCallback = new TestNetworkCallback(); |
| mCm.registerNetworkCallback(networkRequest, networkCallback); |
| |
| // Prepare ipv6 only link properties. |
| final LinkProperties cellLp = new LinkProperties(); |
| cellLp.setInterfaceName(MOBILE_IFNAME); |
| cellLp.addLinkAddress(myIpv6); |
| cellLp.addRoute(ipv6Default); |
| cellLp.addRoute(ipv6Subnet); |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp); |
| reset(mMockDnsResolver); |
| reset(mMockNetd); |
| |
| // Connect with ipv6 link properties. Expect prefix discovery to be started. |
| mCellNetworkAgent.connect(true); |
| int cellNetId = mCellNetworkAgent.getNetwork().netId; |
| waitForIdle(); |
| |
| verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical(cellNetId, |
| INetd.PERMISSION_NONE)); |
| assertRoutesAdded(cellNetId, ipv6Subnet, ipv6Default); |
| verify(mMockDnsResolver, times(1)).createNetworkCache(eq(cellNetId)); |
| verify(mMockNetd, times(1)).networkAddInterface(cellNetId, MOBILE_IFNAME); |
| verify(mDeps).reportNetworkInterfaceForTransports(mServiceContext, |
| cellLp.getInterfaceName(), |
| new int[] { TRANSPORT_CELLULAR }); |
| |
| networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId); |
| |
| // Switching default network updates TCP buffer sizes. |
| verifyTcpBufferSizeChange(ConnectivityService.DEFAULT_TCP_BUFFER_SIZES); |
| // Add an IPv4 address. Expect prefix discovery to be stopped. Netd doesn't tell us that |
| // the NAT64 prefix was removed because one was never discovered. |
| cellLp.addLinkAddress(myIpv4); |
| mCellNetworkAgent.sendLinkProperties(cellLp); |
| networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); |
| assertRoutesAdded(cellNetId, ipv4Subnet); |
| verify(mMockDnsResolver, times(1)).stopPrefix64Discovery(cellNetId); |
| verify(mMockDnsResolver, atLeastOnce()).setResolverConfiguration(any()); |
| |
| // Make sure BatteryStats was not told about any v4- interfaces, as none should have |
| // come online yet. |
| waitForIdle(); |
| verify(mDeps, never()) |
| .reportNetworkInterfaceForTransports(eq(mServiceContext), startsWith("v4-"), any()); |
| |
| verifyNoMoreInteractions(mMockNetd); |
| verifyNoMoreInteractions(mMockDnsResolver); |
| reset(mMockNetd); |
| reset(mMockDnsResolver); |
| when(mMockNetd.interfaceGetCfg(CLAT_MOBILE_IFNAME)) |
| .thenReturn(getClatInterfaceConfigParcel(myIpv4)); |
| |
| // Remove IPv4 address. Expect prefix discovery to be started again. |
| cellLp.removeLinkAddress(myIpv4); |
| mCellNetworkAgent.sendLinkProperties(cellLp); |
| networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); |
| verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId); |
| assertRoutesRemoved(cellNetId, ipv4Subnet); |
| |
| // When NAT64 prefix discovery succeeds, LinkProperties are updated and clatd is started. |
| Nat464Xlat clat = getNat464Xlat(mCellNetworkAgent); |
| assertNull(mCm.getLinkProperties(mCellNetworkAgent.getNetwork()).getNat64Prefix()); |
| mService.mResolverUnsolEventCallback.onNat64PrefixEvent( |
| makeNat64PrefixEvent(cellNetId, PREFIX_OPERATION_ADDED, kNat64PrefixString, 96)); |
| LinkProperties lpBeforeClat = networkCallback.expectCallback( |
| CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent).getLp(); |
| assertEquals(0, lpBeforeClat.getStackedLinks().size()); |
| assertEquals(kNat64Prefix, lpBeforeClat.getNat64Prefix()); |
| verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kNat64Prefix.toString()); |
| |
| // Clat iface comes up. Expect stacked link to be added. |
| clat.interfaceLinkStateChanged(CLAT_MOBILE_IFNAME, true); |
| networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); |
| List<LinkProperties> stackedLps = mCm.getLinkProperties(mCellNetworkAgent.getNetwork()) |
| .getStackedLinks(); |
| assertEquals(makeClatLinkProperties(myIpv4), stackedLps.get(0)); |
| assertRoutesAdded(cellNetId, stackedDefault); |
| verify(mMockNetd, times(1)).networkAddInterface(cellNetId, CLAT_MOBILE_IFNAME); |
| // Change trivial linkproperties and see if stacked link is preserved. |
| cellLp.addDnsServer(InetAddress.getByName("8.8.8.8")); |
| mCellNetworkAgent.sendLinkProperties(cellLp); |
| networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); |
| |
| List<LinkProperties> stackedLpsAfterChange = |
| mCm.getLinkProperties(mCellNetworkAgent.getNetwork()).getStackedLinks(); |
| assertNotEquals(stackedLpsAfterChange, Collections.EMPTY_LIST); |
| assertEquals(makeClatLinkProperties(myIpv4), stackedLpsAfterChange.get(0)); |
| |
| verify(mMockDnsResolver, times(1)).setResolverConfiguration( |
| mResolverParamsParcelCaptor.capture()); |
| ResolverParamsParcel resolvrParams = mResolverParamsParcelCaptor.getValue(); |
| assertEquals(1, resolvrParams.servers.length); |
| assertTrue(ArrayUtils.contains(resolvrParams.servers, "8.8.8.8")); |
| |
| for (final LinkProperties stackedLp : stackedLpsAfterChange) { |
| verify(mDeps).reportNetworkInterfaceForTransports( |
| mServiceContext, stackedLp.getInterfaceName(), |
| new int[] { TRANSPORT_CELLULAR }); |
| } |
| reset(mMockNetd); |
| when(mMockNetd.interfaceGetCfg(CLAT_MOBILE_IFNAME)) |
| .thenReturn(getClatInterfaceConfigParcel(myIpv4)); |
| // Change the NAT64 prefix without first removing it. |
| // Expect clatd to be stopped and started with the new prefix. |
| mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent( |
| cellNetId, PREFIX_OPERATION_ADDED, kOtherNat64PrefixString, 96)); |
| networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, |
| (lp) -> lp.getStackedLinks().size() == 0); |
| verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME); |
| assertRoutesRemoved(cellNetId, stackedDefault); |
| verify(mMockNetd, times(1)).networkRemoveInterface(cellNetId, CLAT_MOBILE_IFNAME); |
| |
| verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kOtherNat64Prefix.toString()); |
| networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, |
| (lp) -> lp.getNat64Prefix().equals(kOtherNat64Prefix)); |
| clat.interfaceLinkStateChanged(CLAT_MOBILE_IFNAME, true); |
| networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, |
| (lp) -> lp.getStackedLinks().size() == 1); |
| assertRoutesAdded(cellNetId, stackedDefault); |
| verify(mMockNetd, times(1)).networkAddInterface(cellNetId, CLAT_MOBILE_IFNAME); |
| reset(mMockNetd); |
| |
| // Add ipv4 address, expect that clatd and prefix discovery are stopped and stacked |
| // linkproperties are cleaned up. |
| cellLp.addLinkAddress(myIpv4); |
| cellLp.addRoute(ipv4Subnet); |
| mCellNetworkAgent.sendLinkProperties(cellLp); |
| networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); |
| assertRoutesAdded(cellNetId, ipv4Subnet); |
| verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME); |
| verify(mMockDnsResolver, times(1)).stopPrefix64Discovery(cellNetId); |
| |
| // As soon as stop is called, the linkproperties lose the stacked interface. |
| networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); |
| LinkProperties actualLpAfterIpv4 = mCm.getLinkProperties(mCellNetworkAgent.getNetwork()); |
| LinkProperties expected = new LinkProperties(cellLp); |
| expected.setNat64Prefix(kOtherNat64Prefix); |
| assertEquals(expected, actualLpAfterIpv4); |
| assertEquals(0, actualLpAfterIpv4.getStackedLinks().size()); |
| assertRoutesRemoved(cellNetId, stackedDefault); |
| |
| // The interface removed callback happens but has no effect after stop is called. |
| clat.interfaceRemoved(CLAT_MOBILE_IFNAME); |
| networkCallback.assertNoCallback(); |
| verify(mMockNetd, times(1)).networkRemoveInterface(cellNetId, CLAT_MOBILE_IFNAME); |
| verifyNoMoreInteractions(mMockNetd); |
| verifyNoMoreInteractions(mMockDnsResolver); |
| reset(mMockNetd); |
| reset(mMockDnsResolver); |
| when(mMockNetd.interfaceGetCfg(CLAT_MOBILE_IFNAME)) |
| .thenReturn(getClatInterfaceConfigParcel(myIpv4)); |
| |
| // Stopping prefix discovery causes netd to tell us that the NAT64 prefix is gone. |
| mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent( |
| cellNetId, PREFIX_OPERATION_REMOVED, kOtherNat64PrefixString, 96)); |
| networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, |
| (lp) -> lp.getNat64Prefix() == null); |
| |
| // Remove IPv4 address and expect prefix discovery and clatd to be started again. |
| cellLp.removeLinkAddress(myIpv4); |
| cellLp.removeRoute(new RouteInfo(myIpv4, null, MOBILE_IFNAME)); |
| cellLp.removeDnsServer(InetAddress.getByName("8.8.8.8")); |
| mCellNetworkAgent.sendLinkProperties(cellLp); |
| networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); |
| assertRoutesRemoved(cellNetId, ipv4Subnet); // Directly-connected routes auto-added. |
| verify(mMockDnsResolver, times(1)).startPrefix64Discovery(cellNetId); |
| mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent( |
| cellNetId, PREFIX_OPERATION_ADDED, kNat64PrefixString, 96)); |
| networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); |
| verify(mMockNetd, times(1)).clatdStart(MOBILE_IFNAME, kNat64Prefix.toString()); |
| |
| // Clat iface comes up. Expect stacked link to be added. |
| clat.interfaceLinkStateChanged(CLAT_MOBILE_IFNAME, true); |
| networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, |
| (lp) -> lp.getStackedLinks().size() == 1 && lp.getNat64Prefix() != null); |
| assertRoutesAdded(cellNetId, stackedDefault); |
| verify(mMockNetd, times(1)).networkAddInterface(cellNetId, CLAT_MOBILE_IFNAME); |
| |
| // NAT64 prefix is removed. Expect that clat is stopped. |
| mService.mResolverUnsolEventCallback.onNat64PrefixEvent(makeNat64PrefixEvent( |
| cellNetId, PREFIX_OPERATION_REMOVED, kNat64PrefixString, 96)); |
| networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, |
| (lp) -> lp.getStackedLinks().size() == 0 && lp.getNat64Prefix() == null); |
| assertRoutesRemoved(cellNetId, ipv4Subnet, stackedDefault); |
| |
| // Stop has no effect because clat is already stopped. |
| verify(mMockNetd, times(1)).clatdStop(MOBILE_IFNAME); |
| networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, |
| (lp) -> lp.getStackedLinks().size() == 0); |
| verify(mMockNetd, times(1)).networkRemoveInterface(cellNetId, CLAT_MOBILE_IFNAME); |
| verify(mMockNetd, times(1)).interfaceGetCfg(CLAT_MOBILE_IFNAME); |
| // Clean up. |
| mCellNetworkAgent.disconnect(); |
| networkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| networkCallback.assertNoCallback(); |
| verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(), |
| eq(Integer.toString(TRANSPORT_CELLULAR))); |
| verify(mMockNetd).networkDestroy(cellNetId); |
| verifyNoMoreInteractions(mMockNetd); |
| reset(mMockNetd); |
| |
| // Test disconnecting a network that is running 464xlat. |
| |
| // Connect a network with a NAT64 prefix. |
| when(mMockNetd.interfaceGetCfg(CLAT_MOBILE_IFNAME)) |
| .thenReturn(getClatInterfaceConfigParcel(myIpv4)); |
| cellLp.setNat64Prefix(kNat64Prefix); |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp); |
| mCellNetworkAgent.connect(false /* validated */); |
| networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); |
| cellNetId = mCellNetworkAgent.getNetwork().netId; |
| verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical(cellNetId, |
| INetd.PERMISSION_NONE)); |
| assertRoutesAdded(cellNetId, ipv6Subnet, ipv6Default); |
| |
| // Clatd is started and clat iface comes up. Expect stacked link to be added. |
| verify(mMockNetd).clatdStart(MOBILE_IFNAME, kNat64Prefix.toString()); |
| clat = getNat464Xlat(mCellNetworkAgent); |
| clat.interfaceLinkStateChanged(CLAT_MOBILE_IFNAME, true /* up */); |
| networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, |
| (lp) -> lp.getStackedLinks().size() == 1 |
| && lp.getNat64Prefix().equals(kNat64Prefix)); |
| verify(mMockNetd).networkAddInterface(cellNetId, CLAT_MOBILE_IFNAME); |
| // assertRoutesAdded sees all calls since last mMockNetd reset, so expect IPv6 routes again. |
| assertRoutesAdded(cellNetId, ipv6Subnet, ipv6Default, stackedDefault); |
| reset(mMockNetd); |
| |
| // Disconnect the network. clat is stopped and the network is destroyed. |
| mCellNetworkAgent.disconnect(); |
| networkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| networkCallback.assertNoCallback(); |
| verify(mMockNetd).clatdStop(MOBILE_IFNAME); |
| verify(mMockNetd).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(), |
| eq(Integer.toString(TRANSPORT_CELLULAR))); |
| verify(mMockNetd).networkDestroy(cellNetId); |
| verifyNoMoreInteractions(mMockNetd); |
| |
| mCm.unregisterNetworkCallback(networkCallback); |
| } |
| |
| private void expectNat64PrefixChange(TestableNetworkCallback callback, |
| TestNetworkAgentWrapper agent, IpPrefix prefix) { |
| callback.expectLinkPropertiesThat(agent, x -> Objects.equals(x.getNat64Prefix(), prefix)); |
| } |
| |
| @Test |
| public void testNat64PrefixMultipleSources() throws Exception { |
| final String iface = "wlan0"; |
| final String pref64FromRaStr = "64:ff9b::"; |
| final String pref64FromDnsStr = "2001:db8:64::"; |
| final IpPrefix pref64FromRa = new IpPrefix(InetAddress.getByName(pref64FromRaStr), 96); |
| final IpPrefix pref64FromDns = new IpPrefix(InetAddress.getByName(pref64FromDnsStr), 96); |
| final IpPrefix newPref64FromRa = new IpPrefix("2001:db8:64:64:64:64::/96"); |
| |
| final NetworkRequest request = new NetworkRequest.Builder() |
| .addCapability(NET_CAPABILITY_INTERNET) |
| .build(); |
| final TestNetworkCallback callback = new TestNetworkCallback(); |
| mCm.registerNetworkCallback(request, callback); |
| |
| final LinkProperties baseLp = new LinkProperties(); |
| baseLp.setInterfaceName(iface); |
| baseLp.addLinkAddress(new LinkAddress("2001:db8:1::1/64")); |
| baseLp.addDnsServer(InetAddress.getByName("2001:4860:4860::6464")); |
| |
| reset(mMockNetd, mMockDnsResolver); |
| InOrder inOrder = inOrder(mMockNetd, mMockDnsResolver); |
| |
| // If a network already has a NAT64 prefix on connect, clatd is started immediately and |
| // prefix discovery is never started. |
| LinkProperties lp = new LinkProperties(baseLp); |
| lp.setNat64Prefix(pref64FromRa); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, lp); |
| mWiFiNetworkAgent.connect(false); |
| final Network network = mWiFiNetworkAgent.getNetwork(); |
| int netId = network.getNetId(); |
| callback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| inOrder.verify(mMockNetd).clatdStart(iface, pref64FromRa.toString()); |
| inOrder.verify(mMockDnsResolver).setPrefix64(netId, pref64FromRa.toString()); |
| inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId); |
| callback.assertNoCallback(); |
| assertEquals(pref64FromRa, mCm.getLinkProperties(network).getNat64Prefix()); |
| |
| // If the RA prefix is withdrawn, clatd is stopped and prefix discovery is started. |
| lp.setNat64Prefix(null); |
| mWiFiNetworkAgent.sendLinkProperties(lp); |
| expectNat64PrefixChange(callback, mWiFiNetworkAgent, null); |
| inOrder.verify(mMockNetd).clatdStop(iface); |
| inOrder.verify(mMockDnsResolver).setPrefix64(netId, ""); |
| inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId); |
| |
| // If the RA prefix appears while DNS discovery is in progress, discovery is stopped and |
| // clatd is started with the prefix from the RA. |
| lp.setNat64Prefix(pref64FromRa); |
| mWiFiNetworkAgent.sendLinkProperties(lp); |
| expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromRa); |
| inOrder.verify(mMockNetd).clatdStart(iface, pref64FromRa.toString()); |
| inOrder.verify(mMockDnsResolver).stopPrefix64Discovery(netId); |
| inOrder.verify(mMockDnsResolver).setPrefix64(netId, pref64FromRa.toString()); |
| |
| // Withdraw the RA prefix so we can test the case where an RA prefix appears after DNS |
| // discovery has succeeded. |
| lp.setNat64Prefix(null); |
| mWiFiNetworkAgent.sendLinkProperties(lp); |
| expectNat64PrefixChange(callback, mWiFiNetworkAgent, null); |
| inOrder.verify(mMockNetd).clatdStop(iface); |
| inOrder.verify(mMockDnsResolver).setPrefix64(netId, ""); |
| inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId); |
| |
| mService.mResolverUnsolEventCallback.onNat64PrefixEvent( |
| makeNat64PrefixEvent(netId, PREFIX_OPERATION_ADDED, pref64FromDnsStr, 96)); |
| expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromDns); |
| inOrder.verify(mMockNetd).clatdStart(iface, pref64FromDns.toString()); |
| |
| // If an RA advertises the same prefix that was discovered by DNS, nothing happens: prefix |
| // discovery is not stopped, and there are no callbacks. |
| lp.setNat64Prefix(pref64FromDns); |
| mWiFiNetworkAgent.sendLinkProperties(lp); |
| callback.assertNoCallback(); |
| inOrder.verify(mMockNetd, never()).clatdStop(iface); |
| inOrder.verify(mMockNetd, never()).clatdStart(eq(iface), anyString()); |
| inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId); |
| inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId); |
| inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), anyString()); |
| |
| // If the RA is later withdrawn, nothing happens again. |
| lp.setNat64Prefix(null); |
| mWiFiNetworkAgent.sendLinkProperties(lp); |
| callback.assertNoCallback(); |
| inOrder.verify(mMockNetd, never()).clatdStop(iface); |
| inOrder.verify(mMockNetd, never()).clatdStart(eq(iface), anyString()); |
| inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId); |
| inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId); |
| inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), anyString()); |
| |
| // If the RA prefix changes, clatd is restarted and prefix discovery is stopped. |
| lp.setNat64Prefix(pref64FromRa); |
| mWiFiNetworkAgent.sendLinkProperties(lp); |
| expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromRa); |
| inOrder.verify(mMockNetd).clatdStop(iface); |
| inOrder.verify(mMockDnsResolver).stopPrefix64Discovery(netId); |
| |
| // Stopping prefix discovery results in a prefix removed notification. |
| mService.mResolverUnsolEventCallback.onNat64PrefixEvent( |
| makeNat64PrefixEvent(netId, PREFIX_OPERATION_REMOVED, pref64FromDnsStr, 96)); |
| |
| inOrder.verify(mMockNetd).clatdStart(iface, pref64FromRa.toString()); |
| inOrder.verify(mMockDnsResolver).setPrefix64(netId, pref64FromRa.toString()); |
| inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId); |
| |
| // If the RA prefix changes, clatd is restarted and prefix discovery is not started. |
| lp.setNat64Prefix(newPref64FromRa); |
| mWiFiNetworkAgent.sendLinkProperties(lp); |
| expectNat64PrefixChange(callback, mWiFiNetworkAgent, newPref64FromRa); |
| inOrder.verify(mMockNetd).clatdStop(iface); |
| inOrder.verify(mMockDnsResolver).setPrefix64(netId, ""); |
| inOrder.verify(mMockNetd).clatdStart(iface, newPref64FromRa.toString()); |
| inOrder.verify(mMockDnsResolver).setPrefix64(netId, newPref64FromRa.toString()); |
| inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId); |
| inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId); |
| |
| // If the RA prefix changes to the same value, nothing happens. |
| lp.setNat64Prefix(newPref64FromRa); |
| mWiFiNetworkAgent.sendLinkProperties(lp); |
| callback.assertNoCallback(); |
| assertEquals(newPref64FromRa, mCm.getLinkProperties(network).getNat64Prefix()); |
| inOrder.verify(mMockNetd, never()).clatdStop(iface); |
| inOrder.verify(mMockNetd, never()).clatdStart(eq(iface), anyString()); |
| inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId); |
| inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId); |
| inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), anyString()); |
| |
| // The transition between no prefix and DNS prefix is tested in testStackedLinkProperties. |
| |
| // If the same prefix is learned first by DNS and then by RA, and clat is later stopped, |
| // (e.g., because the network disconnects) setPrefix64(netid, "") is never called. |
| lp.setNat64Prefix(null); |
| mWiFiNetworkAgent.sendLinkProperties(lp); |
| expectNat64PrefixChange(callback, mWiFiNetworkAgent, null); |
| inOrder.verify(mMockNetd).clatdStop(iface); |
| inOrder.verify(mMockDnsResolver).setPrefix64(netId, ""); |
| inOrder.verify(mMockDnsResolver).startPrefix64Discovery(netId); |
| mService.mResolverUnsolEventCallback.onNat64PrefixEvent( |
| makeNat64PrefixEvent(netId, PREFIX_OPERATION_ADDED, pref64FromDnsStr, 96)); |
| expectNat64PrefixChange(callback, mWiFiNetworkAgent, pref64FromDns); |
| inOrder.verify(mMockNetd).clatdStart(iface, pref64FromDns.toString()); |
| inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), any()); |
| |
| lp.setNat64Prefix(pref64FromDns); |
| mWiFiNetworkAgent.sendLinkProperties(lp); |
| callback.assertNoCallback(); |
| inOrder.verify(mMockNetd, never()).clatdStop(iface); |
| inOrder.verify(mMockNetd, never()).clatdStart(eq(iface), anyString()); |
| inOrder.verify(mMockDnsResolver, never()).stopPrefix64Discovery(netId); |
| inOrder.verify(mMockDnsResolver, never()).startPrefix64Discovery(netId); |
| inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), anyString()); |
| |
| // When tearing down a network, clat state is only updated after CALLBACK_LOST is fired, but |
| // before CONNECTIVITY_ACTION is sent. Wait for CONNECTIVITY_ACTION before verifying that |
| // clat has been stopped, or the test will be flaky. |
| ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED); |
| mWiFiNetworkAgent.disconnect(); |
| callback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| b.expectBroadcast(); |
| |
| inOrder.verify(mMockNetd).clatdStop(iface); |
| inOrder.verify(mMockDnsResolver).stopPrefix64Discovery(netId); |
| inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), anyString()); |
| |
| mCm.unregisterNetworkCallback(callback); |
| } |
| |
| @Test |
| public void testWith464XlatDisable() throws Exception { |
| doReturn(false).when(mDeps).getCellular464XlatEnabled(); |
| |
| final TestNetworkCallback callback = new TestNetworkCallback(); |
| final TestNetworkCallback defaultCallback = new TestNetworkCallback(); |
| final NetworkRequest networkRequest = new NetworkRequest.Builder() |
| .addCapability(NET_CAPABILITY_INTERNET) |
| .build(); |
| mCm.registerNetworkCallback(networkRequest, callback); |
| mCm.registerDefaultNetworkCallback(defaultCallback); |
| |
| // Bring up validated cell. |
| final LinkProperties cellLp = new LinkProperties(); |
| cellLp.setInterfaceName(MOBILE_IFNAME); |
| cellLp.addLinkAddress(new LinkAddress("2001:db8:1::1/64")); |
| cellLp.addRoute(new RouteInfo(new IpPrefix("::/0"), null, MOBILE_IFNAME)); |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| |
| mCellNetworkAgent.sendLinkProperties(cellLp); |
| mCellNetworkAgent.connect(true); |
| callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| defaultCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| final int cellNetId = mCellNetworkAgent.getNetwork().netId; |
| waitForIdle(); |
| |
| verify(mMockDnsResolver, never()).startPrefix64Discovery(cellNetId); |
| Nat464Xlat clat = getNat464Xlat(mCellNetworkAgent); |
| assertTrue("Nat464Xlat was not IDLE", !clat.isStarted()); |
| |
| // This cannot happen because prefix discovery cannot succeed if it is never started. |
| mService.mResolverUnsolEventCallback.onNat64PrefixEvent( |
| makeNat64PrefixEvent(cellNetId, PREFIX_OPERATION_ADDED, "64:ff9b::", 96)); |
| |
| // ... but still, check that even if it did, clatd would not be started. |
| verify(mMockNetd, never()).clatdStart(anyString(), anyString()); |
| assertTrue("Nat464Xlat was not IDLE", !clat.isStarted()); |
| } |
| |
| @Test |
| public void testDataActivityTracking() throws Exception { |
| final TestNetworkCallback networkCallback = new TestNetworkCallback(); |
| final NetworkRequest networkRequest = new NetworkRequest.Builder() |
| .addCapability(NET_CAPABILITY_INTERNET) |
| .build(); |
| mCm.registerNetworkCallback(networkRequest, networkCallback); |
| |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| final LinkProperties cellLp = new LinkProperties(); |
| cellLp.setInterfaceName(MOBILE_IFNAME); |
| mCellNetworkAgent.sendLinkProperties(cellLp); |
| mCellNetworkAgent.connect(true); |
| networkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| verify(mMockNetd, times(1)).idletimerAddInterface(eq(MOBILE_IFNAME), anyInt(), |
| eq(Integer.toString(TRANSPORT_CELLULAR))); |
| |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| final LinkProperties wifiLp = new LinkProperties(); |
| wifiLp.setInterfaceName(WIFI_IFNAME); |
| mWiFiNetworkAgent.sendLinkProperties(wifiLp); |
| |
| // Network switch |
| mWiFiNetworkAgent.connect(true); |
| networkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| networkCallback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); |
| networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); |
| verify(mMockNetd, times(1)).idletimerAddInterface(eq(WIFI_IFNAME), anyInt(), |
| eq(Integer.toString(TRANSPORT_WIFI))); |
| verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(), |
| eq(Integer.toString(TRANSPORT_CELLULAR))); |
| |
| // Disconnect wifi and switch back to cell |
| reset(mMockNetd); |
| mWiFiNetworkAgent.disconnect(); |
| networkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| assertNoCallbacks(networkCallback); |
| verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(WIFI_IFNAME), anyInt(), |
| eq(Integer.toString(TRANSPORT_WIFI))); |
| verify(mMockNetd, times(1)).idletimerAddInterface(eq(MOBILE_IFNAME), anyInt(), |
| eq(Integer.toString(TRANSPORT_CELLULAR))); |
| |
| // reconnect wifi |
| reset(mMockNetd); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| wifiLp.setInterfaceName(WIFI_IFNAME); |
| mWiFiNetworkAgent.sendLinkProperties(wifiLp); |
| mWiFiNetworkAgent.connect(true); |
| networkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| networkCallback.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); |
| networkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); |
| verify(mMockNetd, times(1)).idletimerAddInterface(eq(WIFI_IFNAME), anyInt(), |
| eq(Integer.toString(TRANSPORT_WIFI))); |
| verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(), |
| eq(Integer.toString(TRANSPORT_CELLULAR))); |
| |
| // Disconnect cell |
| reset(mMockNetd); |
| mCellNetworkAgent.disconnect(); |
| networkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| // LOST callback is triggered earlier than removing idle timer. Broadcast should also be |
| // sent as network being switched. Ensure rule removal for cell will not be triggered |
| // unexpectedly before network being removed. |
| waitForIdle(); |
| verify(mMockNetd, times(0)).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(), |
| eq(Integer.toString(TRANSPORT_CELLULAR))); |
| verify(mMockNetd, times(1)).networkDestroy(eq(mCellNetworkAgent.getNetwork().netId)); |
| verify(mMockDnsResolver, times(1)) |
| .destroyNetworkCache(eq(mCellNetworkAgent.getNetwork().netId)); |
| |
| // Disconnect wifi |
| ExpectedBroadcast b = expectConnectivityAction(TYPE_WIFI, DetailedState.DISCONNECTED); |
| mWiFiNetworkAgent.disconnect(); |
| b.expectBroadcast(); |
| verify(mMockNetd, times(1)).idletimerRemoveInterface(eq(WIFI_IFNAME), anyInt(), |
| eq(Integer.toString(TRANSPORT_WIFI))); |
| |
| // Clean up |
| mCm.unregisterNetworkCallback(networkCallback); |
| } |
| |
| private void verifyTcpBufferSizeChange(String tcpBufferSizes) throws Exception { |
| String[] values = tcpBufferSizes.split(","); |
| String rmemValues = String.join(" ", values[0], values[1], values[2]); |
| String wmemValues = String.join(" ", values[3], values[4], values[5]); |
| verify(mMockNetd, atLeastOnce()).setTcpRWmemorySize(rmemValues, wmemValues); |
| reset(mMockNetd); |
| } |
| |
| @Test |
| public void testTcpBufferReset() throws Exception { |
| final String testTcpBufferSizes = "1,2,3,4,5,6"; |
| final NetworkRequest networkRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_CELLULAR) |
| .addCapability(NET_CAPABILITY_INTERNET) |
| .build(); |
| final TestNetworkCallback networkCallback = new TestNetworkCallback(); |
| mCm.registerNetworkCallback(networkRequest, networkCallback); |
| |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| reset(mMockNetd); |
| // Switching default network updates TCP buffer sizes. |
| mCellNetworkAgent.connect(false); |
| networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); |
| verifyTcpBufferSizeChange(ConnectivityService.DEFAULT_TCP_BUFFER_SIZES); |
| // Change link Properties should have updated tcp buffer size. |
| LinkProperties lp = new LinkProperties(); |
| lp.setTcpBufferSizes(testTcpBufferSizes); |
| mCellNetworkAgent.sendLinkProperties(lp); |
| networkCallback.expectCallback(CallbackEntry.LINK_PROPERTIES_CHANGED, mCellNetworkAgent); |
| verifyTcpBufferSizeChange(testTcpBufferSizes); |
| // Clean up. |
| mCellNetworkAgent.disconnect(); |
| networkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| networkCallback.assertNoCallback(); |
| mCm.unregisterNetworkCallback(networkCallback); |
| } |
| |
| @Test |
| public void testGetGlobalProxyForNetwork() throws Exception { |
| final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| final Network wifiNetwork = mWiFiNetworkAgent.getNetwork(); |
| when(mService.mProxyTracker.getGlobalProxy()).thenReturn(testProxyInfo); |
| assertEquals(testProxyInfo, mService.getProxyForNetwork(wifiNetwork)); |
| } |
| |
| @Test |
| public void testGetProxyForActiveNetwork() throws Exception { |
| final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(true); |
| waitForIdle(); |
| assertNull(mService.getProxyForNetwork(null)); |
| |
| final LinkProperties testLinkProperties = new LinkProperties(); |
| testLinkProperties.setHttpProxy(testProxyInfo); |
| |
| mWiFiNetworkAgent.sendLinkProperties(testLinkProperties); |
| waitForIdle(); |
| |
| assertEquals(testProxyInfo, mService.getProxyForNetwork(null)); |
| } |
| |
| @Test |
| public void testGetProxyForVPN() throws Exception { |
| final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); |
| |
| // Set up a WiFi network with no proxy |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(true); |
| waitForIdle(); |
| assertNull(mService.getProxyForNetwork(null)); |
| |
| // Connect a VPN network with a proxy. |
| LinkProperties testLinkProperties = new LinkProperties(); |
| testLinkProperties.setHttpProxy(testProxyInfo); |
| mMockVpn.establishForMyUid(testLinkProperties); |
| assertUidRangesUpdatedForMyUid(true); |
| |
| // Test that the VPN network returns a proxy, and the WiFi does not. |
| assertEquals(testProxyInfo, mService.getProxyForNetwork(mMockVpn.getNetwork())); |
| assertEquals(testProxyInfo, mService.getProxyForNetwork(null)); |
| assertNull(mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork())); |
| |
| // Test that the VPN network returns no proxy when it is set to null. |
| testLinkProperties.setHttpProxy(null); |
| mMockVpn.sendLinkProperties(testLinkProperties); |
| waitForIdle(); |
| assertNull(mService.getProxyForNetwork(mMockVpn.getNetwork())); |
| assertNull(mService.getProxyForNetwork(null)); |
| |
| // Set WiFi proxy and check that the vpn proxy is still null. |
| testLinkProperties.setHttpProxy(testProxyInfo); |
| mWiFiNetworkAgent.sendLinkProperties(testLinkProperties); |
| waitForIdle(); |
| assertNull(mService.getProxyForNetwork(null)); |
| |
| // Disconnect from VPN and check that the active network, which is now the WiFi, has the |
| // correct proxy setting. |
| mMockVpn.disconnect(); |
| waitForIdle(); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| assertEquals(testProxyInfo, mService.getProxyForNetwork(mWiFiNetworkAgent.getNetwork())); |
| assertEquals(testProxyInfo, mService.getProxyForNetwork(null)); |
| } |
| |
| @Test |
| public void testFullyRoutedVpnResultsInInterfaceFilteringRules() throws Exception { |
| LinkProperties lp = new LinkProperties(); |
| lp.setInterfaceName("tun0"); |
| lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); |
| lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE)); |
| // The uid range needs to cover the test app so the network is visible to it. |
| final Set<UidRange> vpnRange = Collections.singleton(PRIMARY_UIDRANGE); |
| mMockVpn.establish(lp, VPN_UID, vpnRange); |
| assertVpnUidRangesUpdated(true, vpnRange, VPN_UID); |
| |
| // A connected VPN should have interface rules set up. There are two expected invocations, |
| // one during the VPN initial connection, one during the VPN LinkProperties update. |
| ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class); |
| verify(mMockNetd, times(2)).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture()); |
| assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID); |
| assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID); |
| assertTrue(mService.mPermissionMonitor.getVpnUidRanges("tun0").equals(vpnRange)); |
| |
| mMockVpn.disconnect(); |
| waitForIdle(); |
| |
| // Disconnected VPN should have interface rules removed |
| verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture()); |
| assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID); |
| assertNull(mService.mPermissionMonitor.getVpnUidRanges("tun0")); |
| } |
| |
| @Test |
| public void testLegacyVpnDoesNotResultInInterfaceFilteringRule() throws Exception { |
| LinkProperties lp = new LinkProperties(); |
| lp.setInterfaceName("tun0"); |
| lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); |
| lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); |
| // The uid range needs to cover the test app so the network is visible to it. |
| final Set<UidRange> vpnRange = Collections.singleton(PRIMARY_UIDRANGE); |
| mMockVpn.establish(lp, Process.SYSTEM_UID, vpnRange); |
| assertVpnUidRangesUpdated(true, vpnRange, Process.SYSTEM_UID); |
| |
| // Legacy VPN should not have interface rules set up |
| verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any()); |
| } |
| |
| @Test |
| public void testLocalIpv4OnlyVpnDoesNotResultInInterfaceFilteringRule() |
| throws Exception { |
| LinkProperties lp = new LinkProperties(); |
| lp.setInterfaceName("tun0"); |
| lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, "tun0")); |
| lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), RTN_UNREACHABLE)); |
| // The uid range needs to cover the test app so the network is visible to it. |
| final Set<UidRange> vpnRange = Collections.singleton(PRIMARY_UIDRANGE); |
| mMockVpn.establish(lp, Process.SYSTEM_UID, vpnRange); |
| assertVpnUidRangesUpdated(true, vpnRange, Process.SYSTEM_UID); |
| |
| // IPv6 unreachable route should not be misinterpreted as a default route |
| verify(mMockNetd, never()).firewallAddUidInterfaceRules(any(), any()); |
| } |
| |
| @Test |
| public void testVpnHandoverChangesInterfaceFilteringRule() throws Exception { |
| LinkProperties lp = new LinkProperties(); |
| lp.setInterfaceName("tun0"); |
| lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); |
| lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); |
| // The uid range needs to cover the test app so the network is visible to it. |
| final Set<UidRange> vpnRange = Collections.singleton(PRIMARY_UIDRANGE); |
| mMockVpn.establish(lp, VPN_UID, vpnRange); |
| assertVpnUidRangesUpdated(true, vpnRange, VPN_UID); |
| |
| // Connected VPN should have interface rules set up. There are two expected invocations, |
| // one during VPN uid update, one during VPN LinkProperties update |
| ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class); |
| verify(mMockNetd, times(2)).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture()); |
| assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID); |
| assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID); |
| |
| reset(mMockNetd); |
| InOrder inOrder = inOrder(mMockNetd); |
| lp.setInterfaceName("tun1"); |
| mMockVpn.sendLinkProperties(lp); |
| waitForIdle(); |
| // VPN handover (switch to a new interface) should result in rules being updated (old rules |
| // removed first, then new rules added) |
| inOrder.verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture()); |
| assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID); |
| inOrder.verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun1"), uidCaptor.capture()); |
| assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID); |
| |
| reset(mMockNetd); |
| lp = new LinkProperties(); |
| lp.setInterfaceName("tun1"); |
| lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, "tun1")); |
| mMockVpn.sendLinkProperties(lp); |
| waitForIdle(); |
| // VPN not routing everything should no longer have interface filtering rules |
| verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture()); |
| assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID); |
| |
| reset(mMockNetd); |
| lp = new LinkProperties(); |
| lp.setInterfaceName("tun1"); |
| lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE)); |
| lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); |
| mMockVpn.sendLinkProperties(lp); |
| waitForIdle(); |
| // Back to routing all IPv6 traffic should have filtering rules |
| verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun1"), uidCaptor.capture()); |
| 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(); |
| lp.setInterfaceName("tun0"); |
| lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), RTN_UNREACHABLE)); |
| lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); |
| // The uid range needs to cover the test app so the network is visible to it. |
| final UidRange vpnRange = PRIMARY_UIDRANGE; |
| final Set<UidRange> vpnRanges = Collections.singleton(vpnRange); |
| mMockVpn.establish(lp, VPN_UID, vpnRanges); |
| assertVpnUidRangesUpdated(true, vpnRanges, VPN_UID); |
| |
| reset(mMockNetd); |
| InOrder inOrder = inOrder(mMockNetd); |
| |
| // Update to new range which is old range minus APP1, i.e. only APP2 |
| final Set<UidRange> newRanges = new HashSet<>(Arrays.asList( |
| new UidRange(vpnRange.start, APP1_UID - 1), |
| new UidRange(APP1_UID + 1, vpnRange.stop))); |
| mMockVpn.setUids(newRanges); |
| waitForIdle(); |
| |
| ArgumentCaptor<int[]> uidCaptor = ArgumentCaptor.forClass(int[].class); |
| // Verify old rules are removed before new rules are added |
| inOrder.verify(mMockNetd).firewallRemoveUidInterfaceRules(uidCaptor.capture()); |
| assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID); |
| inOrder.verify(mMockNetd).firewallAddUidInterfaceRules(eq("tun0"), uidCaptor.capture()); |
| assertContainsExactly(uidCaptor.getValue(), APP2_UID); |
| } |
| |
| @Test |
| public void testLinkPropertiesWithWakeOnLanForActiveNetwork() throws Exception { |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| |
| LinkProperties wifiLp = new LinkProperties(); |
| wifiLp.setInterfaceName(WIFI_WOL_IFNAME); |
| wifiLp.setWakeOnLanSupported(false); |
| |
| // Default network switch should update ifaces. |
| mWiFiNetworkAgent.connect(false); |
| mWiFiNetworkAgent.sendLinkProperties(wifiLp); |
| waitForIdle(); |
| |
| // ConnectivityService should have changed the WakeOnLanSupported to true |
| wifiLp.setWakeOnLanSupported(true); |
| assertEquals(wifiLp, mService.getActiveLinkProperties()); |
| } |
| |
| @Test |
| public void testLegacyExtraInfoSentToNetworkMonitor() throws Exception { |
| class TestNetworkAgent extends NetworkAgent { |
| TestNetworkAgent(Context context, Looper looper, NetworkAgentConfig config) { |
| super(context, looper, "MockAgent", new NetworkCapabilities(), |
| new LinkProperties(), 40 , config, null /* provider */); |
| } |
| } |
| final NetworkAgent naNoExtraInfo = new TestNetworkAgent( |
| mServiceContext, mCsHandlerThread.getLooper(), new NetworkAgentConfig()); |
| naNoExtraInfo.register(); |
| verify(mNetworkStack).makeNetworkMonitor(any(), isNull(String.class), any()); |
| naNoExtraInfo.unregister(); |
| |
| reset(mNetworkStack); |
| final NetworkAgentConfig config = |
| new NetworkAgentConfig.Builder().setLegacyExtraInfo("legacyinfo").build(); |
| final NetworkAgent naExtraInfo = new TestNetworkAgent( |
| mServiceContext, mCsHandlerThread.getLooper(), config); |
| naExtraInfo.register(); |
| verify(mNetworkStack).makeNetworkMonitor(any(), eq("legacyinfo"), any()); |
| naExtraInfo.unregister(); |
| } |
| |
| // To avoid granting location permission bypass. |
| private void denyAllLocationPrivilegedPermissions() { |
| mServiceContext.setPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, |
| PERMISSION_DENIED); |
| mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_DENIED); |
| mServiceContext.setPermission(NETWORK_STACK, PERMISSION_DENIED); |
| mServiceContext.setPermission(Manifest.permission.NETWORK_SETUP_WIZARD, |
| PERMISSION_DENIED); |
| } |
| |
| private void setupLocationPermissions( |
| int targetSdk, boolean locationToggle, String op, String perm) throws Exception { |
| denyAllLocationPrivilegedPermissions(); |
| |
| final ApplicationInfo applicationInfo = new ApplicationInfo(); |
| applicationInfo.targetSdkVersion = targetSdk; |
| when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), any())) |
| .thenReturn(applicationInfo); |
| when(mPackageManager.getTargetSdkVersion(any())).thenReturn(targetSdk); |
| |
| when(mLocationManager.isLocationEnabledForUser(any())).thenReturn(locationToggle); |
| |
| if (op != null) { |
| when(mAppOpsManager.noteOp(eq(op), eq(Process.myUid()), |
| eq(mContext.getPackageName()), eq(getAttributionTag()), anyString())) |
| .thenReturn(AppOpsManager.MODE_ALLOWED); |
| } |
| |
| if (perm != null) { |
| mServiceContext.setPermission(perm, PERMISSION_GRANTED); |
| } |
| } |
| |
| private int getOwnerUidNetCapsPermission(int ownerUid, int callerUid, |
| boolean includeLocationSensitiveInfo) { |
| final NetworkCapabilities netCap = new NetworkCapabilities().setOwnerUid(ownerUid); |
| |
| return mService.createWithLocationInfoSanitizedIfNecessaryWhenParceled( |
| netCap, includeLocationSensitiveInfo, Process.myUid(), callerUid, |
| mContext.getPackageName(), getAttributionTag()) |
| .getOwnerUid(); |
| } |
| |
| private void verifyTransportInfoCopyNetCapsPermission( |
| int callerUid, boolean includeLocationSensitiveInfo, |
| boolean shouldMakeCopyWithLocationSensitiveFieldsParcelable) { |
| final TransportInfo transportInfo = mock(TransportInfo.class); |
| when(transportInfo.getApplicableRedactions()).thenReturn(REDACT_FOR_ACCESS_FINE_LOCATION); |
| final NetworkCapabilities netCap = |
| new NetworkCapabilities().setTransportInfo(transportInfo); |
| |
| mService.createWithLocationInfoSanitizedIfNecessaryWhenParceled( |
| netCap, includeLocationSensitiveInfo, Process.myPid(), callerUid, |
| mContext.getPackageName(), getAttributionTag()); |
| if (shouldMakeCopyWithLocationSensitiveFieldsParcelable) { |
| verify(transportInfo).makeCopy(REDACT_NONE); |
| } else { |
| verify(transportInfo).makeCopy(REDACT_FOR_ACCESS_FINE_LOCATION); |
| } |
| } |
| |
| private void verifyOwnerUidAndTransportInfoNetCapsPermission( |
| boolean shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag, |
| boolean shouldInclLocationSensitiveOwnerUidWithIncludeFlag, |
| boolean shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag, |
| boolean shouldInclLocationSensitiveTransportInfoWithIncludeFlag) { |
| final int myUid = Process.myUid(); |
| |
| final int expectedOwnerUidWithoutIncludeFlag = |
| shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag |
| ? myUid : INVALID_UID; |
| assertEquals(expectedOwnerUidWithoutIncludeFlag, getOwnerUidNetCapsPermission( |
| myUid, myUid, false /* includeLocationSensitiveInfo */)); |
| |
| final int expectedOwnerUidWithIncludeFlag = |
| shouldInclLocationSensitiveOwnerUidWithIncludeFlag ? myUid : INVALID_UID; |
| assertEquals(expectedOwnerUidWithIncludeFlag, getOwnerUidNetCapsPermission( |
| myUid, myUid, true /* includeLocationSensitiveInfo */)); |
| |
| verifyTransportInfoCopyNetCapsPermission(myUid, |
| false, /* includeLocationSensitiveInfo */ |
| shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag); |
| |
| verifyTransportInfoCopyNetCapsPermission(myUid, |
| true, /* includeLocationSensitiveInfo */ |
| shouldInclLocationSensitiveTransportInfoWithIncludeFlag); |
| |
| } |
| |
| private void verifyOwnerUidAndTransportInfoNetCapsPermissionPreS() { |
| verifyOwnerUidAndTransportInfoNetCapsPermission( |
| // Ensure that owner uid is included even if the request asks to remove it (which is |
| // the default) since the app has necessary permissions and targetSdk < S. |
| true, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */ |
| true, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */ |
| // Ensure that location info is removed if the request asks to remove it even if the |
| // app has necessary permissions. |
| false, /* shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag */ |
| true /* shouldInclLocationSensitiveTransportInfoWithIncludeFlag */ |
| ); |
| } |
| |
| @Test |
| public void testCreateWithLocationInfoSanitizedWithFineLocationAfterQPreS() |
| throws Exception { |
| setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, |
| Manifest.permission.ACCESS_FINE_LOCATION); |
| |
| verifyOwnerUidAndTransportInfoNetCapsPermissionPreS(); |
| } |
| |
| @Test |
| public void testCreateWithLocationInfoSanitizedWithFineLocationPreSWithAndWithoutCallbackFlag() |
| throws Exception { |
| setupLocationPermissions(Build.VERSION_CODES.R, true, AppOpsManager.OPSTR_FINE_LOCATION, |
| Manifest.permission.ACCESS_FINE_LOCATION); |
| |
| verifyOwnerUidAndTransportInfoNetCapsPermissionPreS(); |
| } |
| |
| @Test |
| public void |
| testCreateWithLocationInfoSanitizedWithFineLocationAfterSWithAndWithoutCallbackFlag() |
| throws Exception { |
| setupLocationPermissions(Build.VERSION_CODES.S, true, AppOpsManager.OPSTR_FINE_LOCATION, |
| Manifest.permission.ACCESS_FINE_LOCATION); |
| |
| verifyOwnerUidAndTransportInfoNetCapsPermission( |
| // Ensure that the owner UID is removed if the request asks us to remove it even |
| // if the app has necessary permissions since targetSdk >= S. |
| false, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */ |
| true, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */ |
| // Ensure that location info is removed if the request asks to remove it even if the |
| // app has necessary permissions. |
| false, /* shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag */ |
| true /* shouldInclLocationSensitiveTransportInfoWithIncludeFlag */ |
| ); |
| } |
| |
| @Test |
| public void testCreateWithLocationInfoSanitizedWithCoarseLocationPreQ() |
| throws Exception { |
| setupLocationPermissions(Build.VERSION_CODES.P, true, AppOpsManager.OPSTR_COARSE_LOCATION, |
| Manifest.permission.ACCESS_COARSE_LOCATION); |
| |
| verifyOwnerUidAndTransportInfoNetCapsPermissionPreS(); |
| } |
| |
| private void verifyOwnerUidAndTransportInfoNetCapsNotIncluded() { |
| verifyOwnerUidAndTransportInfoNetCapsPermission( |
| false, /* shouldInclLocationSensitiveOwnerUidWithoutIncludeFlag */ |
| false, /* shouldInclLocationSensitiveOwnerUidWithIncludeFlag */ |
| false, /* shouldInclLocationSensitiveTransportInfoWithoutIncludeFlag */ |
| false /* shouldInclLocationSensitiveTransportInfoWithIncludeFlag */ |
| ); |
| } |
| |
| @Test |
| public void testCreateWithLocationInfoSanitizedLocationOff() throws Exception { |
| // Test that even with fine location permission, and UIDs matching, the UID is sanitized. |
| setupLocationPermissions(Build.VERSION_CODES.Q, false, AppOpsManager.OPSTR_FINE_LOCATION, |
| Manifest.permission.ACCESS_FINE_LOCATION); |
| |
| verifyOwnerUidAndTransportInfoNetCapsNotIncluded(); |
| } |
| |
| @Test |
| public void testCreateWithLocationInfoSanitizedWrongUid() throws Exception { |
| // Test that even with fine location permission, not being the owner leads to sanitization. |
| setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, |
| Manifest.permission.ACCESS_FINE_LOCATION); |
| |
| final int myUid = Process.myUid(); |
| assertEquals(Process.INVALID_UID, |
| getOwnerUidNetCapsPermission(myUid + 1, myUid, |
| true /* includeLocationSensitiveInfo */)); |
| } |
| |
| @Test |
| public void testCreateWithLocationInfoSanitizedWithCoarseLocationAfterQ() |
| throws Exception { |
| // Test that not having fine location permission leads to sanitization. |
| setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_COARSE_LOCATION, |
| Manifest.permission.ACCESS_COARSE_LOCATION); |
| |
| verifyOwnerUidAndTransportInfoNetCapsNotIncluded(); |
| } |
| |
| @Test |
| public void testCreateWithLocationInfoSanitizedWithCoarseLocationAfterS() |
| throws Exception { |
| // Test that not having fine location permission leads to sanitization. |
| setupLocationPermissions(Build.VERSION_CODES.S, true, AppOpsManager.OPSTR_COARSE_LOCATION, |
| Manifest.permission.ACCESS_COARSE_LOCATION); |
| |
| verifyOwnerUidAndTransportInfoNetCapsNotIncluded(); |
| } |
| |
| @Test |
| public void testCreateForCallerWithLocalMacAddressSanitizedWithLocalMacAddressPermission() |
| throws Exception { |
| mServiceContext.setPermission(Manifest.permission.LOCAL_MAC_ADDRESS, PERMISSION_GRANTED); |
| |
| final TransportInfo transportInfo = mock(TransportInfo.class); |
| when(transportInfo.getApplicableRedactions()) |
| .thenReturn(REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_LOCAL_MAC_ADDRESS); |
| final NetworkCapabilities netCap = |
| new NetworkCapabilities().setTransportInfo(transportInfo); |
| |
| mService.createWithLocationInfoSanitizedIfNecessaryWhenParceled( |
| netCap, false /* includeLocationSensitiveInfoInTransportInfo */, |
| Process.myPid(), Process.myUid(), |
| mContext.getPackageName(), getAttributionTag()); |
| // don't redact MAC_ADDRESS fields, only location sensitive fields. |
| verify(transportInfo).makeCopy(REDACT_FOR_ACCESS_FINE_LOCATION); |
| } |
| |
| @Test |
| public void testCreateForCallerWithLocalMacAddressSanitizedWithoutLocalMacAddressPermission() |
| throws Exception { |
| mServiceContext.setPermission(Manifest.permission.LOCAL_MAC_ADDRESS, PERMISSION_DENIED); |
| |
| final TransportInfo transportInfo = mock(TransportInfo.class); |
| when(transportInfo.getApplicableRedactions()) |
| .thenReturn(REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_LOCAL_MAC_ADDRESS); |
| final NetworkCapabilities netCap = |
| new NetworkCapabilities().setTransportInfo(transportInfo); |
| |
| mService.createWithLocationInfoSanitizedIfNecessaryWhenParceled( |
| netCap, false /* includeLocationSensitiveInfoInTransportInfo */, |
| Process.myPid(), Process.myUid(), |
| mContext.getPackageName(), getAttributionTag()); |
| // redact both MAC_ADDRESS & location sensitive fields. |
| verify(transportInfo).makeCopy(REDACT_FOR_ACCESS_FINE_LOCATION |
| | REDACT_FOR_LOCAL_MAC_ADDRESS); |
| } |
| |
| @Test |
| public void testCreateForCallerWithLocalMacAddressSanitizedWithSettingsPermission() |
| throws Exception { |
| mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED); |
| |
| final TransportInfo transportInfo = mock(TransportInfo.class); |
| when(transportInfo.getApplicableRedactions()) |
| .thenReturn(REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_NETWORK_SETTINGS); |
| final NetworkCapabilities netCap = |
| new NetworkCapabilities().setTransportInfo(transportInfo); |
| |
| mService.createWithLocationInfoSanitizedIfNecessaryWhenParceled( |
| netCap, false /* includeLocationSensitiveInfoInTransportInfo */, |
| Process.myPid(), Process.myUid(), |
| mContext.getPackageName(), getAttributionTag()); |
| // don't redact NETWORK_SETTINGS fields, only location sensitive fields. |
| verify(transportInfo).makeCopy(REDACT_FOR_ACCESS_FINE_LOCATION); |
| } |
| |
| @Test |
| public void testCreateForCallerWithLocalMacAddressSanitizedWithoutSettingsPermission() |
| throws Exception { |
| mServiceContext.setPermission(Manifest.permission.LOCAL_MAC_ADDRESS, PERMISSION_DENIED); |
| |
| final TransportInfo transportInfo = mock(TransportInfo.class); |
| when(transportInfo.getApplicableRedactions()) |
| .thenReturn(REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_NETWORK_SETTINGS); |
| final NetworkCapabilities netCap = |
| new NetworkCapabilities().setTransportInfo(transportInfo); |
| |
| mService.createWithLocationInfoSanitizedIfNecessaryWhenParceled( |
| netCap, false /* includeLocationSensitiveInfoInTransportInfo */, |
| Process.myPid(), Process.myUid(), |
| mContext.getPackageName(), getAttributionTag()); |
| // redact both NETWORK_SETTINGS & location sensitive fields. |
| verify(transportInfo).makeCopy( |
| REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_NETWORK_SETTINGS); |
| } |
| |
| /** |
| * Test TransportInfo to verify redaction mechanism. |
| */ |
| private static class TestTransportInfo implements TransportInfo { |
| public final boolean locationRedacted; |
| public final boolean localMacAddressRedacted; |
| public final boolean settingsRedacted; |
| |
| TestTransportInfo() { |
| locationRedacted = false; |
| localMacAddressRedacted = false; |
| settingsRedacted = false; |
| } |
| |
| TestTransportInfo(boolean locationRedacted, boolean localMacAddressRedacted, |
| boolean settingsRedacted) { |
| this.locationRedacted = locationRedacted; |
| this.localMacAddressRedacted = |
| localMacAddressRedacted; |
| this.settingsRedacted = settingsRedacted; |
| } |
| |
| @Override |
| public TransportInfo makeCopy(@NetworkCapabilities.RedactionType long redactions) { |
| return new TestTransportInfo( |
| locationRedacted | (redactions & REDACT_FOR_ACCESS_FINE_LOCATION) != 0, |
| localMacAddressRedacted | (redactions & REDACT_FOR_LOCAL_MAC_ADDRESS) != 0, |
| settingsRedacted | (redactions & REDACT_FOR_NETWORK_SETTINGS) != 0 |
| ); |
| } |
| |
| @Override |
| public @NetworkCapabilities.RedactionType long getApplicableRedactions() { |
| return REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_LOCAL_MAC_ADDRESS |
| | REDACT_FOR_NETWORK_SETTINGS; |
| } |
| |
| @Override |
| public boolean equals(Object other) { |
| if (!(other instanceof TestTransportInfo)) return false; |
| TestTransportInfo that = (TestTransportInfo) other; |
| return that.locationRedacted == this.locationRedacted |
| && that.localMacAddressRedacted == this.localMacAddressRedacted |
| && that.settingsRedacted == this.settingsRedacted; |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(locationRedacted, localMacAddressRedacted, settingsRedacted); |
| } |
| |
| @Override |
| public String toString() { |
| return String.format( |
| "TestTransportInfo{locationRedacted=%s macRedacted=%s settingsRedacted=%s}", |
| locationRedacted, localMacAddressRedacted, settingsRedacted); |
| } |
| } |
| |
| private TestTransportInfo getTestTransportInfo(NetworkCapabilities nc) { |
| return (TestTransportInfo) nc.getTransportInfo(); |
| } |
| |
| private TestTransportInfo getTestTransportInfo(TestNetworkAgentWrapper n) { |
| final NetworkCapabilities nc = mCm.getNetworkCapabilities(n.getNetwork()); |
| assertNotNull(nc); |
| return getTestTransportInfo(nc); |
| } |
| |
| |
| private void verifyNetworkCallbackLocationDataInclusionUsingTransportInfoAndOwnerUidInNetCaps( |
| @NonNull TestNetworkCallback wifiNetworkCallback, int actualOwnerUid, |
| @NonNull TransportInfo actualTransportInfo, int expectedOwnerUid, |
| @NonNull TransportInfo expectedTransportInfo) throws Exception { |
| when(mPackageManager.getTargetSdkVersion(anyString())).thenReturn(Build.VERSION_CODES.S); |
| final NetworkCapabilities ncTemplate = |
| new NetworkCapabilities() |
| .addTransportType(TRANSPORT_WIFI) |
| .setOwnerUid(actualOwnerUid); |
| |
| final NetworkRequest wifiRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_WIFI).build(); |
| mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback); |
| |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, new LinkProperties(), |
| ncTemplate); |
| mWiFiNetworkAgent.connect(false); |
| |
| wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| |
| // Send network capabilities update with TransportInfo to trigger capabilities changed |
| // callback. |
| mWiFiNetworkAgent.setNetworkCapabilities( |
| ncTemplate.setTransportInfo(actualTransportInfo), true); |
| |
| wifiNetworkCallback.expectCapabilitiesThat(mWiFiNetworkAgent, |
| nc -> Objects.equals(expectedOwnerUid, nc.getOwnerUid()) |
| && Objects.equals(expectedTransportInfo, nc.getTransportInfo())); |
| } |
| |
| @Test |
| public void testVerifyLocationDataIsNotIncludedWhenInclFlagNotSet() throws Exception { |
| final TestNetworkCallback wifiNetworkCallack = new TestNetworkCallback(); |
| final int ownerUid = Process.myUid(); |
| final TransportInfo transportInfo = new TestTransportInfo(); |
| // Even though the test uid holds privileged permissions, mask location fields since |
| // the callback did not explicitly opt-in to get location data. |
| final TransportInfo sanitizedTransportInfo = new TestTransportInfo( |
| true, /* locationRedacted */ |
| true, /* localMacAddressRedacted */ |
| true /* settingsRedacted */ |
| ); |
| // Should not expect location data since the callback does not set the flag for including |
| // location data. |
| verifyNetworkCallbackLocationDataInclusionUsingTransportInfoAndOwnerUidInNetCaps( |
| wifiNetworkCallack, ownerUid, transportInfo, INVALID_UID, sanitizedTransportInfo); |
| } |
| |
| @Test |
| public void testTransportInfoRedactionInSynchronousCalls() throws Exception { |
| final NetworkCapabilities ncTemplate = new NetworkCapabilities() |
| .addTransportType(TRANSPORT_WIFI) |
| .setTransportInfo(new TestTransportInfo()); |
| |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, new LinkProperties(), |
| ncTemplate); |
| mWiFiNetworkAgent.connect(true /* validated; waits for callback */); |
| |
| // NETWORK_SETTINGS redaction is controlled by the NETWORK_SETTINGS permission |
| assertTrue(getTestTransportInfo(mWiFiNetworkAgent).settingsRedacted); |
| withPermission(NETWORK_SETTINGS, () -> { |
| assertFalse(getTestTransportInfo(mWiFiNetworkAgent).settingsRedacted); |
| }); |
| assertTrue(getTestTransportInfo(mWiFiNetworkAgent).settingsRedacted); |
| |
| // LOCAL_MAC_ADDRESS redaction is controlled by the LOCAL_MAC_ADDRESS permission |
| assertTrue(getTestTransportInfo(mWiFiNetworkAgent).localMacAddressRedacted); |
| withPermission(LOCAL_MAC_ADDRESS, () -> { |
| assertFalse(getTestTransportInfo(mWiFiNetworkAgent).localMacAddressRedacted); |
| }); |
| assertTrue(getTestTransportInfo(mWiFiNetworkAgent).localMacAddressRedacted); |
| |
| // Synchronous getNetworkCapabilities calls never return unredacted location-sensitive |
| // information. |
| assertTrue(getTestTransportInfo(mWiFiNetworkAgent).locationRedacted); |
| setupLocationPermissions(Build.VERSION_CODES.S, true, AppOpsManager.OPSTR_FINE_LOCATION, |
| Manifest.permission.ACCESS_FINE_LOCATION); |
| assertTrue(getTestTransportInfo(mWiFiNetworkAgent).locationRedacted); |
| denyAllLocationPrivilegedPermissions(); |
| assertTrue(getTestTransportInfo(mWiFiNetworkAgent).locationRedacted); |
| } |
| |
| private void setupConnectionOwnerUid(int vpnOwnerUid, @VpnManager.VpnType int vpnType) |
| throws Exception { |
| final Set<UidRange> vpnRange = Collections.singleton(PRIMARY_UIDRANGE); |
| mMockVpn.setVpnType(vpnType); |
| mMockVpn.establish(new LinkProperties(), vpnOwnerUid, vpnRange); |
| assertVpnUidRangesUpdated(true, vpnRange, vpnOwnerUid); |
| |
| final UnderlyingNetworkInfo underlyingNetworkInfo = |
| new UnderlyingNetworkInfo(vpnOwnerUid, VPN_IFNAME, new ArrayList<String>()); |
| mMockVpn.setUnderlyingNetworkInfo(underlyingNetworkInfo); |
| when(mDeps.getConnectionOwnerUid(anyInt(), any(), any())).thenReturn(42); |
| } |
| |
| private void setupConnectionOwnerUidAsVpnApp(int vpnOwnerUid, @VpnManager.VpnType int vpnType) |
| throws Exception { |
| setupConnectionOwnerUid(vpnOwnerUid, vpnType); |
| |
| // Test as VPN app |
| mServiceContext.setPermission(NETWORK_STACK, PERMISSION_DENIED); |
| mServiceContext.setPermission( |
| NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, PERMISSION_DENIED); |
| } |
| |
| private ConnectionInfo getTestConnectionInfo() throws Exception { |
| return new ConnectionInfo( |
| IPPROTO_TCP, |
| new InetSocketAddress(InetAddresses.parseNumericAddress("1.2.3.4"), 1234), |
| new InetSocketAddress(InetAddresses.parseNumericAddress("2.3.4.5"), 2345)); |
| } |
| |
| @Test |
| public void testGetConnectionOwnerUidPlatformVpn() throws Exception { |
| final int myUid = Process.myUid(); |
| setupConnectionOwnerUidAsVpnApp(myUid, VpnManager.TYPE_VPN_PLATFORM); |
| |
| assertEquals(INVALID_UID, mService.getConnectionOwnerUid(getTestConnectionInfo())); |
| } |
| |
| @Test |
| public void testGetConnectionOwnerUidVpnServiceWrongUser() throws Exception { |
| final int myUid = Process.myUid(); |
| setupConnectionOwnerUidAsVpnApp(myUid + 1, VpnManager.TYPE_VPN_SERVICE); |
| |
| assertEquals(INVALID_UID, mService.getConnectionOwnerUid(getTestConnectionInfo())); |
| } |
| |
| @Test |
| public void testGetConnectionOwnerUidVpnServiceDoesNotThrow() throws Exception { |
| final int myUid = Process.myUid(); |
| setupConnectionOwnerUidAsVpnApp(myUid, VpnManager.TYPE_VPN_SERVICE); |
| |
| assertEquals(42, mService.getConnectionOwnerUid(getTestConnectionInfo())); |
| } |
| |
| @Test |
| public void testGetConnectionOwnerUidVpnServiceNetworkStackDoesNotThrow() throws Exception { |
| final int myUid = Process.myUid(); |
| setupConnectionOwnerUid(myUid, VpnManager.TYPE_VPN_SERVICE); |
| mServiceContext.setPermission(NETWORK_STACK, PERMISSION_GRANTED); |
| |
| assertEquals(42, mService.getConnectionOwnerUid(getTestConnectionInfo())); |
| } |
| |
| @Test |
| public void testGetConnectionOwnerUidVpnServiceMainlineNetworkStackDoesNotThrow() |
| throws Exception { |
| final int myUid = Process.myUid(); |
| setupConnectionOwnerUid(myUid, VpnManager.TYPE_VPN_SERVICE); |
| mServiceContext.setPermission( |
| NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, PERMISSION_GRANTED); |
| |
| assertEquals(42, mService.getConnectionOwnerUid(getTestConnectionInfo())); |
| } |
| |
| private static PackageInfo buildPackageInfo(boolean hasSystemPermission, int uid) { |
| final PackageInfo packageInfo = new PackageInfo(); |
| if (hasSystemPermission) { |
| packageInfo.requestedPermissions = new String[] { |
| CHANGE_NETWORK_STATE, CONNECTIVITY_USE_RESTRICTED_NETWORKS }; |
| packageInfo.requestedPermissionsFlags = new int[] { |
| REQUESTED_PERMISSION_GRANTED, REQUESTED_PERMISSION_GRANTED }; |
| } else { |
| packageInfo.requestedPermissions = new String[0]; |
| } |
| packageInfo.applicationInfo = new ApplicationInfo(); |
| packageInfo.applicationInfo.privateFlags = 0; |
| packageInfo.applicationInfo.uid = UserHandle.getUid(UserHandle.USER_SYSTEM, |
| UserHandle.getAppId(uid)); |
| return packageInfo; |
| } |
| |
| @Test |
| public void testRegisterConnectivityDiagnosticsCallbackInvalidRequest() throws Exception { |
| final NetworkRequest request = |
| new NetworkRequest( |
| new NetworkCapabilities(), TYPE_ETHERNET, 0, NetworkRequest.Type.NONE); |
| try { |
| mService.registerConnectivityDiagnosticsCallback( |
| mConnectivityDiagnosticsCallback, request, mContext.getPackageName()); |
| fail("registerConnectivityDiagnosticsCallback should throw on invalid NetworkRequest"); |
| } catch (IllegalArgumentException expected) { |
| } |
| } |
| |
| private void assertRouteInfoParcelMatches(RouteInfo route, RouteInfoParcel parcel) { |
| assertEquals(route.getDestination().toString(), parcel.destination); |
| assertEquals(route.getInterface(), parcel.ifName); |
| assertEquals(route.getMtu(), parcel.mtu); |
| |
| switch (route.getType()) { |
| case RouteInfo.RTN_UNICAST: |
| if (route.hasGateway()) { |
| assertEquals(route.getGateway().getHostAddress(), parcel.nextHop); |
| } else { |
| assertEquals(INetd.NEXTHOP_NONE, parcel.nextHop); |
| } |
| break; |
| case RouteInfo.RTN_UNREACHABLE: |
| assertEquals(INetd.NEXTHOP_UNREACHABLE, parcel.nextHop); |
| break; |
| case RouteInfo.RTN_THROW: |
| assertEquals(INetd.NEXTHOP_THROW, parcel.nextHop); |
| break; |
| default: |
| assertEquals(INetd.NEXTHOP_NONE, parcel.nextHop); |
| break; |
| } |
| } |
| |
| private void assertRoutesAdded(int netId, RouteInfo... routes) throws Exception { |
| // TODO: add @JavaDerive(equals=true) to RouteInfoParcel, use eq() directly, and delete |
| // assertRouteInfoParcelMatches above. |
| ArgumentCaptor<RouteInfoParcel> captor = ArgumentCaptor.forClass(RouteInfoParcel.class); |
| verify(mMockNetd, times(routes.length)).networkAddRouteParcel(eq(netId), captor.capture()); |
| for (int i = 0; i < routes.length; i++) { |
| assertRouteInfoParcelMatches(routes[i], captor.getAllValues().get(i)); |
| } |
| } |
| |
| private void assertRoutesRemoved(int netId, RouteInfo... routes) throws Exception { |
| ArgumentCaptor<RouteInfoParcel> captor = ArgumentCaptor.forClass(RouteInfoParcel.class); |
| verify(mMockNetd, times(routes.length)).networkRemoveRouteParcel(eq(netId), |
| captor.capture()); |
| for (int i = 0; i < routes.length; i++) { |
| assertRouteInfoParcelMatches(routes[i], captor.getAllValues().get(i)); |
| } |
| } |
| |
| @Test |
| public void testRegisterUnregisterConnectivityDiagnosticsCallback() throws Exception { |
| final NetworkRequest wifiRequest = |
| new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build(); |
| when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder); |
| |
| mService.registerConnectivityDiagnosticsCallback( |
| mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName()); |
| |
| // Block until all other events are done processing. |
| HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS); |
| |
| verify(mIBinder).linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); |
| verify(mConnectivityDiagnosticsCallback).asBinder(); |
| assertTrue(mService.mConnectivityDiagnosticsCallbacks.containsKey(mIBinder)); |
| |
| mService.unregisterConnectivityDiagnosticsCallback(mConnectivityDiagnosticsCallback); |
| verify(mIBinder, timeout(TIMEOUT_MS)) |
| .unlinkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); |
| assertFalse(mService.mConnectivityDiagnosticsCallbacks.containsKey(mIBinder)); |
| verify(mConnectivityDiagnosticsCallback, atLeastOnce()).asBinder(); |
| } |
| |
| @Test |
| public void testRegisterDuplicateConnectivityDiagnosticsCallback() throws Exception { |
| final NetworkRequest wifiRequest = |
| new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build(); |
| when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder); |
| |
| mService.registerConnectivityDiagnosticsCallback( |
| mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName()); |
| |
| // Block until all other events are done processing. |
| HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS); |
| |
| verify(mIBinder).linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); |
| verify(mConnectivityDiagnosticsCallback).asBinder(); |
| assertTrue(mService.mConnectivityDiagnosticsCallbacks.containsKey(mIBinder)); |
| |
| // Register the same callback again |
| mService.registerConnectivityDiagnosticsCallback( |
| mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName()); |
| |
| // Block until all other events are done processing. |
| HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS); |
| |
| assertTrue(mService.mConnectivityDiagnosticsCallbacks.containsKey(mIBinder)); |
| } |
| |
| public NetworkAgentInfo fakeMobileNai(NetworkCapabilities nc) { |
| final NetworkCapabilities cellNc = new NetworkCapabilities.Builder(nc) |
| .addTransportType(TRANSPORT_CELLULAR).build(); |
| final NetworkInfo info = new NetworkInfo(TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_LTE, |
| ConnectivityManager.getNetworkTypeName(TYPE_MOBILE), |
| TelephonyManager.getNetworkTypeName(TelephonyManager.NETWORK_TYPE_LTE)); |
| return fakeNai(cellNc, info); |
| } |
| |
| private NetworkAgentInfo fakeWifiNai(NetworkCapabilities nc) { |
| final NetworkCapabilities wifiNc = new NetworkCapabilities.Builder(nc) |
| .addTransportType(TRANSPORT_WIFI).build(); |
| final NetworkInfo info = new NetworkInfo(TYPE_WIFI, 0 /* subtype */, |
| ConnectivityManager.getNetworkTypeName(TYPE_WIFI), "" /* subtypeName */); |
| return fakeNai(wifiNc, info); |
| } |
| |
| private NetworkAgentInfo fakeNai(NetworkCapabilities nc, NetworkInfo networkInfo) { |
| return new NetworkAgentInfo(null, new Network(NET_ID), networkInfo, new LinkProperties(), |
| nc, new NetworkScore.Builder().setLegacyInt(0).build(), |
| mServiceContext, null, new NetworkAgentConfig(), mService, null, null, 0, |
| INVALID_UID, TEST_LINGER_DELAY_MS, mQosCallbackTracker, |
| new ConnectivityService.Dependencies()); |
| } |
| |
| @Test |
| public void testCheckConnectivityDiagnosticsPermissionsNetworkStack() throws Exception { |
| final NetworkAgentInfo naiWithoutUid = fakeMobileNai(new NetworkCapabilities()); |
| |
| mServiceContext.setPermission(NETWORK_STACK, PERMISSION_GRANTED); |
| assertTrue( |
| "NetworkStack permission not applied", |
| mService.checkConnectivityDiagnosticsPermissions( |
| Process.myPid(), Process.myUid(), naiWithoutUid, |
| mContext.getOpPackageName())); |
| } |
| |
| @Test |
| public void testCheckConnectivityDiagnosticsPermissionsWrongUidPackageName() throws Exception { |
| final int wrongUid = Process.myUid() + 1; |
| |
| final NetworkCapabilities nc = new NetworkCapabilities(); |
| nc.setAdministratorUids(new int[] {wrongUid}); |
| final NetworkAgentInfo naiWithUid = fakeWifiNai(nc); |
| |
| mServiceContext.setPermission(NETWORK_STACK, PERMISSION_DENIED); |
| |
| assertFalse( |
| "Mismatched uid/package name should not pass the location permission check", |
| mService.checkConnectivityDiagnosticsPermissions( |
| Process.myPid() + 1, wrongUid, naiWithUid, mContext.getOpPackageName())); |
| } |
| |
| private void verifyConnectivityDiagnosticsPermissionsWithNetworkAgentInfo( |
| NetworkAgentInfo info, boolean expectPermission) { |
| mServiceContext.setPermission(NETWORK_STACK, PERMISSION_DENIED); |
| |
| assertEquals( |
| "Unexpected ConnDiags permission", |
| expectPermission, |
| mService.checkConnectivityDiagnosticsPermissions( |
| Process.myPid(), Process.myUid(), info, mContext.getOpPackageName())); |
| } |
| |
| @Test |
| public void testCheckConnectivityDiagnosticsPermissionsCellularNoLocationPermission() |
| throws Exception { |
| final NetworkCapabilities nc = new NetworkCapabilities(); |
| nc.setAdministratorUids(new int[] {Process.myUid()}); |
| final NetworkAgentInfo naiWithUid = fakeMobileNai(nc); |
| |
| verifyConnectivityDiagnosticsPermissionsWithNetworkAgentInfo(naiWithUid, |
| true /* expectPermission */); |
| } |
| |
| @Test |
| public void testCheckConnectivityDiagnosticsPermissionsWifiNoLocationPermission() |
| throws Exception { |
| final NetworkCapabilities nc = new NetworkCapabilities(); |
| nc.setAdministratorUids(new int[] {Process.myUid()}); |
| final NetworkAgentInfo naiWithUid = fakeWifiNai(nc); |
| |
| verifyConnectivityDiagnosticsPermissionsWithNetworkAgentInfo(naiWithUid, |
| false /* expectPermission */); |
| } |
| |
| @Test |
| public void testCheckConnectivityDiagnosticsPermissionsActiveVpn() throws Exception { |
| final NetworkAgentInfo naiWithoutUid = fakeMobileNai(new NetworkCapabilities()); |
| |
| mMockVpn.establishForMyUid(); |
| assertUidRangesUpdatedForMyUid(true); |
| |
| // Wait for networks to connect and broadcasts to be sent before removing permissions. |
| waitForIdle(); |
| setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, |
| Manifest.permission.ACCESS_FINE_LOCATION); |
| |
| assertTrue(mMockVpn.setUnderlyingNetworks(new Network[] {naiWithoutUid.network})); |
| waitForIdle(); |
| assertTrue( |
| "Active VPN permission not applied", |
| mService.checkConnectivityDiagnosticsPermissions( |
| Process.myPid(), Process.myUid(), naiWithoutUid, |
| mContext.getOpPackageName())); |
| |
| assertTrue(mMockVpn.setUnderlyingNetworks(null)); |
| waitForIdle(); |
| assertFalse( |
| "VPN shouldn't receive callback on non-underlying network", |
| mService.checkConnectivityDiagnosticsPermissions( |
| Process.myPid(), Process.myUid(), naiWithoutUid, |
| mContext.getOpPackageName())); |
| } |
| |
| @Test |
| public void testCheckConnectivityDiagnosticsPermissionsNetworkAdministrator() throws Exception { |
| final NetworkCapabilities nc = new NetworkCapabilities(); |
| nc.setAdministratorUids(new int[] {Process.myUid()}); |
| final NetworkAgentInfo naiWithUid = fakeMobileNai(nc); |
| |
| setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, |
| Manifest.permission.ACCESS_FINE_LOCATION); |
| mServiceContext.setPermission(NETWORK_STACK, PERMISSION_DENIED); |
| |
| assertTrue( |
| "NetworkCapabilities administrator uid permission not applied", |
| mService.checkConnectivityDiagnosticsPermissions( |
| Process.myPid(), Process.myUid(), naiWithUid, mContext.getOpPackageName())); |
| } |
| |
| @Test |
| public void testCheckConnectivityDiagnosticsPermissionsFails() throws Exception { |
| final NetworkCapabilities nc = new NetworkCapabilities(); |
| nc.setOwnerUid(Process.myUid()); |
| nc.setAdministratorUids(new int[] {Process.myUid()}); |
| final NetworkAgentInfo naiWithUid = fakeMobileNai(nc); |
| |
| setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, |
| Manifest.permission.ACCESS_FINE_LOCATION); |
| mServiceContext.setPermission(NETWORK_STACK, PERMISSION_DENIED); |
| |
| // Use wrong pid and uid |
| assertFalse( |
| "Permissions allowed when they shouldn't be granted", |
| mService.checkConnectivityDiagnosticsPermissions( |
| Process.myPid() + 1, Process.myUid() + 1, naiWithUid, |
| mContext.getOpPackageName())); |
| } |
| |
| @Test |
| public void testRegisterConnectivityDiagnosticsCallbackCallsOnConnectivityReport() |
| throws Exception { |
| // Set up the Network, which leads to a ConnectivityReport being cached for the network. |
| final TestNetworkCallback callback = new TestNetworkCallback(); |
| mCm.registerDefaultNetworkCallback(callback); |
| final LinkProperties linkProperties = new LinkProperties(); |
| linkProperties.setInterfaceName(INTERFACE_NAME); |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, linkProperties); |
| mCellNetworkAgent.connect(true); |
| callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| callback.assertNoCallback(); |
| |
| final NetworkRequest request = new NetworkRequest.Builder().build(); |
| when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder); |
| |
| mServiceContext.setPermission(NETWORK_STACK, PERMISSION_GRANTED); |
| |
| mService.registerConnectivityDiagnosticsCallback( |
| mConnectivityDiagnosticsCallback, request, mContext.getPackageName()); |
| |
| // Block until all other events are done processing. |
| HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS); |
| |
| verify(mConnectivityDiagnosticsCallback) |
| .onConnectivityReportAvailable(argThat(report -> { |
| return INTERFACE_NAME.equals(report.getLinkProperties().getInterfaceName()) |
| && report.getNetworkCapabilities().hasTransport(TRANSPORT_CELLULAR); |
| })); |
| } |
| |
| private void setUpConnectivityDiagnosticsCallback() throws Exception { |
| final NetworkRequest request = new NetworkRequest.Builder().build(); |
| when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder); |
| |
| mServiceContext.setPermission(NETWORK_STACK, PERMISSION_GRANTED); |
| |
| mService.registerConnectivityDiagnosticsCallback( |
| mConnectivityDiagnosticsCallback, request, mContext.getPackageName()); |
| |
| // Block until all other events are done processing. |
| HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS); |
| |
| // Connect the cell agent verify that it notifies TestNetworkCallback that it is available |
| final TestNetworkCallback callback = new TestNetworkCallback(); |
| mCm.registerDefaultNetworkCallback(callback); |
| |
| final NetworkCapabilities ncTemplate = new NetworkCapabilities() |
| .addTransportType(TRANSPORT_CELLULAR) |
| .setTransportInfo(new TestTransportInfo()); |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, new LinkProperties(), |
| ncTemplate); |
| mCellNetworkAgent.connect(true); |
| callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| callback.assertNoCallback(); |
| } |
| |
| private boolean areConnDiagCapsRedacted(NetworkCapabilities nc) { |
| TestTransportInfo ti = (TestTransportInfo) nc.getTransportInfo(); |
| return nc.getUids() == null |
| && nc.getAdministratorUids().length == 0 |
| && nc.getOwnerUid() == Process.INVALID_UID |
| && getTestTransportInfo(nc).locationRedacted |
| && getTestTransportInfo(nc).localMacAddressRedacted |
| && getTestTransportInfo(nc).settingsRedacted; |
| } |
| |
| @Test |
| public void testConnectivityDiagnosticsCallbackOnConnectivityReportAvailable() |
| throws Exception { |
| setUpConnectivityDiagnosticsCallback(); |
| |
| // Block until all other events are done processing. |
| HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS); |
| |
| // Verify onConnectivityReport fired |
| verify(mConnectivityDiagnosticsCallback).onConnectivityReportAvailable( |
| argThat(report -> areConnDiagCapsRedacted(report.getNetworkCapabilities()))); |
| } |
| |
| @Test |
| public void testConnectivityDiagnosticsCallbackOnDataStallSuspected() throws Exception { |
| setUpConnectivityDiagnosticsCallback(); |
| |
| // Trigger notifyDataStallSuspected() on the INetworkMonitorCallbacks instance in the |
| // cellular network agent |
| mCellNetworkAgent.notifyDataStallSuspected(); |
| |
| // Block until all other events are done processing. |
| HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS); |
| |
| // Verify onDataStallSuspected fired |
| verify(mConnectivityDiagnosticsCallback).onDataStallSuspected( |
| argThat(report -> areConnDiagCapsRedacted(report.getNetworkCapabilities()))); |
| } |
| |
| @Test |
| public void testConnectivityDiagnosticsCallbackOnConnectivityReported() throws Exception { |
| setUpConnectivityDiagnosticsCallback(); |
| |
| final Network n = mCellNetworkAgent.getNetwork(); |
| final boolean hasConnectivity = true; |
| mService.reportNetworkConnectivity(n, hasConnectivity); |
| |
| // Block until all other events are done processing. |
| HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS); |
| |
| // Verify onNetworkConnectivityReported fired |
| verify(mConnectivityDiagnosticsCallback) |
| .onNetworkConnectivityReported(eq(n), eq(hasConnectivity)); |
| |
| final boolean noConnectivity = false; |
| mService.reportNetworkConnectivity(n, noConnectivity); |
| |
| // Block until all other events are done processing. |
| HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS); |
| |
| // Wait for onNetworkConnectivityReported to fire |
| verify(mConnectivityDiagnosticsCallback) |
| .onNetworkConnectivityReported(eq(n), eq(noConnectivity)); |
| } |
| |
| @Test |
| public void testRouteAddDeleteUpdate() throws Exception { |
| final NetworkRequest request = new NetworkRequest.Builder().build(); |
| final TestNetworkCallback networkCallback = new TestNetworkCallback(); |
| mCm.registerNetworkCallback(request, networkCallback); |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| reset(mMockNetd); |
| mCellNetworkAgent.connect(false); |
| networkCallback.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); |
| final int netId = mCellNetworkAgent.getNetwork().netId; |
| |
| final String iface = "rmnet_data0"; |
| final InetAddress gateway = InetAddress.getByName("fe80::5678"); |
| RouteInfo direct = RouteInfo.makeHostRoute(gateway, iface); |
| RouteInfo rio1 = new RouteInfo(new IpPrefix("2001:db8:1::/48"), gateway, iface); |
| RouteInfo rio2 = new RouteInfo(new IpPrefix("2001:db8:2::/48"), gateway, iface); |
| RouteInfo defaultRoute = new RouteInfo((IpPrefix) null, gateway, iface); |
| RouteInfo defaultWithMtu = new RouteInfo(null, gateway, iface, RouteInfo.RTN_UNICAST, |
| 1280 /* mtu */); |
| |
| // Send LinkProperties and check that we ask netd to add routes. |
| LinkProperties lp = new LinkProperties(); |
| lp.setInterfaceName(iface); |
| lp.addRoute(direct); |
| lp.addRoute(rio1); |
| lp.addRoute(defaultRoute); |
| mCellNetworkAgent.sendLinkProperties(lp); |
| networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, x -> x.getRoutes().size() == 3); |
| |
| assertRoutesAdded(netId, direct, rio1, defaultRoute); |
| reset(mMockNetd); |
| |
| // Send updated LinkProperties and check that we ask netd to add, remove, update routes. |
| assertTrue(lp.getRoutes().contains(defaultRoute)); |
| lp.removeRoute(rio1); |
| lp.addRoute(rio2); |
| lp.addRoute(defaultWithMtu); |
| // Ensure adding the same route with a different MTU replaces the previous route. |
| assertFalse(lp.getRoutes().contains(defaultRoute)); |
| assertTrue(lp.getRoutes().contains(defaultWithMtu)); |
| |
| mCellNetworkAgent.sendLinkProperties(lp); |
| networkCallback.expectLinkPropertiesThat(mCellNetworkAgent, |
| x -> x.getRoutes().contains(rio2)); |
| |
| assertRoutesRemoved(netId, rio1); |
| assertRoutesAdded(netId, rio2); |
| |
| ArgumentCaptor<RouteInfoParcel> captor = ArgumentCaptor.forClass(RouteInfoParcel.class); |
| verify(mMockNetd).networkUpdateRouteParcel(eq(netId), captor.capture()); |
| assertRouteInfoParcelMatches(defaultWithMtu, captor.getValue()); |
| |
| |
| mCm.unregisterNetworkCallback(networkCallback); |
| } |
| |
| @Test |
| public void testDumpDoesNotCrash() { |
| mServiceContext.setPermission(DUMP, PERMISSION_GRANTED); |
| // Filing a couple requests prior to testing the dump. |
| final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback(); |
| final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback(); |
| final NetworkRequest genericRequest = new NetworkRequest.Builder() |
| .clearCapabilities().build(); |
| final NetworkRequest wifiRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_WIFI).build(); |
| mCm.registerNetworkCallback(genericRequest, genericNetworkCallback); |
| mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback); |
| final StringWriter stringWriter = new StringWriter(); |
| |
| mService.dump(new FileDescriptor(), new PrintWriter(stringWriter), new String[0]); |
| |
| assertFalse(stringWriter.toString().isEmpty()); |
| } |
| |
| @Test |
| public void testRequestsSortedByIdSortsCorrectly() { |
| final TestNetworkCallback genericNetworkCallback = new TestNetworkCallback(); |
| final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback(); |
| final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); |
| final NetworkRequest genericRequest = new NetworkRequest.Builder() |
| .clearCapabilities().build(); |
| final NetworkRequest wifiRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_WIFI).build(); |
| final NetworkRequest cellRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_CELLULAR).build(); |
| mCm.registerNetworkCallback(genericRequest, genericNetworkCallback); |
| mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback); |
| mCm.registerNetworkCallback(cellRequest, cellNetworkCallback); |
| waitForIdle(); |
| |
| final NetworkRequestInfo[] nriOutput = mService.requestsSortedById(); |
| |
| assertTrue(nriOutput.length > 1); |
| for (int i = 0; i < nriOutput.length - 1; i++) { |
| final boolean isRequestIdInOrder = |
| nriOutput[i].mRequests.get(0).requestId |
| < nriOutput[i + 1].mRequests.get(0).requestId; |
| assertTrue(isRequestIdInOrder); |
| } |
| } |
| |
| private void assertUidRangesUpdatedForMyUid(boolean add) throws Exception { |
| final int uid = Process.myUid(); |
| assertVpnUidRangesUpdated(add, uidRangesForUids(uid), uid); |
| } |
| |
| private void assertVpnUidRangesUpdated(boolean add, Set<UidRange> vpnRanges, int exemptUid) |
| throws Exception { |
| InOrder inOrder = inOrder(mMockNetd); |
| ArgumentCaptor<int[]> exemptUidCaptor = ArgumentCaptor.forClass(int[].class); |
| |
| inOrder.verify(mMockNetd, times(1)).socketDestroy(eq(toUidRangeStableParcels(vpnRanges)), |
| exemptUidCaptor.capture()); |
| assertContainsExactly(exemptUidCaptor.getValue(), Process.VPN_UID, exemptUid); |
| |
| if (add) { |
| inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel( |
| new NativeUidRangeConfig(mMockVpn.getNetwork().getNetId(), |
| toUidRangeStableParcels(vpnRanges), PREFERENCE_PRIORITY_VPN)); |
| } else { |
| inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel( |
| new NativeUidRangeConfig(mMockVpn.getNetwork().getNetId(), |
| toUidRangeStableParcels(vpnRanges), PREFERENCE_PRIORITY_VPN)); |
| } |
| |
| inOrder.verify(mMockNetd, times(1)).socketDestroy(eq(toUidRangeStableParcels(vpnRanges)), |
| exemptUidCaptor.capture()); |
| assertContainsExactly(exemptUidCaptor.getValue(), Process.VPN_UID, exemptUid); |
| } |
| |
| @Test |
| public void testVpnUidRangesUpdate() throws Exception { |
| // Set up a WiFi network without proxy. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(true); |
| assertNull(mService.getProxyForNetwork(null)); |
| assertNull(mCm.getDefaultProxy()); |
| |
| final LinkProperties lp = new LinkProperties(); |
| lp.setInterfaceName("tun0"); |
| lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); |
| lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); |
| final UidRange vpnRange = PRIMARY_UIDRANGE; |
| final Set<UidRange> vpnRanges = Collections.singleton(vpnRange); |
| mMockVpn.establish(lp, VPN_UID, vpnRanges); |
| assertVpnUidRangesUpdated(true, vpnRanges, VPN_UID); |
| // VPN is connected but proxy is not set, so there is no need to send proxy broadcast. |
| verify(mProxyTracker, never()).sendProxyBroadcast(); |
| |
| // Update to new range which is old range minus APP1, i.e. only APP2 |
| final Set<UidRange> newRanges = new HashSet<>(Arrays.asList( |
| new UidRange(vpnRange.start, APP1_UID - 1), |
| new UidRange(APP1_UID + 1, vpnRange.stop))); |
| mMockVpn.setUids(newRanges); |
| waitForIdle(); |
| |
| assertVpnUidRangesUpdated(true, newRanges, VPN_UID); |
| assertVpnUidRangesUpdated(false, vpnRanges, VPN_UID); |
| |
| // Uid has changed but proxy is not set, so there is no need to send proxy broadcast. |
| verify(mProxyTracker, never()).sendProxyBroadcast(); |
| |
| final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); |
| lp.setHttpProxy(testProxyInfo); |
| mMockVpn.sendLinkProperties(lp); |
| waitForIdle(); |
| // Proxy is set, so send a proxy broadcast. |
| verify(mProxyTracker, times(1)).sendProxyBroadcast(); |
| reset(mProxyTracker); |
| |
| mMockVpn.setUids(vpnRanges); |
| waitForIdle(); |
| // Uid has changed and proxy is already set, so send a proxy broadcast. |
| verify(mProxyTracker, times(1)).sendProxyBroadcast(); |
| reset(mProxyTracker); |
| |
| // Proxy is removed, send a proxy broadcast. |
| lp.setHttpProxy(null); |
| mMockVpn.sendLinkProperties(lp); |
| waitForIdle(); |
| verify(mProxyTracker, times(1)).sendProxyBroadcast(); |
| reset(mProxyTracker); |
| |
| // Proxy is added in WiFi(default network), setDefaultProxy will be called. |
| final LinkProperties wifiLp = mCm.getLinkProperties(mWiFiNetworkAgent.getNetwork()); |
| assertNotNull(wifiLp); |
| wifiLp.setHttpProxy(testProxyInfo); |
| mWiFiNetworkAgent.sendLinkProperties(wifiLp); |
| waitForIdle(); |
| verify(mProxyTracker, times(1)).setDefaultProxy(eq(testProxyInfo)); |
| reset(mProxyTracker); |
| } |
| |
| @Test |
| public void testProxyBroadcastWillBeSentWhenVpnHasProxyAndConnects() throws Exception { |
| // Set up a WiFi network without proxy. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(true); |
| assertNull(mService.getProxyForNetwork(null)); |
| assertNull(mCm.getDefaultProxy()); |
| |
| final LinkProperties lp = new LinkProperties(); |
| lp.setInterfaceName("tun0"); |
| lp.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0), null)); |
| lp.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0), null)); |
| final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); |
| lp.setHttpProxy(testProxyInfo); |
| final UidRange vpnRange = PRIMARY_UIDRANGE; |
| final Set<UidRange> vpnRanges = Collections.singleton(vpnRange); |
| mMockVpn.setOwnerAndAdminUid(VPN_UID); |
| mMockVpn.registerAgent(false, vpnRanges, lp); |
| // In any case, the proxy broadcast won't be sent before VPN goes into CONNECTED state. |
| // Otherwise, the app that calls ConnectivityManager#getDefaultProxy() when it receives the |
| // proxy broadcast will get null. |
| verify(mProxyTracker, never()).sendProxyBroadcast(); |
| mMockVpn.connect(true /* validated */, true /* hasInternet */, false /* isStrictMode */); |
| waitForIdle(); |
| assertVpnUidRangesUpdated(true, vpnRanges, VPN_UID); |
| // Vpn is connected with proxy, so the proxy broadcast will be sent to inform the apps to |
| // update their proxy data. |
| verify(mProxyTracker, times(1)).sendProxyBroadcast(); |
| } |
| |
| @Test |
| public void testProxyBroadcastWillBeSentWhenTheProxyOfNonDefaultNetworkHasChanged() |
| throws Exception { |
| // Set up a CELLULAR network without proxy. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| assertNull(mService.getProxyForNetwork(null)); |
| assertNull(mCm.getDefaultProxy()); |
| // CELLULAR network should be the default network. |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| |
| // Set up a WiFi network without proxy. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(true); |
| assertNull(mService.getProxyForNetwork(null)); |
| assertNull(mCm.getDefaultProxy()); |
| // WiFi network should be the default network. |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| // CELLULAR network is not the default network. |
| assertNotEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| |
| // CELLULAR network is not the system default network, but it might be a per-app default |
| // network. The proxy broadcast should be sent once its proxy has changed. |
| final LinkProperties cellularLp = new LinkProperties(); |
| cellularLp.setInterfaceName(MOBILE_IFNAME); |
| final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888); |
| cellularLp.setHttpProxy(testProxyInfo); |
| mCellNetworkAgent.sendLinkProperties(cellularLp); |
| waitForIdle(); |
| verify(mProxyTracker, times(1)).sendProxyBroadcast(); |
| } |
| |
| @Test |
| public void testInvalidRequestTypes() { |
| final int[] invalidReqTypeInts = new int[]{-1, NetworkRequest.Type.NONE.ordinal(), |
| NetworkRequest.Type.LISTEN.ordinal(), NetworkRequest.Type.values().length}; |
| final NetworkCapabilities nc = new NetworkCapabilities().addTransportType(TRANSPORT_WIFI); |
| |
| for (int reqTypeInt : invalidReqTypeInts) { |
| assertThrows("Expect throws for invalid request type " + reqTypeInt, |
| IllegalArgumentException.class, |
| () -> mService.requestNetwork(Process.INVALID_UID, nc, reqTypeInt, null, 0, |
| null, ConnectivityManager.TYPE_NONE, NetworkCallback.FLAG_NONE, |
| mContext.getPackageName(), getAttributionTag()) |
| ); |
| } |
| } |
| |
| @Test |
| public void testKeepConnected() throws Exception { |
| setAlwaysOnNetworks(false); |
| registerDefaultNetworkCallbacks(); |
| final TestNetworkCallback allNetworksCb = new TestNetworkCallback(); |
| final NetworkRequest allNetworksRequest = new NetworkRequest.Builder().clearCapabilities() |
| .build(); |
| mCm.registerNetworkCallback(allNetworksRequest, allNetworksCb); |
| |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true /* validated */); |
| |
| mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| allNetworksCb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(true /* validated */); |
| |
| mDefaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); |
| // While the default callback doesn't see the network before it's validated, the listen |
| // sees the network come up and validate later |
| allNetworksCb.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent); |
| allNetworksCb.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent); |
| allNetworksCb.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent); |
| allNetworksCb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent, |
| TEST_LINGER_DELAY_MS * 2); |
| |
| // The cell network has disconnected (see LOST above) because it was outscored and |
| // had no requests (see setAlwaysOnNetworks(false) above) |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| final NetworkScore score = new NetworkScore.Builder().setLegacyInt(30).build(); |
| mCellNetworkAgent.setScore(score); |
| mCellNetworkAgent.connect(false /* validated */); |
| |
| // The cell network gets torn down right away. |
| allNetworksCb.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); |
| allNetworksCb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent, |
| TEST_NASCENT_DELAY_MS * 2); |
| allNetworksCb.assertNoCallback(); |
| |
| // Now create a cell network with KEEP_CONNECTED_FOR_HANDOVER and make sure it's |
| // not disconnected immediately when outscored. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| final NetworkScore scoreKeepup = new NetworkScore.Builder().setLegacyInt(30) |
| .setKeepConnectedReason(KEEP_CONNECTED_FOR_HANDOVER).build(); |
| mCellNetworkAgent.setScore(scoreKeepup); |
| mCellNetworkAgent.connect(true /* validated */); |
| |
| allNetworksCb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| mDefaultNetworkCallback.assertNoCallback(); |
| |
| mWiFiNetworkAgent.disconnect(); |
| |
| allNetworksCb.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); |
| |
| // Reconnect a WiFi network and make sure the cell network is still not torn down. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(true /* validated */); |
| |
| allNetworksCb.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); |
| mDefaultNetworkCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); |
| |
| // Now remove the reason to keep connected and make sure the network lingers and is |
| // torn down. |
| mCellNetworkAgent.setScore(new NetworkScore.Builder().setLegacyInt(30).build()); |
| allNetworksCb.expectCallback(CallbackEntry.LOSING, mCellNetworkAgent, |
| TEST_NASCENT_DELAY_MS * 2); |
| allNetworksCb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent, |
| TEST_LINGER_DELAY_MS * 2); |
| mDefaultNetworkCallback.assertNoCallback(); |
| |
| mCm.unregisterNetworkCallback(allNetworksCb); |
| // mDefaultNetworkCallback will be unregistered by tearDown() |
| } |
| |
| private class QosCallbackMockHelper { |
| @NonNull public final QosFilter mFilter; |
| @NonNull public final IQosCallback mCallback; |
| @NonNull public final TestNetworkAgentWrapper mAgentWrapper; |
| @NonNull private final List<IQosCallback> mCallbacks = new ArrayList(); |
| |
| QosCallbackMockHelper() throws Exception { |
| Log.d(TAG, "QosCallbackMockHelper: "); |
| mFilter = mock(QosFilter.class); |
| |
| // Ensure the network is disconnected before anything else occurs |
| assertNull(mCellNetworkAgent); |
| |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| |
| verifyActiveNetwork(TRANSPORT_CELLULAR); |
| waitForIdle(); |
| final Network network = mCellNetworkAgent.getNetwork(); |
| |
| final Pair<IQosCallback, IBinder> pair = createQosCallback(); |
| mCallback = pair.first; |
| |
| when(mFilter.getNetwork()).thenReturn(network); |
| when(mFilter.validate()).thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE); |
| mAgentWrapper = mCellNetworkAgent; |
| } |
| |
| void registerQosCallback(@NonNull final QosFilter filter, |
| @NonNull final IQosCallback callback) { |
| mCallbacks.add(callback); |
| final NetworkAgentInfo nai = |
| mService.getNetworkAgentInfoForNetwork(filter.getNetwork()); |
| mService.registerQosCallbackInternal(filter, callback, nai); |
| } |
| |
| void tearDown() { |
| for (int i = 0; i < mCallbacks.size(); i++) { |
| mService.unregisterQosCallback(mCallbacks.get(i)); |
| } |
| } |
| } |
| |
| private Pair<IQosCallback, IBinder> createQosCallback() { |
| final IQosCallback callback = mock(IQosCallback.class); |
| final IBinder binder = mock(Binder.class); |
| when(callback.asBinder()).thenReturn(binder); |
| when(binder.isBinderAlive()).thenReturn(true); |
| return new Pair<>(callback, binder); |
| } |
| |
| |
| @Test |
| public void testQosCallbackRegistration() throws Exception { |
| mQosCallbackMockHelper = new QosCallbackMockHelper(); |
| final NetworkAgentWrapper wrapper = mQosCallbackMockHelper.mAgentWrapper; |
| |
| when(mQosCallbackMockHelper.mFilter.validate()) |
| .thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE); |
| mQosCallbackMockHelper.registerQosCallback( |
| mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback); |
| |
| final NetworkAgentWrapper.CallbackType.OnQosCallbackRegister cbRegister1 = |
| (NetworkAgentWrapper.CallbackType.OnQosCallbackRegister) |
| wrapper.getCallbackHistory().poll(1000, x -> true); |
| assertNotNull(cbRegister1); |
| |
| final int registerCallbackId = cbRegister1.mQosCallbackId; |
| mService.unregisterQosCallback(mQosCallbackMockHelper.mCallback); |
| final NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister cbUnregister; |
| cbUnregister = (NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister) |
| wrapper.getCallbackHistory().poll(1000, x -> true); |
| assertNotNull(cbUnregister); |
| assertEquals(registerCallbackId, cbUnregister.mQosCallbackId); |
| assertNull(wrapper.getCallbackHistory().poll(200, x -> true)); |
| } |
| |
| @Test |
| public void testQosCallbackNoRegistrationOnValidationError() throws Exception { |
| mQosCallbackMockHelper = new QosCallbackMockHelper(); |
| |
| when(mQosCallbackMockHelper.mFilter.validate()) |
| .thenReturn(QosCallbackException.EX_TYPE_FILTER_NETWORK_RELEASED); |
| mQosCallbackMockHelper.registerQosCallback( |
| mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback); |
| waitForIdle(); |
| verify(mQosCallbackMockHelper.mCallback) |
| .onError(eq(QosCallbackException.EX_TYPE_FILTER_NETWORK_RELEASED)); |
| } |
| |
| @Test |
| public void testQosCallbackAvailableAndLost() throws Exception { |
| mQosCallbackMockHelper = new QosCallbackMockHelper(); |
| final int sessionId = 10; |
| final int qosCallbackId = 1; |
| |
| when(mQosCallbackMockHelper.mFilter.validate()) |
| .thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE); |
| mQosCallbackMockHelper.registerQosCallback( |
| mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback); |
| waitForIdle(); |
| |
| final EpsBearerQosSessionAttributes attributes = new EpsBearerQosSessionAttributes( |
| 1, 2, 3, 4, 5, new ArrayList<>()); |
| mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent() |
| .sendQosSessionAvailable(qosCallbackId, sessionId, attributes); |
| waitForIdle(); |
| |
| verify(mQosCallbackMockHelper.mCallback).onQosEpsBearerSessionAvailable(argThat(session -> |
| session.getSessionId() == sessionId |
| && session.getSessionType() == QosSession.TYPE_EPS_BEARER), eq(attributes)); |
| |
| mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent() |
| .sendQosSessionLost(qosCallbackId, sessionId, QosSession.TYPE_EPS_BEARER); |
| waitForIdle(); |
| verify(mQosCallbackMockHelper.mCallback).onQosSessionLost(argThat(session -> |
| session.getSessionId() == sessionId |
| && session.getSessionType() == QosSession.TYPE_EPS_BEARER)); |
| } |
| |
| @Test |
| public void testNrQosCallbackAvailableAndLost() throws Exception { |
| mQosCallbackMockHelper = new QosCallbackMockHelper(); |
| final int sessionId = 10; |
| final int qosCallbackId = 1; |
| |
| when(mQosCallbackMockHelper.mFilter.validate()) |
| .thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE); |
| mQosCallbackMockHelper.registerQosCallback( |
| mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback); |
| waitForIdle(); |
| |
| final NrQosSessionAttributes attributes = new NrQosSessionAttributes( |
| 1, 2, 3, 4, 5, 6, 7, new ArrayList<>()); |
| mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent() |
| .sendQosSessionAvailable(qosCallbackId, sessionId, attributes); |
| waitForIdle(); |
| |
| verify(mQosCallbackMockHelper.mCallback).onNrQosSessionAvailable(argThat(session -> |
| session.getSessionId() == sessionId |
| && session.getSessionType() == QosSession.TYPE_NR_BEARER), eq(attributes)); |
| |
| mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent() |
| .sendQosSessionLost(qosCallbackId, sessionId, QosSession.TYPE_NR_BEARER); |
| waitForIdle(); |
| verify(mQosCallbackMockHelper.mCallback).onQosSessionLost(argThat(session -> |
| session.getSessionId() == sessionId |
| && session.getSessionType() == QosSession.TYPE_NR_BEARER)); |
| } |
| |
| @Test |
| public void testQosCallbackTooManyRequests() throws Exception { |
| mQosCallbackMockHelper = new QosCallbackMockHelper(); |
| |
| when(mQosCallbackMockHelper.mFilter.validate()) |
| .thenReturn(QosCallbackException.EX_TYPE_FILTER_NONE); |
| for (int i = 0; i < 100; i++) { |
| final Pair<IQosCallback, IBinder> pair = createQosCallback(); |
| |
| try { |
| mQosCallbackMockHelper.registerQosCallback( |
| mQosCallbackMockHelper.mFilter, pair.first); |
| } catch (ServiceSpecificException e) { |
| assertEquals(e.errorCode, ConnectivityManager.Errors.TOO_MANY_REQUESTS); |
| if (i < 50) { |
| fail("TOO_MANY_REQUESTS thrown too early, the count is " + i); |
| } |
| |
| // As long as there is at least 50 requests, it is safe to assume it works. |
| // Note: The count isn't being tested precisely against 100 because the counter |
| // is shared with request network. |
| return; |
| } |
| } |
| fail("TOO_MANY_REQUESTS never thrown"); |
| } |
| |
| private void mockGetApplicationInfo(@NonNull final String packageName, final int uid) { |
| mockGetApplicationInfo(packageName, uid, PRIMARY_USER_HANDLE); |
| } |
| |
| private void mockGetApplicationInfo(@NonNull final String packageName, final int uid, |
| @NonNull final UserHandle user) { |
| final ApplicationInfo applicationInfo = new ApplicationInfo(); |
| applicationInfo.uid = uid; |
| try { |
| when(mPackageManager.getApplicationInfoAsUser(eq(packageName), anyInt(), eq(user))) |
| .thenReturn(applicationInfo); |
| } catch (Exception e) { |
| fail(e.getMessage()); |
| } |
| } |
| |
| private void mockGetApplicationInfoThrowsNameNotFound(@NonNull final String packageName, |
| @NonNull final UserHandle user) |
| throws Exception { |
| when(mPackageManager.getApplicationInfoAsUser(eq(packageName), anyInt(), eq(user))) |
| .thenThrow(new PackageManager.NameNotFoundException(packageName)); |
| } |
| |
| private void mockHasSystemFeature(@NonNull final String featureName, final boolean hasFeature) { |
| when(mPackageManager.hasSystemFeature(eq(featureName))) |
| .thenReturn(hasFeature); |
| } |
| |
| private Range<Integer> getNriFirstUidRange(@NonNull final NetworkRequestInfo nri) { |
| return nri.mRequests.get(0).networkCapabilities.getUids().iterator().next(); |
| } |
| |
| private OemNetworkPreferences createDefaultOemNetworkPreferences( |
| @OemNetworkPreferences.OemNetworkPreference final int preference) { |
| // Arrange PackageManager mocks |
| mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID); |
| |
| // Build OemNetworkPreferences object |
| return new OemNetworkPreferences.Builder() |
| .addNetworkPreference(TEST_PACKAGE_NAME, preference) |
| .build(); |
| } |
| |
| @Test |
| public void testOemNetworkRequestFactoryPreferenceUninitializedThrowsError() { |
| @OemNetworkPreferences.OemNetworkPreference final int prefToTest = |
| OEM_NETWORK_PREFERENCE_UNINITIALIZED; |
| |
| // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() |
| assertThrows(IllegalArgumentException.class, |
| () -> mService.new OemNetworkRequestFactory() |
| .createNrisFromOemNetworkPreferences( |
| createDefaultOemNetworkPreferences(prefToTest))); |
| } |
| |
| @Test |
| public void testOemNetworkRequestFactoryPreferenceOemPaid() |
| throws Exception { |
| // Expectations |
| final int expectedNumOfNris = 1; |
| final int expectedNumOfRequests = 3; |
| |
| @OemNetworkPreferences.OemNetworkPreference final int prefToTest = |
| OEM_NETWORK_PREFERENCE_OEM_PAID; |
| |
| // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() |
| final ArraySet<NetworkRequestInfo> nris = |
| mService.new OemNetworkRequestFactory() |
| .createNrisFromOemNetworkPreferences( |
| createDefaultOemNetworkPreferences(prefToTest)); |
| final NetworkRequestInfo nri = nris.iterator().next(); |
| assertEquals(PREFERENCE_PRIORITY_OEM, nri.mPreferencePriority); |
| final List<NetworkRequest> mRequests = nri.mRequests; |
| assertEquals(expectedNumOfNris, nris.size()); |
| assertEquals(expectedNumOfRequests, mRequests.size()); |
| assertTrue(mRequests.get(0).isListen()); |
| assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_NOT_METERED)); |
| assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_VALIDATED)); |
| assertTrue(mRequests.get(1).isRequest()); |
| assertTrue(mRequests.get(1).hasCapability(NET_CAPABILITY_OEM_PAID)); |
| assertEquals(NetworkRequest.Type.TRACK_DEFAULT, mRequests.get(2).type); |
| assertTrue(mService.getDefaultRequest().networkCapabilities.equalsNetCapabilities( |
| mRequests.get(2).networkCapabilities)); |
| } |
| |
| @Test |
| public void testOemNetworkRequestFactoryPreferenceOemPaidNoFallback() |
| throws Exception { |
| // Expectations |
| final int expectedNumOfNris = 1; |
| final int expectedNumOfRequests = 2; |
| |
| @OemNetworkPreferences.OemNetworkPreference final int prefToTest = |
| OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK; |
| |
| // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() |
| final ArraySet<NetworkRequestInfo> nris = |
| mService.new OemNetworkRequestFactory() |
| .createNrisFromOemNetworkPreferences( |
| createDefaultOemNetworkPreferences(prefToTest)); |
| final NetworkRequestInfo nri = nris.iterator().next(); |
| assertEquals(PREFERENCE_PRIORITY_OEM, nri.mPreferencePriority); |
| final List<NetworkRequest> mRequests = nri.mRequests; |
| assertEquals(expectedNumOfNris, nris.size()); |
| assertEquals(expectedNumOfRequests, mRequests.size()); |
| assertTrue(mRequests.get(0).isListen()); |
| assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_NOT_METERED)); |
| assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_VALIDATED)); |
| assertTrue(mRequests.get(1).isRequest()); |
| assertTrue(mRequests.get(1).hasCapability(NET_CAPABILITY_OEM_PAID)); |
| } |
| |
| @Test |
| public void testOemNetworkRequestFactoryPreferenceOemPaidOnly() |
| throws Exception { |
| // Expectations |
| final int expectedNumOfNris = 1; |
| final int expectedNumOfRequests = 1; |
| |
| @OemNetworkPreferences.OemNetworkPreference final int prefToTest = |
| OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; |
| |
| // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() |
| final ArraySet<NetworkRequestInfo> nris = |
| mService.new OemNetworkRequestFactory() |
| .createNrisFromOemNetworkPreferences( |
| createDefaultOemNetworkPreferences(prefToTest)); |
| final NetworkRequestInfo nri = nris.iterator().next(); |
| assertEquals(PREFERENCE_PRIORITY_OEM, nri.mPreferencePriority); |
| final List<NetworkRequest> mRequests = nri.mRequests; |
| assertEquals(expectedNumOfNris, nris.size()); |
| assertEquals(expectedNumOfRequests, mRequests.size()); |
| assertTrue(mRequests.get(0).isRequest()); |
| assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_OEM_PAID)); |
| } |
| |
| @Test |
| public void testOemNetworkRequestFactoryPreferenceOemPrivateOnly() |
| throws Exception { |
| // Expectations |
| final int expectedNumOfNris = 1; |
| final int expectedNumOfRequests = 1; |
| |
| @OemNetworkPreferences.OemNetworkPreference final int prefToTest = |
| OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; |
| |
| // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() |
| final ArraySet<NetworkRequestInfo> nris = |
| mService.new OemNetworkRequestFactory() |
| .createNrisFromOemNetworkPreferences( |
| createDefaultOemNetworkPreferences(prefToTest)); |
| final NetworkRequestInfo nri = nris.iterator().next(); |
| assertEquals(PREFERENCE_PRIORITY_OEM, nri.mPreferencePriority); |
| final List<NetworkRequest> mRequests = nri.mRequests; |
| assertEquals(expectedNumOfNris, nris.size()); |
| assertEquals(expectedNumOfRequests, mRequests.size()); |
| assertTrue(mRequests.get(0).isRequest()); |
| assertTrue(mRequests.get(0).hasCapability(NET_CAPABILITY_OEM_PRIVATE)); |
| assertFalse(mRequests.get(0).hasCapability(NET_CAPABILITY_OEM_PAID)); |
| } |
| |
| @Test |
| public void testOemNetworkRequestFactoryCreatesCorrectNumOfNris() |
| throws Exception { |
| // Expectations |
| final int expectedNumOfNris = 2; |
| |
| // Arrange PackageManager mocks |
| final String testPackageName2 = "com.google.apps.dialer"; |
| mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID); |
| mockGetApplicationInfo(testPackageName2, TEST_PACKAGE_UID); |
| |
| // Build OemNetworkPreferences object |
| final int testOemPref = OEM_NETWORK_PREFERENCE_OEM_PAID; |
| final int testOemPref2 = OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK; |
| final OemNetworkPreferences pref = new OemNetworkPreferences.Builder() |
| .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref) |
| .addNetworkPreference(testPackageName2, testOemPref2) |
| .build(); |
| |
| // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() |
| final ArraySet<NetworkRequestInfo> nris = |
| mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(pref); |
| |
| assertNotNull(nris); |
| assertEquals(expectedNumOfNris, nris.size()); |
| } |
| |
| @Test |
| public void testOemNetworkRequestFactoryMultiplePrefsCorrectlySetsUids() |
| throws Exception { |
| // Arrange PackageManager mocks |
| final String testPackageName2 = "com.google.apps.dialer"; |
| final int testPackageNameUid2 = 456; |
| mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID); |
| mockGetApplicationInfo(testPackageName2, testPackageNameUid2); |
| |
| // Build OemNetworkPreferences object |
| final int testOemPref = OEM_NETWORK_PREFERENCE_OEM_PAID; |
| final int testOemPref2 = OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK; |
| final OemNetworkPreferences pref = new OemNetworkPreferences.Builder() |
| .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref) |
| .addNetworkPreference(testPackageName2, testOemPref2) |
| .build(); |
| |
| // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() |
| final List<NetworkRequestInfo> nris = |
| new ArrayList<>( |
| mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences( |
| pref)); |
| |
| // Sort by uid to access nris by index |
| nris.sort(Comparator.comparingInt(nri -> getNriFirstUidRange(nri).getLower())); |
| assertEquals(TEST_PACKAGE_UID, (int) getNriFirstUidRange(nris.get(0)).getLower()); |
| assertEquals(TEST_PACKAGE_UID, (int) getNriFirstUidRange(nris.get(0)).getUpper()); |
| assertEquals(testPackageNameUid2, (int) getNriFirstUidRange(nris.get(1)).getLower()); |
| assertEquals(testPackageNameUid2, (int) getNriFirstUidRange(nris.get(1)).getUpper()); |
| } |
| |
| @Test |
| public void testOemNetworkRequestFactoryMultipleUsersSetsUids() |
| throws Exception { |
| // Arrange users |
| final int secondUserTestPackageUid = UserHandle.getUid(SECONDARY_USER, TEST_PACKAGE_UID); |
| final int thirdUserTestPackageUid = UserHandle.getUid(TERTIARY_USER, TEST_PACKAGE_UID); |
| when(mUserManager.getUserHandles(anyBoolean())).thenReturn( |
| Arrays.asList(PRIMARY_USER_HANDLE, SECONDARY_USER_HANDLE, TERTIARY_USER_HANDLE)); |
| |
| // Arrange PackageManager mocks testing for users who have and don't have a package. |
| mockGetApplicationInfoThrowsNameNotFound(TEST_PACKAGE_NAME, PRIMARY_USER_HANDLE); |
| mockGetApplicationInfo(TEST_PACKAGE_NAME, secondUserTestPackageUid, SECONDARY_USER_HANDLE); |
| mockGetApplicationInfo(TEST_PACKAGE_NAME, thirdUserTestPackageUid, TERTIARY_USER_HANDLE); |
| |
| // Build OemNetworkPreferences object |
| final int testOemPref = OEM_NETWORK_PREFERENCE_OEM_PAID; |
| final OemNetworkPreferences pref = new OemNetworkPreferences.Builder() |
| .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref) |
| .build(); |
| |
| // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() |
| final List<NetworkRequestInfo> nris = |
| new ArrayList<>( |
| mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences( |
| pref)); |
| |
| // UIDs for users with installed packages should be present. |
| // Three users exist, but only two have the test package installed. |
| final int expectedUidSize = 2; |
| final List<Range<Integer>> uids = |
| new ArrayList<>(nris.get(0).mRequests.get(0).networkCapabilities.getUids()); |
| assertEquals(expectedUidSize, uids.size()); |
| |
| // Sort by uid to access nris by index |
| uids.sort(Comparator.comparingInt(uid -> uid.getLower())); |
| assertEquals(secondUserTestPackageUid, (int) uids.get(0).getLower()); |
| assertEquals(secondUserTestPackageUid, (int) uids.get(0).getUpper()); |
| assertEquals(thirdUserTestPackageUid, (int) uids.get(1).getLower()); |
| assertEquals(thirdUserTestPackageUid, (int) uids.get(1).getUpper()); |
| } |
| |
| @Test |
| public void testOemNetworkRequestFactoryAddsPackagesToCorrectPreference() |
| throws Exception { |
| // Expectations |
| final int expectedNumOfNris = 1; |
| final int expectedNumOfAppUids = 2; |
| |
| // Arrange PackageManager mocks |
| final String testPackageName2 = "com.google.apps.dialer"; |
| final int testPackageNameUid2 = 456; |
| mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID); |
| mockGetApplicationInfo(testPackageName2, testPackageNameUid2); |
| |
| // Build OemNetworkPreferences object |
| final int testOemPref = OEM_NETWORK_PREFERENCE_OEM_PAID; |
| final OemNetworkPreferences pref = new OemNetworkPreferences.Builder() |
| .addNetworkPreference(TEST_PACKAGE_NAME, testOemPref) |
| .addNetworkPreference(testPackageName2, testOemPref) |
| .build(); |
| |
| // Act on OemNetworkRequestFactory.createNrisFromOemNetworkPreferences() |
| final ArraySet<NetworkRequestInfo> nris = |
| mService.new OemNetworkRequestFactory().createNrisFromOemNetworkPreferences(pref); |
| |
| assertEquals(expectedNumOfNris, nris.size()); |
| assertEquals(expectedNumOfAppUids, |
| nris.iterator().next().mRequests.get(0).networkCapabilities.getUids().size()); |
| } |
| |
| @Test |
| public void testSetOemNetworkPreferenceNullListenerAndPrefParamThrowsNpe() { |
| mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, true); |
| |
| // Act on ConnectivityService.setOemNetworkPreference() |
| assertThrows(NullPointerException.class, |
| () -> mService.setOemNetworkPreference( |
| null, |
| null)); |
| } |
| |
| @Test |
| public void testSetOemNetworkPreferenceFailsForNonAutomotive() |
| throws Exception { |
| mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, false); |
| @OemNetworkPreferences.OemNetworkPreference final int networkPref = |
| OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; |
| |
| // Act on ConnectivityService.setOemNetworkPreference() |
| assertThrows(UnsupportedOperationException.class, |
| () -> mService.setOemNetworkPreference( |
| createDefaultOemNetworkPreferences(networkPref), |
| null)); |
| } |
| |
| @Test |
| public void testSetOemNetworkPreferenceFailsForTestRequestWithoutPermission() { |
| // Calling setOemNetworkPreference() with a test pref requires the permission |
| // MANAGE_TEST_NETWORKS. |
| mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, false); |
| @OemNetworkPreferences.OemNetworkPreference final int networkPref = |
| OEM_NETWORK_PREFERENCE_TEST; |
| |
| // Act on ConnectivityService.setOemNetworkPreference() |
| assertThrows(SecurityException.class, |
| () -> mService.setOemNetworkPreference( |
| createDefaultOemNetworkPreferences(networkPref), |
| null)); |
| } |
| |
| @Test |
| public void testSetOemNetworkPreferenceFailsForInvalidTestRequest() { |
| assertSetOemNetworkPreferenceFailsForInvalidTestRequest(OEM_NETWORK_PREFERENCE_TEST); |
| } |
| |
| @Test |
| public void testSetOemNetworkPreferenceFailsForInvalidTestOnlyRequest() { |
| assertSetOemNetworkPreferenceFailsForInvalidTestRequest(OEM_NETWORK_PREFERENCE_TEST_ONLY); |
| } |
| |
| private void assertSetOemNetworkPreferenceFailsForInvalidTestRequest( |
| @OemNetworkPreferences.OemNetworkPreference final int oemNetworkPreferenceForTest) { |
| mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, true); |
| final String secondPackage = "does.not.matter"; |
| |
| // A valid test request would only have a single mapping. |
| final OemNetworkPreferences pref = new OemNetworkPreferences.Builder() |
| .addNetworkPreference(TEST_PACKAGE_NAME, oemNetworkPreferenceForTest) |
| .addNetworkPreference(secondPackage, oemNetworkPreferenceForTest) |
| .build(); |
| |
| // Act on ConnectivityService.setOemNetworkPreference() |
| assertThrows(IllegalArgumentException.class, |
| () -> mService.setOemNetworkPreference(pref, null)); |
| } |
| |
| private void setOemNetworkPreferenceAgentConnected(final int transportType, |
| final boolean connectAgent) throws Exception { |
| switch(transportType) { |
| // Corresponds to a metered cellular network. Will be used for the default network. |
| case TRANSPORT_CELLULAR: |
| if (!connectAgent) { |
| mCellNetworkAgent.disconnect(); |
| break; |
| } |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.removeCapability(NET_CAPABILITY_NOT_METERED); |
| mCellNetworkAgent.connect(true); |
| break; |
| // Corresponds to a restricted ethernet network with OEM_PAID/OEM_PRIVATE. |
| case TRANSPORT_ETHERNET: |
| if (!connectAgent) { |
| stopOemManagedNetwork(); |
| break; |
| } |
| startOemManagedNetwork(true); |
| break; |
| // Corresponds to unmetered Wi-Fi. |
| case TRANSPORT_WIFI: |
| if (!connectAgent) { |
| mWiFiNetworkAgent.disconnect(); |
| break; |
| } |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); |
| mWiFiNetworkAgent.connect(true); |
| break; |
| default: |
| throw new AssertionError("Unsupported transport type passed in."); |
| |
| } |
| waitForIdle(); |
| } |
| |
| private void startOemManagedNetwork(final boolean isOemPaid) throws Exception { |
| mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET); |
| mEthernetNetworkAgent.addCapability( |
| isOemPaid ? NET_CAPABILITY_OEM_PAID : NET_CAPABILITY_OEM_PRIVATE); |
| mEthernetNetworkAgent.removeCapability(NET_CAPABILITY_NOT_RESTRICTED); |
| mEthernetNetworkAgent.connect(true); |
| } |
| |
| private void stopOemManagedNetwork() { |
| mEthernetNetworkAgent.disconnect(); |
| waitForIdle(); |
| } |
| |
| private void verifyMultipleDefaultNetworksTracksCorrectly( |
| final int expectedOemRequestsSize, |
| @NonNull final Network expectedDefaultNetwork, |
| @NonNull final Network expectedPerAppNetwork) { |
| // The current test setup assumes two tracked default network requests; one for the default |
| // network and the other for the OEM network preference being tested. This will be validated |
| // each time to confirm it doesn't change under test. |
| final int expectedDefaultNetworkRequestsSize = 2; |
| assertEquals(expectedDefaultNetworkRequestsSize, mService.mDefaultNetworkRequests.size()); |
| for (final NetworkRequestInfo defaultRequest : mService.mDefaultNetworkRequests) { |
| final Network defaultNetwork = defaultRequest.getSatisfier() == null |
| ? null : defaultRequest.getSatisfier().network(); |
| // If this is the default request. |
| if (defaultRequest == mService.mDefaultRequest) { |
| assertEquals( |
| expectedDefaultNetwork, |
| defaultNetwork); |
| // Make sure this value doesn't change. |
| assertEquals(1, defaultRequest.mRequests.size()); |
| continue; |
| } |
| assertEquals(expectedPerAppNetwork, defaultNetwork); |
| assertEquals(expectedOemRequestsSize, defaultRequest.mRequests.size()); |
| } |
| verifyMultipleDefaultCallbacks(expectedDefaultNetwork, expectedPerAppNetwork); |
| } |
| |
| /** |
| * Verify default callbacks for 'available' fire as expected. This will only run if |
| * registerDefaultNetworkCallbacks() was executed prior and will only be different if the |
| * setOemNetworkPreference() per-app API was used for the current process. |
| * @param expectedSystemDefault the expected network for the system default. |
| * @param expectedPerAppDefault the expected network for the current process's default. |
| */ |
| private void verifyMultipleDefaultCallbacks( |
| @NonNull final Network expectedSystemDefault, |
| @NonNull final Network expectedPerAppDefault) { |
| if (null != mSystemDefaultNetworkCallback && null != expectedSystemDefault |
| && mService.mNoServiceNetwork.network() != expectedSystemDefault) { |
| // getLastAvailableNetwork() is used as this method can be called successively with |
| // the same network to validate therefore expectAvailableThenValidatedCallbacks |
| // can't be used. |
| assertEquals(mSystemDefaultNetworkCallback.getLastAvailableNetwork(), |
| expectedSystemDefault); |
| } |
| if (null != mDefaultNetworkCallback && null != expectedPerAppDefault |
| && mService.mNoServiceNetwork.network() != expectedPerAppDefault) { |
| assertEquals(mDefaultNetworkCallback.getLastAvailableNetwork(), |
| expectedPerAppDefault); |
| } |
| } |
| |
| private void registerDefaultNetworkCallbacks() { |
| if (mSystemDefaultNetworkCallback != null || mDefaultNetworkCallback != null |
| || mProfileDefaultNetworkCallback != null |
| || mTestPackageDefaultNetworkCallback != null) { |
| throw new IllegalStateException("Default network callbacks already registered"); |
| } |
| |
| // Using Manifest.permission.NETWORK_SETTINGS for registerSystemDefaultNetworkCallback() |
| mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED); |
| mSystemDefaultNetworkCallback = new TestNetworkCallback(); |
| mDefaultNetworkCallback = new TestNetworkCallback(); |
| mProfileDefaultNetworkCallback = new TestNetworkCallback(); |
| mTestPackageDefaultNetworkCallback = new TestNetworkCallback(); |
| mCm.registerSystemDefaultNetworkCallback(mSystemDefaultNetworkCallback, |
| new Handler(ConnectivityThread.getInstanceLooper())); |
| mCm.registerDefaultNetworkCallback(mDefaultNetworkCallback); |
| registerDefaultNetworkCallbackAsUid(mProfileDefaultNetworkCallback, |
| TEST_WORK_PROFILE_APP_UID); |
| registerDefaultNetworkCallbackAsUid(mTestPackageDefaultNetworkCallback, TEST_PACKAGE_UID); |
| // TODO: test using ConnectivityManager#registerDefaultNetworkCallbackAsUid as well. |
| mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_DENIED); |
| } |
| |
| private void unregisterDefaultNetworkCallbacks() { |
| if (null != mDefaultNetworkCallback) { |
| mCm.unregisterNetworkCallback(mDefaultNetworkCallback); |
| } |
| if (null != mSystemDefaultNetworkCallback) { |
| mCm.unregisterNetworkCallback(mSystemDefaultNetworkCallback); |
| } |
| if (null != mProfileDefaultNetworkCallback) { |
| mCm.unregisterNetworkCallback(mProfileDefaultNetworkCallback); |
| } |
| if (null != mTestPackageDefaultNetworkCallback) { |
| mCm.unregisterNetworkCallback(mTestPackageDefaultNetworkCallback); |
| } |
| } |
| |
| private void setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest( |
| @OemNetworkPreferences.OemNetworkPreference final int networkPrefToSetup) |
| throws Exception { |
| final int testPackageNameUid = TEST_PACKAGE_UID; |
| final String testPackageName = "per.app.defaults.package"; |
| setupMultipleDefaultNetworksForOemNetworkPreferenceTest( |
| networkPrefToSetup, testPackageNameUid, testPackageName); |
| } |
| |
| private void setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest( |
| @OemNetworkPreferences.OemNetworkPreference final int networkPrefToSetup) |
| throws Exception { |
| final int testPackageNameUid = Process.myUid(); |
| final String testPackageName = "per.app.defaults.package"; |
| setupMultipleDefaultNetworksForOemNetworkPreferenceTest( |
| networkPrefToSetup, testPackageNameUid, testPackageName); |
| } |
| |
| private void setupMultipleDefaultNetworksForOemNetworkPreferenceTest( |
| @OemNetworkPreferences.OemNetworkPreference final int networkPrefToSetup, |
| final int testPackageUid, @NonNull final String testPackageName) throws Exception { |
| // Only the default request should be included at start. |
| assertEquals(1, mService.mDefaultNetworkRequests.size()); |
| |
| final UidRangeParcel[] uidRanges = |
| toUidRangeStableParcels(uidRangesForUids(testPackageUid)); |
| setupSetOemNetworkPreferenceForPreferenceTest( |
| networkPrefToSetup, uidRanges, testPackageName); |
| } |
| |
| private void setupSetOemNetworkPreferenceForPreferenceTest( |
| @OemNetworkPreferences.OemNetworkPreference final int networkPrefToSetup, |
| @NonNull final UidRangeParcel[] uidRanges, |
| @NonNull final String testPackageName) throws Exception { |
| setupSetOemNetworkPreferenceForPreferenceTest(networkPrefToSetup, uidRanges, |
| testPackageName, PRIMARY_USER_HANDLE, true /* hasAutomotiveFeature */); |
| } |
| |
| private void setupSetOemNetworkPreferenceForPreferenceTest( |
| @OemNetworkPreferences.OemNetworkPreference final int networkPrefToSetup, |
| @NonNull final UidRangeParcel[] uidRanges, |
| @NonNull final String testPackageName, |
| @NonNull final UserHandle user) throws Exception { |
| setupSetOemNetworkPreferenceForPreferenceTest(networkPrefToSetup, uidRanges, |
| testPackageName, user, true /* hasAutomotiveFeature */); |
| } |
| |
| private void setupSetOemNetworkPreferenceForPreferenceTest( |
| @OemNetworkPreferences.OemNetworkPreference final int networkPrefToSetup, |
| @NonNull final UidRangeParcel[] uidRanges, |
| @NonNull final String testPackageName, |
| @NonNull final UserHandle user, |
| final boolean hasAutomotiveFeature) throws Exception { |
| mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, hasAutomotiveFeature); |
| |
| // These tests work off a single UID therefore using 'start' is valid. |
| mockGetApplicationInfo(testPackageName, uidRanges[0].start, user); |
| |
| setOemNetworkPreference(networkPrefToSetup, testPackageName); |
| } |
| |
| private void setOemNetworkPreference(final int networkPrefToSetup, |
| @NonNull final String... testPackageNames) |
| throws Exception { |
| mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, true); |
| |
| // Build OemNetworkPreferences object |
| final OemNetworkPreferences.Builder builder = new OemNetworkPreferences.Builder(); |
| for (final String packageName : testPackageNames) { |
| builder.addNetworkPreference(packageName, networkPrefToSetup); |
| } |
| final OemNetworkPreferences pref = builder.build(); |
| |
| // Act on ConnectivityService.setOemNetworkPreference() |
| final TestOemListenerCallback oemPrefListener = new TestOemListenerCallback(); |
| mService.setOemNetworkPreference(pref, oemPrefListener); |
| |
| // Verify call returned successfully |
| oemPrefListener.expectOnComplete(); |
| } |
| |
| private static class TestOemListenerCallback implements IOnCompleteListener { |
| final CompletableFuture<Object> mDone = new CompletableFuture<>(); |
| |
| @Override |
| public void onComplete() { |
| mDone.complete(new Object()); |
| } |
| |
| void expectOnComplete() { |
| try { |
| mDone.get(TIMEOUT_MS, TimeUnit.MILLISECONDS); |
| } catch (TimeoutException e) { |
| fail("Expected onComplete() not received after " + TIMEOUT_MS + " ms"); |
| } catch (Exception e) { |
| fail(e.getMessage()); |
| } |
| } |
| |
| @Override |
| public IBinder asBinder() { |
| return null; |
| } |
| } |
| |
| @Test |
| public void testMultiDefaultGetActiveNetworkIsCorrect() throws Exception { |
| @OemNetworkPreferences.OemNetworkPreference final int networkPref = |
| OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; |
| final int expectedOemPrefRequestSize = 1; |
| registerDefaultNetworkCallbacks(); |
| |
| // Setup the test process to use networkPref for their default network. |
| setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); |
| |
| // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. |
| // The active network for the default should be null at this point as this is a retricted |
| // network. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| null, |
| mEthernetNetworkAgent.getNetwork()); |
| |
| // Verify that the active network is correct |
| verifyActiveNetwork(TRANSPORT_ETHERNET); |
| // default NCs will be unregistered in tearDown |
| } |
| |
| @Test |
| public void testMultiDefaultIsActiveNetworkMeteredIsCorrect() throws Exception { |
| @OemNetworkPreferences.OemNetworkPreference final int networkPref = |
| OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; |
| final int expectedOemPrefRequestSize = 1; |
| registerDefaultNetworkCallbacks(); |
| |
| // Setup the test process to use networkPref for their default network. |
| setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); |
| |
| // Returns true by default when no network is available. |
| assertTrue(mCm.isActiveNetworkMetered()); |
| |
| // Connect to an unmetered restricted network that will only be available to the OEM pref. |
| mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET); |
| mEthernetNetworkAgent.addCapability(NET_CAPABILITY_OEM_PAID); |
| mEthernetNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); |
| mEthernetNetworkAgent.removeCapability(NET_CAPABILITY_NOT_RESTRICTED); |
| mEthernetNetworkAgent.connect(true); |
| waitForIdle(); |
| |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| null, |
| mEthernetNetworkAgent.getNetwork()); |
| |
| assertFalse(mCm.isActiveNetworkMetered()); |
| // default NCs will be unregistered in tearDown |
| } |
| |
| @Test |
| public void testPerAppDefaultRegisterDefaultNetworkCallback() throws Exception { |
| @OemNetworkPreferences.OemNetworkPreference final int networkPref = |
| OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; |
| final int expectedOemPrefRequestSize = 1; |
| final TestNetworkCallback defaultNetworkCallback = new TestNetworkCallback(); |
| |
| // Register the default network callback before the pref is already set. This means that |
| // the policy will be applied to the callback on setOemNetworkPreference(). |
| mCm.registerDefaultNetworkCallback(defaultNetworkCallback); |
| defaultNetworkCallback.assertNoCallback(); |
| |
| final TestNetworkCallback otherUidDefaultCallback = new TestNetworkCallback(); |
| withPermission(NETWORK_SETTINGS, () -> |
| mCm.registerDefaultNetworkCallbackForUid(TEST_PACKAGE_UID, otherUidDefaultCallback, |
| new Handler(ConnectivityThread.getInstanceLooper()))); |
| |
| // Setup the test process to use networkPref for their default network. |
| setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); |
| |
| // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. |
| // The active nai for the default is null at this point as this is a restricted network. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| null, |
| mEthernetNetworkAgent.getNetwork()); |
| |
| // At this point with a restricted network used, the available callback should trigger. |
| defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent); |
| assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), |
| mEthernetNetworkAgent.getNetwork()); |
| otherUidDefaultCallback.assertNoCallback(); |
| |
| // Now bring down the default network which should trigger a LOST callback. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); |
| |
| // At this point, with no network is available, the lost callback should trigger |
| defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); |
| otherUidDefaultCallback.assertNoCallback(); |
| |
| // Confirm we can unregister without issues. |
| mCm.unregisterNetworkCallback(defaultNetworkCallback); |
| mCm.unregisterNetworkCallback(otherUidDefaultCallback); |
| } |
| |
| @Test |
| public void testPerAppDefaultRegisterDefaultNetworkCallbackAfterPrefSet() throws Exception { |
| @OemNetworkPreferences.OemNetworkPreference final int networkPref = |
| OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; |
| final int expectedOemPrefRequestSize = 1; |
| final TestNetworkCallback defaultNetworkCallback = new TestNetworkCallback(); |
| |
| // Setup the test process to use networkPref for their default network. |
| setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); |
| |
| // Register the default network callback after the pref is already set. This means that |
| // the policy will be applied to the callback on requestNetwork(). |
| mCm.registerDefaultNetworkCallback(defaultNetworkCallback); |
| defaultNetworkCallback.assertNoCallback(); |
| |
| final TestNetworkCallback otherUidDefaultCallback = new TestNetworkCallback(); |
| withPermission(NETWORK_SETTINGS, () -> |
| mCm.registerDefaultNetworkCallbackForUid(TEST_PACKAGE_UID, otherUidDefaultCallback, |
| new Handler(ConnectivityThread.getInstanceLooper()))); |
| |
| // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. |
| // The active nai for the default is null at this point as this is a restricted network. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| null, |
| mEthernetNetworkAgent.getNetwork()); |
| |
| // At this point with a restricted network used, the available callback should trigger |
| defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent); |
| assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), |
| mEthernetNetworkAgent.getNetwork()); |
| otherUidDefaultCallback.assertNoCallback(); |
| |
| // Now bring down the default network which should trigger a LOST callback. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); |
| otherUidDefaultCallback.assertNoCallback(); |
| |
| // At this point, with no network is available, the lost callback should trigger |
| defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); |
| otherUidDefaultCallback.assertNoCallback(); |
| |
| // Confirm we can unregister without issues. |
| mCm.unregisterNetworkCallback(defaultNetworkCallback); |
| mCm.unregisterNetworkCallback(otherUidDefaultCallback); |
| } |
| |
| @Test |
| public void testPerAppDefaultRegisterDefaultNetworkCallbackDoesNotFire() throws Exception { |
| @OemNetworkPreferences.OemNetworkPreference final int networkPref = |
| OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; |
| final int expectedOemPrefRequestSize = 1; |
| final TestNetworkCallback defaultNetworkCallback = new TestNetworkCallback(); |
| final int userId = UserHandle.getUserId(Process.myUid()); |
| |
| mCm.registerDefaultNetworkCallback(defaultNetworkCallback); |
| defaultNetworkCallback.assertNoCallback(); |
| |
| final TestNetworkCallback otherUidDefaultCallback = new TestNetworkCallback(); |
| withPermission(NETWORK_SETTINGS, () -> |
| mCm.registerDefaultNetworkCallbackForUid(TEST_PACKAGE_UID, otherUidDefaultCallback, |
| new Handler(ConnectivityThread.getInstanceLooper()))); |
| |
| // Setup a process different than the test process to use the default network. This means |
| // that the defaultNetworkCallback won't be tracked by the per-app policy. |
| setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest(networkPref); |
| |
| // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. |
| // The active nai for the default is null at this point as this is a restricted network. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| null, |
| mEthernetNetworkAgent.getNetwork()); |
| |
| // As this callback does not have access to the OEM_PAID network, it will not fire. |
| defaultNetworkCallback.assertNoCallback(); |
| assertDefaultNetworkCapabilities(userId /* no networks */); |
| |
| // The other UID does have access, and gets a callback. |
| otherUidDefaultCallback.expectAvailableThenValidatedCallbacks(mEthernetNetworkAgent); |
| |
| // Bring up unrestricted cellular. This should now satisfy the default network. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| mCellNetworkAgent.getNetwork(), |
| mEthernetNetworkAgent.getNetwork()); |
| |
| // At this point with an unrestricted network used, the available callback should trigger |
| // The other UID is unaffected and remains on the paid network. |
| defaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| assertEquals(defaultNetworkCallback.getLastAvailableNetwork(), |
| mCellNetworkAgent.getNetwork()); |
| assertDefaultNetworkCapabilities(userId, mCellNetworkAgent); |
| otherUidDefaultCallback.assertNoCallback(); |
| |
| // Now bring down the per-app network. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); |
| |
| // Since the callback didn't use the per-app network, only the other UID gets a callback. |
| // Because the preference specifies no fallback, it does not switch to cellular. |
| defaultNetworkCallback.assertNoCallback(); |
| otherUidDefaultCallback.expectCallback(CallbackEntry.LOST, mEthernetNetworkAgent); |
| |
| // Now bring down the default network. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); |
| |
| // As this callback was tracking the default, this should now trigger. |
| defaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| otherUidDefaultCallback.assertNoCallback(); |
| |
| // Confirm we can unregister without issues. |
| mCm.unregisterNetworkCallback(defaultNetworkCallback); |
| mCm.unregisterNetworkCallback(otherUidDefaultCallback); |
| } |
| |
| /** |
| * This method assumes that the same uidRanges input will be used to verify that dependencies |
| * are called as expected. |
| */ |
| private void verifySetOemNetworkPreferenceForPreference( |
| @NonNull final UidRangeParcel[] uidRanges, |
| final int addUidRangesNetId, |
| final int addUidRangesTimes, |
| final int removeUidRangesNetId, |
| final int removeUidRangesTimes, |
| final boolean shouldDestroyNetwork) throws RemoteException { |
| verifySetOemNetworkPreferenceForPreference(uidRanges, uidRanges, |
| addUidRangesNetId, addUidRangesTimes, removeUidRangesNetId, removeUidRangesTimes, |
| shouldDestroyNetwork); |
| } |
| |
| private void verifySetOemNetworkPreferenceForPreference( |
| @NonNull final UidRangeParcel[] addedUidRanges, |
| @NonNull final UidRangeParcel[] removedUidRanges, |
| final int addUidRangesNetId, |
| final int addUidRangesTimes, |
| final int removeUidRangesNetId, |
| final int removeUidRangesTimes, |
| final boolean shouldDestroyNetwork) throws RemoteException { |
| final boolean useAnyIdForAdd = OEM_PREF_ANY_NET_ID == addUidRangesNetId; |
| final boolean useAnyIdForRemove = OEM_PREF_ANY_NET_ID == removeUidRangesNetId; |
| |
| // Validate that add/remove uid range (with oem priority) to/from netd. |
| verify(mMockNetd, times(addUidRangesTimes)).networkAddUidRangesParcel(argThat(config -> |
| (useAnyIdForAdd ? true : addUidRangesNetId == config.netId) |
| && Arrays.equals(addedUidRanges, config.uidRanges) |
| && PREFERENCE_PRIORITY_OEM == config.subPriority)); |
| verify(mMockNetd, times(removeUidRangesTimes)).networkRemoveUidRangesParcel( |
| argThat(config -> (useAnyIdForRemove ? true : removeUidRangesNetId == config.netId) |
| && Arrays.equals(removedUidRanges, config.uidRanges) |
| && PREFERENCE_PRIORITY_OEM == config.subPriority)); |
| if (shouldDestroyNetwork) { |
| verify(mMockNetd, times(1)) |
| .networkDestroy((useAnyIdForRemove ? anyInt() : eq(removeUidRangesNetId))); |
| } |
| reset(mMockNetd); |
| } |
| |
| /** |
| * Test the tracked default requests allows test requests without standard setup. |
| */ |
| @Test |
| public void testSetOemNetworkPreferenceAllowsValidTestRequestWithoutChecks() throws Exception { |
| @OemNetworkPreferences.OemNetworkPreference int networkPref = |
| OEM_NETWORK_PREFERENCE_TEST; |
| validateSetOemNetworkPreferenceAllowsValidTestPrefRequest(networkPref); |
| } |
| |
| /** |
| * Test the tracked default requests allows test only requests without standard setup. |
| */ |
| @Test |
| public void testSetOemNetworkPreferenceAllowsValidTestOnlyRequestWithoutChecks() |
| throws Exception { |
| @OemNetworkPreferences.OemNetworkPreference int networkPref = |
| OEM_NETWORK_PREFERENCE_TEST_ONLY; |
| validateSetOemNetworkPreferenceAllowsValidTestPrefRequest(networkPref); |
| } |
| |
| private void validateSetOemNetworkPreferenceAllowsValidTestPrefRequest(int networkPref) |
| throws Exception { |
| // The caller must have the MANAGE_TEST_NETWORKS permission. |
| final int testPackageUid = 123; |
| final String validTestPackageName = "does.not.matter"; |
| final UidRangeParcel[] uidRanges = |
| toUidRangeStableParcels(uidRangesForUids(testPackageUid)); |
| mServiceContext.setPermission( |
| Manifest.permission.MANAGE_TEST_NETWORKS, PERMISSION_GRANTED); |
| |
| // Put the system into a state in which setOemNetworkPreference() would normally fail. This |
| // will confirm that a valid test request can bypass these checks. |
| mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, false); |
| mServiceContext.setPermission( |
| Manifest.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE, PERMISSION_DENIED); |
| |
| // Validate the starting requests only includes the system default request. |
| assertEquals(1, mService.mDefaultNetworkRequests.size()); |
| |
| // Add an OEM default network request to track. |
| setupSetOemNetworkPreferenceForPreferenceTest( |
| networkPref, uidRanges, validTestPackageName, PRIMARY_USER_HANDLE, |
| false /* hasAutomotiveFeature */); |
| |
| // Two requests should now exist; the system default and the test request. |
| assertEquals(2, mService.mDefaultNetworkRequests.size()); |
| } |
| |
| /** |
| * Test the tracked default requests clear previous OEM requests on setOemNetworkPreference(). |
| */ |
| @Test |
| public void testSetOemNetworkPreferenceClearPreviousOemValues() throws Exception { |
| @OemNetworkPreferences.OemNetworkPreference int networkPref = |
| OEM_NETWORK_PREFERENCE_OEM_PAID; |
| final int testPackageUid = 123; |
| final String testPackageName = "com.google.apps.contacts"; |
| final UidRangeParcel[] uidRanges = |
| toUidRangeStableParcels(uidRangesForUids(testPackageUid)); |
| |
| // Validate the starting requests only includes the system default request. |
| assertEquals(1, mService.mDefaultNetworkRequests.size()); |
| |
| // Add an OEM default network request to track. |
| setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, testPackageName); |
| |
| // Two requests should exist, one for the fallback and one for the pref. |
| assertEquals(2, mService.mDefaultNetworkRequests.size()); |
| |
| networkPref = OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; |
| setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, testPackageName); |
| |
| // Two requests should still exist validating the previous per-app request was replaced. |
| assertEquals(2, mService.mDefaultNetworkRequests.size()); |
| } |
| |
| /** |
| * Test network priority for preference OEM_NETWORK_PREFERENCE_OEM_PAID in the following order: |
| * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID -> fallback |
| */ |
| @Test |
| public void testMultilayerForPreferenceOemPaidEvaluatesCorrectly() |
| throws Exception { |
| @OemNetworkPreferences.OemNetworkPreference final int networkPref = |
| OEM_NETWORK_PREFERENCE_OEM_PAID; |
| |
| // Arrange PackageManager mocks |
| final UidRangeParcel[] uidRanges = |
| toUidRangeStableParcels(uidRangesForUids(TEST_PACKAGE_UID)); |
| setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, TEST_PACKAGE_NAME); |
| |
| // Verify the starting state. No networks should be connected. |
| verifySetOemNetworkPreferenceForPreference(uidRanges, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| false /* shouldDestroyNetwork */); |
| |
| // Test lowest to highest priority requests. |
| // Bring up metered cellular. This will satisfy the fallback network. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); |
| verifySetOemNetworkPreferenceForPreference(uidRanges, |
| mCellNetworkAgent.getNetwork().netId, 1 /* times */, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| false /* shouldDestroyNetwork */); |
| |
| // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); |
| verifySetOemNetworkPreferenceForPreference(uidRanges, |
| mEthernetNetworkAgent.getNetwork().netId, 1 /* times */, |
| mCellNetworkAgent.getNetwork().netId, 1 /* times */, |
| false /* shouldDestroyNetwork */); |
| |
| // Bring up unmetered Wi-Fi. This will satisfy NET_CAPABILITY_NOT_METERED. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); |
| verifySetOemNetworkPreferenceForPreference(uidRanges, |
| mWiFiNetworkAgent.getNetwork().netId, 1 /* times */, |
| mEthernetNetworkAgent.getNetwork().netId, 1 /* times */, |
| false /* shouldDestroyNetwork */); |
| |
| // Disconnecting OEM_PAID should have no effect as it is lower in priority then unmetered. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); |
| // netd should not be called as default networks haven't changed. |
| verifySetOemNetworkPreferenceForPreference(uidRanges, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| false /* shouldDestroyNetwork */); |
| |
| // Disconnecting unmetered should put PANS on lowest priority fallback request. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false); |
| verifySetOemNetworkPreferenceForPreference(uidRanges, |
| mCellNetworkAgent.getNetwork().netId, 1 /* times */, |
| mWiFiNetworkAgent.getNetwork().netId, 0 /* times */, |
| true /* shouldDestroyNetwork */); |
| |
| // Disconnecting the fallback network should result in no connectivity. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); |
| verifySetOemNetworkPreferenceForPreference(uidRanges, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| mCellNetworkAgent.getNetwork().netId, 0 /* times */, |
| true /* shouldDestroyNetwork */); |
| } |
| |
| /** |
| * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK in the following order: |
| * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID |
| */ |
| @Test |
| public void testMultilayerForPreferenceOemPaidNoFallbackEvaluatesCorrectly() |
| throws Exception { |
| @OemNetworkPreferences.OemNetworkPreference final int networkPref = |
| OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK; |
| |
| // Arrange PackageManager mocks |
| final UidRangeParcel[] uidRanges = |
| toUidRangeStableParcels(uidRangesForUids(TEST_PACKAGE_UID)); |
| setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, TEST_PACKAGE_NAME); |
| |
| // Verify the starting state. This preference doesn't support using the fallback network |
| // therefore should be on the disconnected network as it has no networks to connect to. |
| verifySetOemNetworkPreferenceForPreference(uidRanges, |
| mService.mNoServiceNetwork.network.getNetId(), 1 /* times */, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| false /* shouldDestroyNetwork */); |
| |
| // Test lowest to highest priority requests. |
| // Bring up metered cellular. This will satisfy the fallback network. |
| // This preference should not use this network as it doesn't support fallback usage. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); |
| verifySetOemNetworkPreferenceForPreference(uidRanges, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| false /* shouldDestroyNetwork */); |
| |
| // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); |
| verifySetOemNetworkPreferenceForPreference(uidRanges, |
| mEthernetNetworkAgent.getNetwork().netId, 1 /* times */, |
| mService.mNoServiceNetwork.network.getNetId(), 1 /* times */, |
| false /* shouldDestroyNetwork */); |
| |
| // Bring up unmetered Wi-Fi. This will satisfy NET_CAPABILITY_NOT_METERED. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); |
| verifySetOemNetworkPreferenceForPreference(uidRanges, |
| mWiFiNetworkAgent.getNetwork().netId, 1 /* times */, |
| mEthernetNetworkAgent.getNetwork().netId, 1 /* times */, |
| false /* shouldDestroyNetwork */); |
| |
| // Disconnecting unmetered should put PANS on OEM_PAID. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false); |
| verifySetOemNetworkPreferenceForPreference(uidRanges, |
| mEthernetNetworkAgent.getNetwork().netId, 1 /* times */, |
| mWiFiNetworkAgent.getNetwork().netId, 0 /* times */, |
| true /* shouldDestroyNetwork */); |
| |
| // Disconnecting OEM_PAID should result in no connectivity. |
| // OEM_PAID_NO_FALLBACK not supporting a fallback now uses the disconnected network. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); |
| verifySetOemNetworkPreferenceForPreference(uidRanges, |
| mService.mNoServiceNetwork.network.getNetId(), 1 /* times */, |
| mEthernetNetworkAgent.getNetwork().netId, 0 /* times */, |
| true /* shouldDestroyNetwork */); |
| } |
| |
| /** |
| * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY in the following order: |
| * NET_CAPABILITY_OEM_PAID |
| * This preference should only apply to OEM_PAID networks. |
| */ |
| @Test |
| public void testMultilayerForPreferenceOemPaidOnlyEvaluatesCorrectly() |
| throws Exception { |
| @OemNetworkPreferences.OemNetworkPreference final int networkPref = |
| OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; |
| |
| // Arrange PackageManager mocks |
| final UidRangeParcel[] uidRanges = |
| toUidRangeStableParcels(uidRangesForUids(TEST_PACKAGE_UID)); |
| setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, TEST_PACKAGE_NAME); |
| |
| // Verify the starting state. This preference doesn't support using the fallback network |
| // therefore should be on the disconnected network as it has no networks to connect to. |
| verifySetOemNetworkPreferenceForPreference(uidRanges, |
| mService.mNoServiceNetwork.network.getNetId(), 1 /* times */, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| false /* shouldDestroyNetwork */); |
| |
| // Bring up metered cellular. This should not apply to this preference. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); |
| verifySetOemNetworkPreferenceForPreference(uidRanges, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| false /* shouldDestroyNetwork */); |
| |
| // Bring up unmetered Wi-Fi. This should not apply to this preference. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); |
| verifySetOemNetworkPreferenceForPreference(uidRanges, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| false /* shouldDestroyNetwork */); |
| |
| // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); |
| verifySetOemNetworkPreferenceForPreference(uidRanges, |
| mEthernetNetworkAgent.getNetwork().netId, 1 /* times */, |
| mService.mNoServiceNetwork.network.getNetId(), 1 /* times */, |
| false /* shouldDestroyNetwork */); |
| |
| // Disconnecting OEM_PAID should result in no connectivity. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); |
| verifySetOemNetworkPreferenceForPreference(uidRanges, |
| mService.mNoServiceNetwork.network.getNetId(), 1 /* times */, |
| mEthernetNetworkAgent.getNetwork().netId, 0 /* times */, |
| true /* shouldDestroyNetwork */); |
| } |
| |
| /** |
| * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY in the following order: |
| * NET_CAPABILITY_OEM_PRIVATE |
| * This preference should only apply to OEM_PRIVATE networks. |
| */ |
| @Test |
| public void testMultilayerForPreferenceOemPrivateOnlyEvaluatesCorrectly() |
| throws Exception { |
| @OemNetworkPreferences.OemNetworkPreference final int networkPref = |
| OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; |
| |
| // Arrange PackageManager mocks |
| final UidRangeParcel[] uidRanges = |
| toUidRangeStableParcels(uidRangesForUids(TEST_PACKAGE_UID)); |
| setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, TEST_PACKAGE_NAME); |
| |
| // Verify the starting state. This preference doesn't support using the fallback network |
| // therefore should be on the disconnected network as it has no networks to connect to. |
| verifySetOemNetworkPreferenceForPreference(uidRanges, |
| mService.mNoServiceNetwork.network.getNetId(), 1 /* times */, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| false /* shouldDestroyNetwork */); |
| |
| // Bring up metered cellular. This should not apply to this preference. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); |
| verifySetOemNetworkPreferenceForPreference(uidRanges, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| false /* shouldDestroyNetwork */); |
| |
| // Bring up unmetered Wi-Fi. This should not apply to this preference. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); |
| verifySetOemNetworkPreferenceForPreference(uidRanges, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| false /* shouldDestroyNetwork */); |
| |
| // Bring up ethernet with OEM_PRIVATE. This will satisfy NET_CAPABILITY_OEM_PRIVATE. |
| startOemManagedNetwork(false); |
| verifySetOemNetworkPreferenceForPreference(uidRanges, |
| mEthernetNetworkAgent.getNetwork().netId, 1 /* times */, |
| mService.mNoServiceNetwork.network.getNetId(), 1 /* times */, |
| false /* shouldDestroyNetwork */); |
| |
| // Disconnecting OEM_PRIVATE should result in no connectivity. |
| stopOemManagedNetwork(); |
| verifySetOemNetworkPreferenceForPreference(uidRanges, |
| mService.mNoServiceNetwork.network.getNetId(), 1 /* times */, |
| mEthernetNetworkAgent.getNetwork().netId, 0 /* times */, |
| true /* shouldDestroyNetwork */); |
| } |
| |
| @Test |
| public void testMultilayerForMultipleUsersEvaluatesCorrectly() |
| throws Exception { |
| @OemNetworkPreferences.OemNetworkPreference final int networkPref = |
| OEM_NETWORK_PREFERENCE_OEM_PAID; |
| |
| // Arrange users |
| final int secondUser = 10; |
| final UserHandle secondUserHandle = new UserHandle(secondUser); |
| when(mUserManager.getUserHandles(anyBoolean())).thenReturn( |
| Arrays.asList(PRIMARY_USER_HANDLE, secondUserHandle)); |
| |
| // Arrange PackageManager mocks |
| final int secondUserTestPackageUid = UserHandle.getUid(secondUser, TEST_PACKAGE_UID); |
| final UidRangeParcel[] uidRanges = |
| toUidRangeStableParcels( |
| uidRangesForUids(TEST_PACKAGE_UID, secondUserTestPackageUid)); |
| mockGetApplicationInfo(TEST_PACKAGE_NAME, secondUserTestPackageUid, secondUserHandle); |
| setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, TEST_PACKAGE_NAME); |
| |
| // Verify the starting state. No networks should be connected. |
| verifySetOemNetworkPreferenceForPreference(uidRanges, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| false /* shouldDestroyNetwork */); |
| |
| // Test that we correctly add the expected values for multiple users. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); |
| verifySetOemNetworkPreferenceForPreference(uidRanges, |
| mCellNetworkAgent.getNetwork().netId, 1 /* times */, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| false /* shouldDestroyNetwork */); |
| |
| // Test that we correctly remove the expected values for multiple users. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); |
| verifySetOemNetworkPreferenceForPreference(uidRanges, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| mCellNetworkAgent.getNetwork().netId, 0 /* times */, |
| true /* shouldDestroyNetwork */); |
| } |
| |
| @Test |
| public void testMultilayerForBroadcastedUsersEvaluatesCorrectly() |
| throws Exception { |
| @OemNetworkPreferences.OemNetworkPreference final int networkPref = |
| OEM_NETWORK_PREFERENCE_OEM_PAID; |
| |
| // Arrange users |
| final int secondUser = 10; |
| final UserHandle secondUserHandle = new UserHandle(secondUser); |
| when(mUserManager.getUserHandles(anyBoolean())).thenReturn( |
| Arrays.asList(PRIMARY_USER_HANDLE)); |
| |
| // Arrange PackageManager mocks |
| final int secondUserTestPackageUid = UserHandle.getUid(secondUser, TEST_PACKAGE_UID); |
| final UidRangeParcel[] uidRangesSingleUser = |
| toUidRangeStableParcels(uidRangesForUids(TEST_PACKAGE_UID)); |
| final UidRangeParcel[] uidRangesBothUsers = |
| toUidRangeStableParcels( |
| uidRangesForUids(TEST_PACKAGE_UID, secondUserTestPackageUid)); |
| mockGetApplicationInfo(TEST_PACKAGE_NAME, secondUserTestPackageUid, secondUserHandle); |
| setupSetOemNetworkPreferenceForPreferenceTest( |
| networkPref, uidRangesSingleUser, TEST_PACKAGE_NAME); |
| |
| // Verify the starting state. No networks should be connected. |
| verifySetOemNetworkPreferenceForPreference(uidRangesSingleUser, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| false /* shouldDestroyNetwork */); |
| |
| // Test that we correctly add the expected values for multiple users. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); |
| verifySetOemNetworkPreferenceForPreference(uidRangesSingleUser, |
| mCellNetworkAgent.getNetwork().netId, 1 /* times */, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| false /* shouldDestroyNetwork */); |
| |
| // Send a broadcast indicating a user was added. |
| when(mUserManager.getUserHandles(anyBoolean())).thenReturn( |
| Arrays.asList(PRIMARY_USER_HANDLE, secondUserHandle)); |
| final Intent addedIntent = new Intent(ACTION_USER_ADDED); |
| addedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(secondUser)); |
| processBroadcast(addedIntent); |
| |
| // Test that we correctly add values for all users and remove for the single user. |
| verifySetOemNetworkPreferenceForPreference(uidRangesBothUsers, uidRangesSingleUser, |
| mCellNetworkAgent.getNetwork().netId, 1 /* times */, |
| mCellNetworkAgent.getNetwork().netId, 1 /* times */, |
| false /* shouldDestroyNetwork */); |
| |
| // Send a broadcast indicating a user was removed. |
| when(mUserManager.getUserHandles(anyBoolean())).thenReturn( |
| Arrays.asList(PRIMARY_USER_HANDLE)); |
| final Intent removedIntent = new Intent(ACTION_USER_REMOVED); |
| removedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(secondUser)); |
| processBroadcast(removedIntent); |
| |
| // Test that we correctly add values for the single user and remove for the all users. |
| verifySetOemNetworkPreferenceForPreference(uidRangesSingleUser, uidRangesBothUsers, |
| mCellNetworkAgent.getNetwork().netId, 1 /* times */, |
| mCellNetworkAgent.getNetwork().netId, 1 /* times */, |
| false /* shouldDestroyNetwork */); |
| } |
| |
| @Test |
| public void testMultilayerForPackageChangesEvaluatesCorrectly() |
| throws Exception { |
| @OemNetworkPreferences.OemNetworkPreference final int networkPref = |
| OEM_NETWORK_PREFERENCE_OEM_PAID; |
| final String packageScheme = "package:"; |
| |
| // Arrange PackageManager mocks |
| final String packageToInstall = "package.to.install"; |
| final int packageToInstallUid = 81387; |
| final UidRangeParcel[] uidRangesSinglePackage = |
| toUidRangeStableParcels(uidRangesForUids(TEST_PACKAGE_UID)); |
| mockGetApplicationInfo(TEST_PACKAGE_NAME, TEST_PACKAGE_UID); |
| mockGetApplicationInfoThrowsNameNotFound(packageToInstall, PRIMARY_USER_HANDLE); |
| setOemNetworkPreference(networkPref, TEST_PACKAGE_NAME, packageToInstall); |
| grantUsingBackgroundNetworksPermissionForUid(Binder.getCallingUid(), packageToInstall); |
| |
| // Verify the starting state. No networks should be connected. |
| verifySetOemNetworkPreferenceForPreference(uidRangesSinglePackage, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| false /* shouldDestroyNetwork */); |
| |
| // Test that we correctly add the expected values for installed packages. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); |
| verifySetOemNetworkPreferenceForPreference(uidRangesSinglePackage, |
| mCellNetworkAgent.getNetwork().netId, 1 /* times */, |
| OEM_PREF_ANY_NET_ID, 0 /* times */, |
| false /* shouldDestroyNetwork */); |
| |
| // Set the system to recognize the package to be installed |
| mockGetApplicationInfo(packageToInstall, packageToInstallUid); |
| final UidRangeParcel[] uidRangesAllPackages = |
| toUidRangeStableParcels(uidRangesForUids(TEST_PACKAGE_UID, packageToInstallUid)); |
| |
| // Send a broadcast indicating a package was installed. |
| final Intent addedIntent = new Intent(ACTION_PACKAGE_ADDED); |
| addedIntent.setData(Uri.parse(packageScheme + packageToInstall)); |
| processBroadcast(addedIntent); |
| |
| // Test the single package is removed and the combined packages are added. |
| verifySetOemNetworkPreferenceForPreference(uidRangesAllPackages, uidRangesSinglePackage, |
| mCellNetworkAgent.getNetwork().netId, 1 /* times */, |
| mCellNetworkAgent.getNetwork().netId, 1 /* times */, |
| false /* shouldDestroyNetwork */); |
| |
| // Set the system to no longer recognize the package to be installed |
| mockGetApplicationInfoThrowsNameNotFound(packageToInstall, PRIMARY_USER_HANDLE); |
| |
| // Send a broadcast indicating a package was removed. |
| final Intent removedIntent = new Intent(ACTION_PACKAGE_REMOVED); |
| removedIntent.setData(Uri.parse(packageScheme + packageToInstall)); |
| processBroadcast(removedIntent); |
| |
| // Test the combined packages are removed and the single package is added. |
| verifySetOemNetworkPreferenceForPreference(uidRangesSinglePackage, uidRangesAllPackages, |
| mCellNetworkAgent.getNetwork().netId, 1 /* times */, |
| mCellNetworkAgent.getNetwork().netId, 1 /* times */, |
| false /* shouldDestroyNetwork */); |
| |
| // Set the system to change the installed package's uid |
| final int replacedTestPackageUid = TEST_PACKAGE_UID + 1; |
| mockGetApplicationInfo(TEST_PACKAGE_NAME, replacedTestPackageUid); |
| final UidRangeParcel[] uidRangesReplacedPackage = |
| toUidRangeStableParcels(uidRangesForUids(replacedTestPackageUid)); |
| |
| // Send a broadcast indicating a package was replaced. |
| final Intent replacedIntent = new Intent(ACTION_PACKAGE_REPLACED); |
| replacedIntent.setData(Uri.parse(packageScheme + TEST_PACKAGE_NAME)); |
| processBroadcast(replacedIntent); |
| |
| // Test the original uid is removed and is replaced with the new uid. |
| verifySetOemNetworkPreferenceForPreference(uidRangesReplacedPackage, uidRangesSinglePackage, |
| mCellNetworkAgent.getNetwork().netId, 1 /* times */, |
| mCellNetworkAgent.getNetwork().netId, 1 /* times */, |
| false /* shouldDestroyNetwork */); |
| } |
| |
| /** |
| * Test network priority for preference OEM_NETWORK_PREFERENCE_OEM_PAID in the following order: |
| * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID -> fallback |
| */ |
| @Test |
| public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPaidCorrectly() |
| throws Exception { |
| @OemNetworkPreferences.OemNetworkPreference final int networkPref = |
| OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID; |
| setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); |
| final int expectedDefaultRequestSize = 2; |
| final int expectedOemPrefRequestSize = 3; |
| registerDefaultNetworkCallbacks(); |
| |
| // The fallback as well as the OEM preference should now be tracked. |
| assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size()); |
| |
| // Test lowest to highest priority requests. |
| // Bring up metered cellular. This will satisfy the fallback network. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| mCellNetworkAgent.getNetwork(), |
| mCellNetworkAgent.getNetwork()); |
| |
| // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| mCellNetworkAgent.getNetwork(), |
| mEthernetNetworkAgent.getNetwork()); |
| |
| // Bring up unmetered Wi-Fi. This will satisfy NET_CAPABILITY_NOT_METERED. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| mWiFiNetworkAgent.getNetwork(), |
| mWiFiNetworkAgent.getNetwork()); |
| |
| // Disconnecting unmetered Wi-Fi will put the pref on OEM_PAID and fallback on cellular. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| mCellNetworkAgent.getNetwork(), |
| mEthernetNetworkAgent.getNetwork()); |
| |
| // Disconnecting cellular should keep OEM network on OEM_PAID and fallback will be null. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| null, |
| mEthernetNetworkAgent.getNetwork()); |
| |
| // Disconnecting OEM_PAID will put both on null as it is the last network. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| null, |
| null); |
| |
| // default callbacks will be unregistered in tearDown |
| } |
| |
| @Test |
| public void testNetworkFactoryRequestsWithMultilayerRequest() |
| throws Exception { |
| // First use OEM_PAID preference to create a multi-layer request : 1. listen for |
| // unmetered, 2. request network with cap OEM_PAID, 3, request the default network for |
| // fallback. |
| @OemNetworkPreferences.OemNetworkPreference final int networkPref = |
| OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID; |
| setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); |
| |
| final HandlerThread handlerThread = new HandlerThread("MockFactory"); |
| handlerThread.start(); |
| NetworkCapabilities internetFilter = new NetworkCapabilities() |
| .addCapability(NET_CAPABILITY_INTERNET) |
| .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); |
| final MockNetworkFactory internetFactory = new MockNetworkFactory(handlerThread.getLooper(), |
| mServiceContext, "internetFactory", internetFilter, mCsHandlerThread); |
| internetFactory.setScoreFilter(40); |
| internetFactory.register(); |
| // Default internet request only. The unmetered request is never sent to factories (it's a |
| // LISTEN, not requestable). The 3rd (fallback) request in OEM_PAID NRI is TRACK_DEFAULT |
| // which is also not sent to factories. Finally, the OEM_PAID request doesn't match the |
| // internetFactory filter. |
| internetFactory.expectRequestAdds(1); |
| internetFactory.assertRequestCountEquals(1); |
| |
| NetworkCapabilities oemPaidFilter = new NetworkCapabilities() |
| .addCapability(NET_CAPABILITY_INTERNET) |
| .addCapability(NET_CAPABILITY_OEM_PAID) |
| .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) |
| .removeCapability(NET_CAPABILITY_NOT_RESTRICTED); |
| final MockNetworkFactory oemPaidFactory = new MockNetworkFactory(handlerThread.getLooper(), |
| mServiceContext, "oemPaidFactory", oemPaidFilter, mCsHandlerThread); |
| oemPaidFactory.setScoreFilter(40); |
| oemPaidFactory.register(); |
| oemPaidFactory.expectRequestAdd(); // Because nobody satisfies the request |
| |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| |
| // A network connected that satisfies the default internet request. For the OEM_PAID |
| // preference, this is not as good as an OEM_PAID network, so even if the score of |
| // the network is better than the factory announced, it still should try to bring up |
| // the network. |
| expectNoRequestChanged(oemPaidFactory); |
| oemPaidFactory.assertRequestCountEquals(1); |
| // The internet factory however is outscored, and should lose its requests. |
| internetFactory.expectRequestRemove(); |
| internetFactory.assertRequestCountEquals(0); |
| |
| final NetworkCapabilities oemPaidNc = new NetworkCapabilities(); |
| oemPaidNc.addCapability(NET_CAPABILITY_OEM_PAID); |
| oemPaidNc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED); |
| final TestNetworkAgentWrapper oemPaidAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, |
| new LinkProperties(), oemPaidNc); |
| oemPaidAgent.connect(true); |
| |
| // The oemPaidAgent has score 50/cell transport, so it beats what the oemPaidFactory can |
| // provide, therefore it loses the request. |
| oemPaidFactory.expectRequestRemove(); |
| oemPaidFactory.assertRequestCountEquals(0); |
| expectNoRequestChanged(internetFactory); |
| internetFactory.assertRequestCountEquals(0); |
| |
| oemPaidAgent.setScore(new NetworkScore.Builder().setLegacyInt(20).setExiting(true).build()); |
| // Now the that the agent is weak, the oemPaidFactory can beat the existing network for the |
| // OEM_PAID request. The internet factory however can't beat a network that has OEM_PAID |
| // for the preference request, so it doesn't see the request. |
| oemPaidFactory.expectRequestAdd(); |
| oemPaidFactory.assertRequestCountEquals(1); |
| expectNoRequestChanged(internetFactory); |
| internetFactory.assertRequestCountEquals(0); |
| |
| mCellNetworkAgent.disconnect(); |
| // The network satisfying the default internet request has disconnected, so the |
| // internetFactory sees the default request again. However there is a network with OEM_PAID |
| // connected, so the 2nd OEM_PAID req is already satisfied, so the oemPaidFactory doesn't |
| // care about networks that don't have OEM_PAID. |
| expectNoRequestChanged(oemPaidFactory); |
| oemPaidFactory.assertRequestCountEquals(1); |
| internetFactory.expectRequestAdd(); |
| internetFactory.assertRequestCountEquals(1); |
| |
| // Cell connects again, still with score 50. Back to the previous state. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| expectNoRequestChanged(oemPaidFactory); |
| oemPaidFactory.assertRequestCountEquals(1); |
| internetFactory.expectRequestRemove(); |
| internetFactory.assertRequestCountEquals(0); |
| |
| // Create a request that holds the upcoming wifi network. |
| final TestNetworkCallback wifiCallback = new TestNetworkCallback(); |
| mCm.requestNetwork(new NetworkRequest.Builder().addTransportType(TRANSPORT_WIFI).build(), |
| wifiCallback); |
| |
| // Now WiFi connects and it's unmetered, but it's weaker than cell. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.addCapability(NET_CAPABILITY_NOT_METERED); |
| mWiFiNetworkAgent.setScore(new NetworkScore.Builder().setLegacyInt(30).setExiting(true) |
| .build()); // Not the best Internet network, but unmetered |
| mWiFiNetworkAgent.connect(true); |
| |
| // The OEM_PAID preference prefers an unmetered network to an OEM_PAID network, so |
| // the oemPaidFactory can't beat wifi no matter how high its score. |
| oemPaidFactory.expectRequestRemove(); |
| expectNoRequestChanged(internetFactory); |
| |
| mCellNetworkAgent.disconnect(); |
| // Now that the best internet network (cell, with its 50 score compared to 30 for WiFi |
| // at this point), the default internet request is satisfied by a network worse than |
| // the internetFactory announced, so it gets the request. However, there is still an |
| // unmetered network, so the oemPaidNetworkFactory still can't beat this. |
| expectNoRequestChanged(oemPaidFactory); |
| internetFactory.expectRequestAdd(); |
| mCm.unregisterNetworkCallback(wifiCallback); |
| } |
| |
| /** |
| * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK in the following order: |
| * NET_CAPABILITY_NOT_METERED -> NET_CAPABILITY_OEM_PAID |
| */ |
| @Test |
| public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPaidNoFallbackCorrectly() |
| throws Exception { |
| @OemNetworkPreferences.OemNetworkPreference final int networkPref = |
| OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK; |
| setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); |
| final int expectedDefaultRequestSize = 2; |
| final int expectedOemPrefRequestSize = 2; |
| registerDefaultNetworkCallbacks(); |
| |
| // The fallback as well as the OEM preference should now be tracked. |
| assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size()); |
| |
| // Test lowest to highest priority requests. |
| // Bring up metered cellular. This will satisfy the fallback network but not the pref. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| mCellNetworkAgent.getNetwork(), |
| mService.mNoServiceNetwork.network()); |
| |
| // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| mCellNetworkAgent.getNetwork(), |
| mEthernetNetworkAgent.getNetwork()); |
| |
| // Bring up unmetered Wi-Fi. This will satisfy NET_CAPABILITY_NOT_METERED. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| mWiFiNetworkAgent.getNetwork(), |
| mWiFiNetworkAgent.getNetwork()); |
| |
| // Disconnecting unmetered Wi-Fi will put the OEM pref on OEM_PAID and fallback on cellular. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| mCellNetworkAgent.getNetwork(), |
| mEthernetNetworkAgent.getNetwork()); |
| |
| // Disconnecting cellular should keep OEM network on OEM_PAID and fallback will be null. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| null, |
| mEthernetNetworkAgent.getNetwork()); |
| |
| // Disconnecting OEM_PAID puts the fallback on null and the pref on the disconnected net. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| null, |
| mService.mNoServiceNetwork.network()); |
| |
| // default callbacks will be unregistered in tearDown |
| } |
| |
| /** |
| * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY in the following order: |
| * NET_CAPABILITY_OEM_PAID |
| * This preference should only apply to OEM_PAID networks. |
| */ |
| @Test |
| public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPaidOnlyCorrectly() |
| throws Exception { |
| @OemNetworkPreferences.OemNetworkPreference final int networkPref = |
| OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY; |
| setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); |
| final int expectedDefaultRequestSize = 2; |
| final int expectedOemPrefRequestSize = 1; |
| registerDefaultNetworkCallbacks(); |
| |
| // The fallback as well as the OEM preference should now be tracked. |
| assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size()); |
| |
| // Test lowest to highest priority requests. |
| // Bring up metered cellular. This will satisfy the fallback network. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| mCellNetworkAgent.getNetwork(), |
| mService.mNoServiceNetwork.network()); |
| |
| // Bring up ethernet with OEM_PAID. This will satisfy NET_CAPABILITY_OEM_PAID. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, true); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| mCellNetworkAgent.getNetwork(), |
| mEthernetNetworkAgent.getNetwork()); |
| |
| // Bring up unmetered Wi-Fi. The OEM network shouldn't change, the fallback will take Wi-Fi. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| mWiFiNetworkAgent.getNetwork(), |
| mEthernetNetworkAgent.getNetwork()); |
| |
| // Disconnecting unmetered Wi-Fi shouldn't change the OEM network with fallback on cellular. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| mCellNetworkAgent.getNetwork(), |
| mEthernetNetworkAgent.getNetwork()); |
| |
| // Disconnecting OEM_PAID will keep the fallback on cellular and nothing for OEM_PAID. |
| // OEM_PAID_ONLY not supporting a fallback now uses the disconnected network. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_ETHERNET, false); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| mCellNetworkAgent.getNetwork(), |
| mService.mNoServiceNetwork.network()); |
| |
| // Disconnecting cellular will put the fallback on null and the pref on disconnected. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| null, |
| mService.mNoServiceNetwork.network()); |
| |
| // default callbacks will be unregistered in tearDown |
| } |
| |
| /** |
| * Test network priority for OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY in the following order: |
| * NET_CAPABILITY_OEM_PRIVATE |
| * This preference should only apply to OEM_PRIVATE networks. |
| */ |
| @Test |
| public void testMultipleDefaultNetworksTracksOemNetworkPreferenceOemPrivateOnlyCorrectly() |
| throws Exception { |
| @OemNetworkPreferences.OemNetworkPreference final int networkPref = |
| OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; |
| setupMultipleDefaultNetworksForOemNetworkPreferenceCurrentUidTest(networkPref); |
| final int expectedDefaultRequestSize = 2; |
| final int expectedOemPrefRequestSize = 1; |
| registerDefaultNetworkCallbacks(); |
| |
| // The fallback as well as the OEM preference should now be tracked. |
| assertEquals(expectedDefaultRequestSize, mService.mDefaultNetworkRequests.size()); |
| |
| // Test lowest to highest priority requests. |
| // Bring up metered cellular. This will satisfy the fallback network. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| mCellNetworkAgent.getNetwork(), |
| mService.mNoServiceNetwork.network()); |
| |
| // Bring up ethernet with OEM_PRIVATE. This will satisfy NET_CAPABILITY_OEM_PRIVATE. |
| startOemManagedNetwork(false); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| mCellNetworkAgent.getNetwork(), |
| mEthernetNetworkAgent.getNetwork()); |
| |
| // Bring up unmetered Wi-Fi. The OEM network shouldn't change, the fallback will take Wi-Fi. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, true); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| mWiFiNetworkAgent.getNetwork(), |
| mEthernetNetworkAgent.getNetwork()); |
| |
| // Disconnecting unmetered Wi-Fi shouldn't change the OEM network with fallback on cellular. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_WIFI, false); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| mCellNetworkAgent.getNetwork(), |
| mEthernetNetworkAgent.getNetwork()); |
| |
| // Disconnecting OEM_PRIVATE will keep the fallback on cellular. |
| // OEM_PRIVATE_ONLY not supporting a fallback now uses to the disconnected network. |
| stopOemManagedNetwork(); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| mCellNetworkAgent.getNetwork(), |
| mService.mNoServiceNetwork.network()); |
| |
| // Disconnecting cellular will put the fallback on null and pref on disconnected. |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, false); |
| verifyMultipleDefaultNetworksTracksCorrectly(expectedOemPrefRequestSize, |
| null, |
| mService.mNoServiceNetwork.network()); |
| |
| // default callbacks will be unregistered in tearDown |
| } |
| |
| @Test |
| public void testCapabilityWithOemNetworkPreference() throws Exception { |
| @OemNetworkPreferences.OemNetworkPreference final int networkPref = |
| OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; |
| setupMultipleDefaultNetworksForOemNetworkPreferenceNotCurrentUidTest(networkPref); |
| registerDefaultNetworkCallbacks(); |
| |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); |
| |
| mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| |
| mCellNetworkAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED); |
| mSystemDefaultNetworkCallback.expectCapabilitiesThat(mCellNetworkAgent, nc -> |
| nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)); |
| mDefaultNetworkCallback.expectCapabilitiesThat(mCellNetworkAgent, nc -> |
| nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)); |
| |
| // default callbacks will be unregistered in tearDown |
| } |
| |
| @Test |
| public void testSetOemNetworkPreferenceLogsRequest() throws Exception { |
| mServiceContext.setPermission(DUMP, PERMISSION_GRANTED); |
| @OemNetworkPreferences.OemNetworkPreference final int networkPref = |
| OEM_NETWORK_PREFERENCE_OEM_PAID; |
| final StringWriter stringWriter = new StringWriter(); |
| final String logIdentifier = "UPDATE INITIATED: OemNetworkPreferences"; |
| final Pattern pattern = Pattern.compile(logIdentifier); |
| |
| final int expectedNumLogs = 2; |
| final UidRangeParcel[] uidRanges = |
| toUidRangeStableParcels(uidRangesForUids(TEST_PACKAGE_UID)); |
| |
| // Call twice to generate two logs. |
| setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, TEST_PACKAGE_NAME); |
| setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges, TEST_PACKAGE_NAME); |
| mService.dump(new FileDescriptor(), new PrintWriter(stringWriter), new String[0]); |
| |
| final String dumpOutput = stringWriter.toString(); |
| final Matcher matcher = pattern.matcher(dumpOutput); |
| int count = 0; |
| while (matcher.find()) { |
| count++; |
| } |
| assertEquals(expectedNumLogs, count); |
| } |
| |
| @Test |
| public void testGetAllNetworkStateSnapshots() throws Exception { |
| verifyNoNetwork(); |
| |
| // Setup test cellular network with specified LinkProperties and NetworkCapabilities, |
| // verify the content of the snapshot matches. |
| final LinkProperties cellLp = new LinkProperties(); |
| final LinkAddress myIpv4Addr = new LinkAddress(InetAddress.getByName("192.0.2.129"), 25); |
| final LinkAddress myIpv6Addr = new LinkAddress(InetAddress.getByName("2001:db8::1"), 64); |
| cellLp.setInterfaceName("test01"); |
| cellLp.addLinkAddress(myIpv4Addr); |
| cellLp.addLinkAddress(myIpv6Addr); |
| cellLp.addRoute(new RouteInfo(InetAddress.getByName("fe80::1234"))); |
| cellLp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254"))); |
| cellLp.addRoute(new RouteInfo(myIpv4Addr, null)); |
| cellLp.addRoute(new RouteInfo(myIpv6Addr, null)); |
| final NetworkCapabilities cellNcTemplate = new NetworkCapabilities.Builder() |
| .addTransportType(TRANSPORT_CELLULAR).addCapability(NET_CAPABILITY_MMS).build(); |
| |
| final TestNetworkCallback cellCb = new TestNetworkCallback(); |
| mCm.requestNetwork(new NetworkRequest.Builder().addCapability(NET_CAPABILITY_MMS).build(), |
| cellCb); |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp, cellNcTemplate); |
| mCellNetworkAgent.connect(true); |
| cellCb.expectAvailableCallbacksUnvalidated(mCellNetworkAgent); |
| List<NetworkStateSnapshot> snapshots = mCm.getAllNetworkStateSnapshots(); |
| assertLength(1, snapshots); |
| |
| // Compose the expected cellular snapshot for verification. |
| final NetworkCapabilities cellNc = |
| mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork()); |
| final NetworkStateSnapshot cellSnapshot = new NetworkStateSnapshot( |
| mCellNetworkAgent.getNetwork(), cellNc, cellLp, |
| null, ConnectivityManager.TYPE_MOBILE); |
| assertEquals(cellSnapshot, snapshots.get(0)); |
| |
| // Connect wifi and verify the snapshots. |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(true); |
| waitForIdle(); |
| // Compose the expected wifi snapshot for verification. |
| final NetworkCapabilities wifiNc = |
| mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()); |
| final NetworkStateSnapshot wifiSnapshot = new NetworkStateSnapshot( |
| mWiFiNetworkAgent.getNetwork(), wifiNc, new LinkProperties(), null, |
| ConnectivityManager.TYPE_WIFI); |
| |
| snapshots = mCm.getAllNetworkStateSnapshots(); |
| assertLength(2, snapshots); |
| assertContainsAll(snapshots, cellSnapshot, wifiSnapshot); |
| |
| // Set cellular as suspended, verify the snapshots will not contain suspended networks. |
| // TODO: Consider include SUSPENDED networks, which should be considered as |
| // temporary shortage of connectivity of a connected network. |
| mCellNetworkAgent.suspend(); |
| waitForIdle(); |
| snapshots = mCm.getAllNetworkStateSnapshots(); |
| assertLength(1, snapshots); |
| assertEquals(wifiSnapshot, snapshots.get(0)); |
| |
| // Disconnect wifi, verify the snapshots contain nothing. |
| mWiFiNetworkAgent.disconnect(); |
| waitForIdle(); |
| snapshots = mCm.getAllNetworkStateSnapshots(); |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); |
| assertLength(0, snapshots); |
| |
| mCellNetworkAgent.resume(); |
| waitForIdle(); |
| snapshots = mCm.getAllNetworkStateSnapshots(); |
| assertLength(1, snapshots); |
| assertEquals(cellSnapshot, snapshots.get(0)); |
| |
| mCellNetworkAgent.disconnect(); |
| waitForIdle(); |
| verifyNoNetwork(); |
| mCm.unregisterNetworkCallback(cellCb); |
| } |
| |
| // Cannot be part of MockNetworkFactory since it requires method of the test. |
| private void expectNoRequestChanged(@NonNull MockNetworkFactory factory) { |
| waitForIdle(); |
| factory.assertNoRequestChanged(); |
| } |
| |
| @Test |
| public void testRegisterBestMatchingNetworkCallback_noIssueToFactory() throws Exception { |
| // Prepare mock mms factory. |
| final HandlerThread handlerThread = new HandlerThread("MockCellularFactory"); |
| handlerThread.start(); |
| NetworkCapabilities filter = new NetworkCapabilities() |
| .addTransportType(TRANSPORT_CELLULAR) |
| .addCapability(NET_CAPABILITY_MMS); |
| final MockNetworkFactory testFactory = new MockNetworkFactory(handlerThread.getLooper(), |
| mServiceContext, "testFactory", filter, mCsHandlerThread); |
| testFactory.setScoreFilter(40); |
| |
| try { |
| // Register the factory. It doesn't see the default request because its filter does |
| // not include INTERNET. |
| testFactory.register(); |
| expectNoRequestChanged(testFactory); |
| testFactory.assertRequestCountEquals(0); |
| // The factory won't try to start the network since the default request doesn't |
| // match the filter (no INTERNET capability). |
| assertFalse(testFactory.getMyStartRequested()); |
| |
| // Register callback for listening best matching network. Verify that the request won't |
| // be sent to factory. |
| final TestNetworkCallback bestMatchingCb = new TestNetworkCallback(); |
| mCm.registerBestMatchingNetworkCallback( |
| new NetworkRequest.Builder().addCapability(NET_CAPABILITY_MMS).build(), |
| bestMatchingCb, mCsHandlerThread.getThreadHandler()); |
| bestMatchingCb.assertNoCallback(); |
| expectNoRequestChanged(testFactory); |
| testFactory.assertRequestCountEquals(0); |
| assertFalse(testFactory.getMyStartRequested()); |
| |
| // Fire a normal mms request, verify the factory will only see the request. |
| final TestNetworkCallback mmsNetworkCallback = new TestNetworkCallback(); |
| final NetworkRequest mmsRequest = new NetworkRequest.Builder() |
| .addCapability(NET_CAPABILITY_MMS).build(); |
| mCm.requestNetwork(mmsRequest, mmsNetworkCallback); |
| testFactory.expectRequestAdd(); |
| testFactory.assertRequestCountEquals(1); |
| assertTrue(testFactory.getMyStartRequested()); |
| |
| // Unregister best matching callback, verify factory see no change. |
| mCm.unregisterNetworkCallback(bestMatchingCb); |
| expectNoRequestChanged(testFactory); |
| testFactory.assertRequestCountEquals(1); |
| assertTrue(testFactory.getMyStartRequested()); |
| } finally { |
| testFactory.terminate(); |
| } |
| } |
| |
| @Test |
| public void testRegisterBestMatchingNetworkCallback_trackBestNetwork() throws Exception { |
| final TestNetworkCallback bestMatchingCb = new TestNetworkCallback(); |
| mCm.registerBestMatchingNetworkCallback( |
| new NetworkRequest.Builder().addCapability(NET_CAPABILITY_TRUSTED).build(), |
| bestMatchingCb, mCsHandlerThread.getThreadHandler()); |
| |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| bestMatchingCb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(true); |
| bestMatchingCb.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent); |
| |
| // Change something on cellular to trigger capabilities changed, since the callback |
| // only cares about the best network, verify it received nothing from cellular. |
| mCellNetworkAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED); |
| bestMatchingCb.assertNoCallback(); |
| |
| // Make cellular the best network again, verify the callback now tracks cellular. |
| mWiFiNetworkAgent.adjustScore(-50); |
| bestMatchingCb.expectAvailableCallbacksValidated(mCellNetworkAgent); |
| |
| // Make cellular temporary non-trusted, which will not satisfying the request. |
| // Verify the callback switch from/to the other network accordingly. |
| mCellNetworkAgent.removeCapability(NET_CAPABILITY_TRUSTED); |
| bestMatchingCb.expectAvailableCallbacksValidated(mWiFiNetworkAgent); |
| mCellNetworkAgent.addCapability(NET_CAPABILITY_TRUSTED); |
| bestMatchingCb.expectAvailableDoubleValidatedCallbacks(mCellNetworkAgent); |
| |
| // Verify the callback doesn't care about wifi disconnect. |
| mWiFiNetworkAgent.disconnect(); |
| bestMatchingCb.assertNoCallback(); |
| mCellNetworkAgent.disconnect(); |
| bestMatchingCb.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| } |
| |
| private UidRangeParcel[] uidRangeFor(final UserHandle handle) { |
| UidRange range = UidRange.createForUser(handle); |
| return new UidRangeParcel[] { new UidRangeParcel(range.start, range.stop) }; |
| } |
| |
| private static class TestOnCompleteListener implements Runnable { |
| final class OnComplete {} |
| final ArrayTrackRecord<OnComplete>.ReadHead mHistory = |
| new ArrayTrackRecord<OnComplete>().newReadHead(); |
| |
| @Override |
| public void run() { |
| mHistory.add(new OnComplete()); |
| } |
| |
| public void expectOnComplete() { |
| assertNotNull(mHistory.poll(TIMEOUT_MS, it -> true)); |
| } |
| } |
| |
| private TestNetworkAgentWrapper makeEnterpriseNetworkAgent() throws Exception { |
| final NetworkCapabilities workNc = new NetworkCapabilities(); |
| workNc.addCapability(NET_CAPABILITY_ENTERPRISE); |
| workNc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED); |
| return new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, new LinkProperties(), workNc); |
| } |
| |
| private TestNetworkCallback mEnterpriseCallback; |
| private UserHandle setupEnterpriseNetwork() { |
| final UserHandle userHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID); |
| mServiceContext.setWorkProfile(userHandle, true); |
| |
| // File a request to avoid the enterprise network being disconnected as soon as the default |
| // request goes away – it would make impossible to test that networkRemoveUidRanges |
| // is called, as the network would disconnect first for lack of a request. |
| mEnterpriseCallback = new TestNetworkCallback(); |
| final NetworkRequest keepUpRequest = new NetworkRequest.Builder() |
| .addCapability(NET_CAPABILITY_ENTERPRISE) |
| .build(); |
| mCm.requestNetwork(keepUpRequest, mEnterpriseCallback); |
| return userHandle; |
| } |
| |
| private void maybeTearDownEnterpriseNetwork() { |
| if (null != mEnterpriseCallback) { |
| mCm.unregisterNetworkCallback(mEnterpriseCallback); |
| } |
| } |
| |
| /** |
| * Make sure per-profile networking preference behaves as expected when the enterprise network |
| * goes up and down while the preference is active. Make sure they behave as expected whether |
| * there is a general default network or not. |
| */ |
| @Test |
| public void testPreferenceForUserNetworkUpDown() throws Exception { |
| final InOrder inOrder = inOrder(mMockNetd); |
| final UserHandle testHandle = setupEnterpriseNetwork(); |
| registerDefaultNetworkCallbacks(); |
| |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| |
| mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| mProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical( |
| mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE)); |
| |
| |
| final TestOnCompleteListener listener = new TestOnCompleteListener(); |
| mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, |
| r -> r.run(), listener); |
| listener.expectOnComplete(); |
| |
| // Setting a network preference for this user will create a new set of routing rules for |
| // the UID range that corresponds to this user, so as to define the default network |
| // for these apps separately. This is true because the multi-layer request relevant to |
| // this UID range contains a TRACK_DEFAULT, so the range will be moved through UID-specific |
| // rules to the correct network – in this case the system default network. The case where |
| // the default network for the profile happens to be the same as the system default |
| // is not handled specially, the rules are always active as long as a preference is set. |
| inOrder.verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig( |
| mCellNetworkAgent.getNetwork().netId, uidRangeFor(testHandle), |
| PREFERENCE_PRIORITY_PROFILE)); |
| |
| // The enterprise network is not ready yet. |
| assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, |
| mProfileDefaultNetworkCallback); |
| |
| final TestNetworkAgentWrapper workAgent = makeEnterpriseNetworkAgent(); |
| workAgent.connect(false); |
| |
| mProfileDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(workAgent); |
| mSystemDefaultNetworkCallback.assertNoCallback(); |
| mDefaultNetworkCallback.assertNoCallback(); |
| inOrder.verify(mMockNetd).networkCreate( |
| nativeNetworkConfigPhysical(workAgent.getNetwork().netId, INetd.PERMISSION_SYSTEM)); |
| inOrder.verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig( |
| workAgent.getNetwork().netId, uidRangeFor(testHandle), |
| PREFERENCE_PRIORITY_PROFILE)); |
| inOrder.verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig( |
| mCellNetworkAgent.getNetwork().netId, uidRangeFor(testHandle), |
| PREFERENCE_PRIORITY_PROFILE)); |
| |
| // Make sure changes to the work agent send callbacks to the app in the work profile, but |
| // not to the other apps. |
| workAgent.setNetworkValid(true /* isStrictMode */); |
| workAgent.mNetworkMonitor.forceReevaluation(Process.myUid()); |
| mProfileDefaultNetworkCallback.expectCapabilitiesThat(workAgent, |
| nc -> nc.hasCapability(NET_CAPABILITY_VALIDATED) |
| && nc.hasCapability(NET_CAPABILITY_ENTERPRISE)); |
| assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); |
| |
| workAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED); |
| mProfileDefaultNetworkCallback.expectCapabilitiesThat(workAgent, nc -> |
| nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)); |
| assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); |
| |
| // Conversely, change a capability on the system-wide default network and make sure |
| // that only the apps outside of the work profile receive the callbacks. |
| mCellNetworkAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED); |
| mSystemDefaultNetworkCallback.expectCapabilitiesThat(mCellNetworkAgent, nc -> |
| nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)); |
| mDefaultNetworkCallback.expectCapabilitiesThat(mCellNetworkAgent, nc -> |
| nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)); |
| mProfileDefaultNetworkCallback.assertNoCallback(); |
| |
| // Disconnect and reconnect the system-wide default network and make sure that the |
| // apps on this network see the appropriate callbacks, and the app on the work profile |
| // doesn't because it continues to use the enterprise network. |
| mCellNetworkAgent.disconnect(); |
| mSystemDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| mProfileDefaultNetworkCallback.assertNoCallback(); |
| inOrder.verify(mMockNetd).networkDestroy(mCellNetworkAgent.getNetwork().netId); |
| |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| mProfileDefaultNetworkCallback.assertNoCallback(); |
| inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical( |
| mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE)); |
| |
| // When the agent disconnects, test that the app on the work profile falls back to the |
| // default network. |
| workAgent.disconnect(); |
| mProfileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, workAgent); |
| mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); |
| assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); |
| inOrder.verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig( |
| mCellNetworkAgent.getNetwork().netId, uidRangeFor(testHandle), |
| PREFERENCE_PRIORITY_PROFILE)); |
| inOrder.verify(mMockNetd).networkDestroy(workAgent.getNetwork().netId); |
| |
| mCellNetworkAgent.disconnect(); |
| mSystemDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| mProfileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| |
| // Waiting for the handler to be idle before checking for networkDestroy is necessary |
| // here because ConnectivityService calls onLost before the network is fully torn down. |
| waitForIdle(); |
| inOrder.verify(mMockNetd).networkDestroy(mCellNetworkAgent.getNetwork().netId); |
| |
| // If the control comes here, callbacks seem to behave correctly in the presence of |
| // a default network when the enterprise network goes up and down. Now, make sure they |
| // also behave correctly in the absence of a system-wide default network. |
| final TestNetworkAgentWrapper workAgent2 = makeEnterpriseNetworkAgent(); |
| workAgent2.connect(false); |
| |
| mProfileDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(workAgent2); |
| assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); |
| inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical( |
| workAgent2.getNetwork().netId, INetd.PERMISSION_SYSTEM)); |
| inOrder.verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig( |
| workAgent2.getNetwork().netId, uidRangeFor(testHandle), |
| PREFERENCE_PRIORITY_PROFILE)); |
| |
| workAgent2.setNetworkValid(true /* isStrictMode */); |
| workAgent2.mNetworkMonitor.forceReevaluation(Process.myUid()); |
| mProfileDefaultNetworkCallback.expectCapabilitiesThat(workAgent2, |
| nc -> nc.hasCapability(NET_CAPABILITY_ENTERPRISE) |
| && !nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)); |
| assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); |
| inOrder.verify(mMockNetd, never()).networkAddUidRangesParcel(any()); |
| |
| // When the agent disconnects, test that the app on the work profile falls back to the |
| // default network. |
| workAgent2.disconnect(); |
| mProfileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, workAgent2); |
| assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); |
| inOrder.verify(mMockNetd).networkDestroy(workAgent2.getNetwork().netId); |
| |
| assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, |
| mProfileDefaultNetworkCallback); |
| |
| // Callbacks will be unregistered by tearDown() |
| } |
| |
| /** |
| * Test that, in a given networking context, calling setPreferenceForUser to set per-profile |
| * defaults on then off works as expected. |
| */ |
| @Test |
| public void testSetPreferenceForUserOnOff() throws Exception { |
| final InOrder inOrder = inOrder(mMockNetd); |
| final UserHandle testHandle = setupEnterpriseNetwork(); |
| |
| // Connect both a regular cell agent and an enterprise network first. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| |
| final TestNetworkAgentWrapper workAgent = makeEnterpriseNetworkAgent(); |
| workAgent.connect(true); |
| |
| final TestOnCompleteListener listener = new TestOnCompleteListener(); |
| mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, |
| r -> r.run(), listener); |
| listener.expectOnComplete(); |
| inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical( |
| mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE)); |
| inOrder.verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig( |
| workAgent.getNetwork().netId, uidRangeFor(testHandle), |
| PREFERENCE_PRIORITY_PROFILE)); |
| |
| registerDefaultNetworkCallbacks(); |
| |
| mSystemDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); |
| mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); |
| mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(workAgent); |
| |
| mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_DEFAULT, |
| r -> r.run(), listener); |
| listener.expectOnComplete(); |
| |
| mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); |
| assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback); |
| inOrder.verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig( |
| workAgent.getNetwork().netId, uidRangeFor(testHandle), |
| PREFERENCE_PRIORITY_PROFILE)); |
| |
| workAgent.disconnect(); |
| mCellNetworkAgent.disconnect(); |
| |
| // Callbacks will be unregistered by tearDown() |
| } |
| |
| /** |
| * Test per-profile default networks for two different profiles concurrently. |
| */ |
| @Test |
| public void testSetPreferenceForTwoProfiles() throws Exception { |
| final InOrder inOrder = inOrder(mMockNetd); |
| final UserHandle testHandle2 = setupEnterpriseNetwork(); |
| final UserHandle testHandle4 = UserHandle.of(TEST_WORK_PROFILE_USER_ID + 2); |
| mServiceContext.setWorkProfile(testHandle4, true); |
| registerDefaultNetworkCallbacks(); |
| |
| final TestNetworkCallback app4Cb = new TestNetworkCallback(); |
| final int testWorkProfileAppUid4 = |
| UserHandle.getUid(testHandle4.getIdentifier(), TEST_APP_ID); |
| registerDefaultNetworkCallbackAsUid(app4Cb, testWorkProfileAppUid4); |
| |
| // Connect both a regular cell agent and an enterprise network first. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| |
| final TestNetworkAgentWrapper workAgent = makeEnterpriseNetworkAgent(); |
| workAgent.connect(true); |
| |
| mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| mProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| app4Cb.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical( |
| mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE)); |
| inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical( |
| workAgent.getNetwork().netId, INetd.PERMISSION_SYSTEM)); |
| |
| final TestOnCompleteListener listener = new TestOnCompleteListener(); |
| mCm.setProfileNetworkPreference(testHandle2, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, |
| r -> r.run(), listener); |
| listener.expectOnComplete(); |
| inOrder.verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig( |
| workAgent.getNetwork().netId, uidRangeFor(testHandle2), |
| PREFERENCE_PRIORITY_PROFILE)); |
| |
| mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(workAgent); |
| assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, |
| app4Cb); |
| |
| mCm.setProfileNetworkPreference(testHandle4, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, |
| r -> r.run(), listener); |
| listener.expectOnComplete(); |
| inOrder.verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig( |
| workAgent.getNetwork().netId, uidRangeFor(testHandle4), |
| PREFERENCE_PRIORITY_PROFILE)); |
| |
| app4Cb.expectAvailableCallbacksValidated(workAgent); |
| assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, |
| mProfileDefaultNetworkCallback); |
| |
| mCm.setProfileNetworkPreference(testHandle2, PROFILE_NETWORK_PREFERENCE_DEFAULT, |
| r -> r.run(), listener); |
| listener.expectOnComplete(); |
| inOrder.verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig( |
| workAgent.getNetwork().netId, uidRangeFor(testHandle2), |
| PREFERENCE_PRIORITY_PROFILE)); |
| |
| mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); |
| assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, |
| app4Cb); |
| |
| workAgent.disconnect(); |
| mCellNetworkAgent.disconnect(); |
| |
| mCm.unregisterNetworkCallback(app4Cb); |
| // Other callbacks will be unregistered by tearDown() |
| } |
| |
| @Test |
| public void testProfilePreferenceRemovedUponUserRemoved() throws Exception { |
| final InOrder inOrder = inOrder(mMockNetd); |
| final UserHandle testHandle = setupEnterpriseNetwork(); |
| |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| |
| final TestOnCompleteListener listener = new TestOnCompleteListener(); |
| mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, |
| r -> r.run(), listener); |
| listener.expectOnComplete(); |
| inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical( |
| mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE)); |
| inOrder.verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig( |
| mCellNetworkAgent.getNetwork().netId, uidRangeFor(testHandle), |
| PREFERENCE_PRIORITY_PROFILE)); |
| |
| final Intent removedIntent = new Intent(ACTION_USER_REMOVED); |
| removedIntent.putExtra(Intent.EXTRA_USER, testHandle); |
| processBroadcast(removedIntent); |
| |
| inOrder.verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig( |
| mCellNetworkAgent.getNetwork().netId, uidRangeFor(testHandle), |
| PREFERENCE_PRIORITY_PROFILE)); |
| } |
| |
| /** |
| * Make sure wrong preferences for per-profile default networking are rejected. |
| */ |
| @Test |
| public void testProfileNetworkPrefWrongPreference() throws Exception { |
| final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID); |
| mServiceContext.setWorkProfile(testHandle, true); |
| assertThrows("Should not be able to set an illegal preference", |
| IllegalArgumentException.class, |
| () -> mCm.setProfileNetworkPreference(testHandle, |
| PROFILE_NETWORK_PREFERENCE_ENTERPRISE + 1, null, null)); |
| } |
| |
| /** |
| * Make sure requests for per-profile default networking for a non-work profile are |
| * rejected |
| */ |
| @Test |
| public void testProfileNetworkPrefWrongProfile() throws Exception { |
| final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID); |
| mServiceContext.setWorkProfile(testHandle, false); |
| assertThrows("Should not be able to set a user pref for a non-work profile", |
| IllegalArgumentException.class , () -> |
| mCm.setProfileNetworkPreference(testHandle, |
| PROFILE_NETWORK_PREFERENCE_ENTERPRISE, null, null)); |
| } |
| |
| @Test |
| public void testSubIdsClearedWithoutNetworkFactoryPermission() throws Exception { |
| mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_DENIED); |
| final NetworkCapabilities nc = new NetworkCapabilities(); |
| nc.setSubscriptionIds(Collections.singleton(Process.myUid())); |
| |
| final NetworkCapabilities result = |
| mService.networkCapabilitiesRestrictedForCallerPermissions( |
| nc, Process.myPid(), Process.myUid()); |
| assertTrue(result.getSubscriptionIds().isEmpty()); |
| } |
| |
| @Test |
| public void testSubIdsExistWithNetworkFactoryPermission() throws Exception { |
| mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED); |
| |
| final Set<Integer> subIds = Collections.singleton(Process.myUid()); |
| final NetworkCapabilities nc = new NetworkCapabilities(); |
| nc.setSubscriptionIds(subIds); |
| |
| final NetworkCapabilities result = |
| mService.networkCapabilitiesRestrictedForCallerPermissions( |
| nc, Process.myPid(), Process.myUid()); |
| assertEquals(subIds, result.getSubscriptionIds()); |
| } |
| |
| private NetworkRequest getRequestWithSubIds() { |
| return new NetworkRequest.Builder() |
| .setSubscriptionIds(Collections.singleton(Process.myUid())) |
| .build(); |
| } |
| |
| @Test |
| public void testNetworkRequestWithSubIdsWithNetworkFactoryPermission() throws Exception { |
| mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED); |
| final PendingIntent pendingIntent = PendingIntent.getBroadcast( |
| mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE); |
| final NetworkCallback networkCallback1 = new NetworkCallback(); |
| final NetworkCallback networkCallback2 = new NetworkCallback(); |
| |
| mCm.requestNetwork(getRequestWithSubIds(), networkCallback1); |
| mCm.requestNetwork(getRequestWithSubIds(), pendingIntent); |
| mCm.registerNetworkCallback(getRequestWithSubIds(), networkCallback2); |
| |
| mCm.unregisterNetworkCallback(networkCallback1); |
| mCm.releaseNetworkRequest(pendingIntent); |
| mCm.unregisterNetworkCallback(networkCallback2); |
| } |
| |
| @Test |
| public void testNetworkRequestWithSubIdsWithoutNetworkFactoryPermission() throws Exception { |
| mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_DENIED); |
| final PendingIntent pendingIntent = PendingIntent.getBroadcast( |
| mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE); |
| |
| final Class<SecurityException> expected = SecurityException.class; |
| assertThrows( |
| expected, () -> mCm.requestNetwork(getRequestWithSubIds(), new NetworkCallback())); |
| assertThrows(expected, () -> mCm.requestNetwork(getRequestWithSubIds(), pendingIntent)); |
| assertThrows( |
| expected, |
| () -> mCm.registerNetworkCallback(getRequestWithSubIds(), new NetworkCallback())); |
| } |
| |
| /** |
| * Validate request counts are counted accurately on setProfileNetworkPreference on set/replace. |
| */ |
| @Test |
| public void testProfileNetworkPrefCountsRequestsCorrectlyOnSet() throws Exception { |
| final UserHandle testHandle = setupEnterpriseNetwork(); |
| 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(); |
| |
| // 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); |
| } |
| }); |
| }); |
| } |
| |
| /** |
| * Validate request counts are counted accurately on setOemNetworkPreference on set/replace. |
| */ |
| @Test |
| public void testSetOemNetworkPreferenceCountsRequestsCorrectlyOnSet() throws Exception { |
| mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, true); |
| @OemNetworkPreferences.OemNetworkPreference final int networkPref = |
| OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY; |
| // 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(); |
| })); |
| } |
| |
| private void testRequestCountLimits(final int countToLeaveAvailable, |
| @NonNull final ExceptionalRunnable r) throws Exception { |
| final ArraySet<TestNetworkCallback> callbacks = new ArraySet<>(); |
| try { |
| final int requestCount = mService.mSystemNetworkRequestCounter |
| .mUidToNetworkRequestCount.get(Process.myUid()); |
| // 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 < countToFile; i++) { |
| final TestNetworkCallback cb = new TestNetworkCallback(); |
| mCm.registerDefaultNetworkCallback(cb); |
| callbacks.add(cb); |
| } |
| 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); |
| } |
| } |
| } |
| |
| private void assertCreateNrisFromMobileDataPreferredUids(Set<Integer> uids) { |
| final Set<NetworkRequestInfo> nris = |
| mService.createNrisFromMobileDataPreferredUids(uids); |
| final NetworkRequestInfo nri = nris.iterator().next(); |
| // Verify that one NRI is created with multilayer requests. Because one NRI can contain |
| // multiple uid ranges, so it only need create one NRI here. |
| assertEquals(1, nris.size()); |
| assertTrue(nri.isMultilayerRequest()); |
| assertEquals(nri.getUids(), uidRangesForUids(uids)); |
| assertEquals(PREFERENCE_PRIORITY_MOBILE_DATA_PREFERERRED, nri.mPreferencePriority); |
| } |
| |
| /** |
| * Test createNrisFromMobileDataPreferredUids returns correct NetworkRequestInfo. |
| */ |
| @Test |
| public void testCreateNrisFromMobileDataPreferredUids() { |
| // Verify that empty uid set should not create any NRI for it. |
| final Set<NetworkRequestInfo> nrisNoUid = |
| mService.createNrisFromMobileDataPreferredUids(new ArraySet<>()); |
| assertEquals(0, nrisNoUid.size()); |
| |
| final int uid1 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID); |
| final int uid2 = PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID2); |
| final int uid3 = SECONDARY_USER_HANDLE.getUid(TEST_PACKAGE_UID); |
| assertCreateNrisFromMobileDataPreferredUids(Set.of(uid1)); |
| assertCreateNrisFromMobileDataPreferredUids(Set.of(uid1, uid3)); |
| assertCreateNrisFromMobileDataPreferredUids(Set.of(uid1, uid2)); |
| } |
| |
| private void setAndUpdateMobileDataPreferredUids(Set<Integer> uids) { |
| ConnectivitySettingsManager.setMobileDataPreferredUids(mServiceContext, uids); |
| mService.updateMobileDataPreferredUids(); |
| waitForIdle(); |
| } |
| |
| /** |
| * Test that MOBILE_DATA_PREFERRED_UIDS changes will send correct net id and uid ranges to netd. |
| */ |
| @Test |
| public void testMobileDataPreferredUidsChanged() throws Exception { |
| final InOrder inorder = inOrder(mMockNetd); |
| registerDefaultNetworkCallbacks(); |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| mTestPackageDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| |
| final int cellNetId = mCellNetworkAgent.getNetwork().netId; |
| inorder.verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical( |
| cellNetId, INetd.PERMISSION_NONE)); |
| |
| // Initial mobile data preferred uids status. |
| setAndUpdateMobileDataPreferredUids(Set.of()); |
| inorder.verify(mMockNetd, never()).networkAddUidRangesParcel(any()); |
| inorder.verify(mMockNetd, never()).networkRemoveUidRangesParcel(any()); |
| |
| // Set MOBILE_DATA_PREFERRED_UIDS setting and verify that net id and uid ranges send to netd |
| final Set<Integer> uids1 = Set.of(PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)); |
| final UidRangeParcel[] uidRanges1 = toUidRangeStableParcels(uidRangesForUids(uids1)); |
| final NativeUidRangeConfig config1 = new NativeUidRangeConfig(cellNetId, uidRanges1, |
| PREFERENCE_PRIORITY_MOBILE_DATA_PREFERERRED); |
| setAndUpdateMobileDataPreferredUids(uids1); |
| inorder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(config1); |
| inorder.verify(mMockNetd, never()).networkRemoveUidRangesParcel(any()); |
| |
| // Set MOBILE_DATA_PREFERRED_UIDS setting again and verify that old rules are removed and |
| // new rules are added. |
| final Set<Integer> uids2 = Set.of(PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID), |
| PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID2), |
| SECONDARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)); |
| final UidRangeParcel[] uidRanges2 = toUidRangeStableParcels(uidRangesForUids(uids2)); |
| final NativeUidRangeConfig config2 = new NativeUidRangeConfig(cellNetId, uidRanges2, |
| PREFERENCE_PRIORITY_MOBILE_DATA_PREFERERRED); |
| setAndUpdateMobileDataPreferredUids(uids2); |
| inorder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(config1); |
| inorder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(config2); |
| |
| // Clear MOBILE_DATA_PREFERRED_UIDS setting again and verify that old rules are removed and |
| // new rules are not added. |
| setAndUpdateMobileDataPreferredUids(Set.of()); |
| inorder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(config2); |
| inorder.verify(mMockNetd, never()).networkAddUidRangesParcel(any()); |
| } |
| |
| /** |
| * Make sure mobile data preferred uids feature behaves as expected when the mobile network |
| * goes up and down while the uids is set. Make sure they behave as expected whether |
| * there is a general default network or not. |
| */ |
| @Test |
| public void testMobileDataPreferenceForMobileNetworkUpDown() throws Exception { |
| final InOrder inorder = inOrder(mMockNetd); |
| // File a request for cell to ensure it doesn't go down. |
| final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); |
| final NetworkRequest cellRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_CELLULAR).build(); |
| mCm.requestNetwork(cellRequest, cellNetworkCallback); |
| cellNetworkCallback.assertNoCallback(); |
| |
| registerDefaultNetworkCallbacks(); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(true); |
| mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); |
| mTestPackageDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID)); |
| |
| final int wifiNetId = mWiFiNetworkAgent.getNetwork().netId; |
| inorder.verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical( |
| wifiNetId, INetd.PERMISSION_NONE)); |
| |
| // Initial mobile data preferred uids status. |
| setAndUpdateMobileDataPreferredUids(Set.of()); |
| inorder.verify(mMockNetd, never()).networkAddUidRangesParcel(any()); |
| inorder.verify(mMockNetd, never()).networkRemoveUidRangesParcel(any()); |
| |
| // Set MOBILE_DATA_PREFERRED_UIDS setting and verify that wifi net id and uid ranges send to |
| // netd. |
| final Set<Integer> uids = Set.of(PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)); |
| final UidRangeParcel[] uidRanges = toUidRangeStableParcels(uidRangesForUids(uids)); |
| final NativeUidRangeConfig wifiConfig = new NativeUidRangeConfig(wifiNetId, uidRanges, |
| PREFERENCE_PRIORITY_MOBILE_DATA_PREFERERRED); |
| setAndUpdateMobileDataPreferredUids(uids); |
| inorder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(wifiConfig); |
| inorder.verify(mMockNetd, never()).networkRemoveUidRangesParcel(any()); |
| |
| // Cellular network connected. mTestPackageDefaultNetworkCallback should receive |
| // callback with cellular network and net id and uid ranges should be updated to netd. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| mDefaultNetworkCallback.assertNoCallback(); |
| mTestPackageDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID)); |
| |
| final int cellNetId = mCellNetworkAgent.getNetwork().netId; |
| final NativeUidRangeConfig cellConfig = new NativeUidRangeConfig(cellNetId, uidRanges, |
| PREFERENCE_PRIORITY_MOBILE_DATA_PREFERERRED); |
| inorder.verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical( |
| cellNetId, INetd.PERMISSION_NONE)); |
| inorder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(cellConfig); |
| inorder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(wifiConfig); |
| |
| // Cellular network disconnected. mTestPackageDefaultNetworkCallback should receive |
| // callback with wifi network from fallback request. |
| mCellNetworkAgent.disconnect(); |
| mDefaultNetworkCallback.assertNoCallback(); |
| cellNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| mTestPackageDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent); |
| mTestPackageDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID)); |
| inorder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(wifiConfig); |
| inorder.verify(mMockNetd, never()).networkRemoveUidRangesParcel(any()); |
| inorder.verify(mMockNetd).networkDestroy(cellNetId); |
| |
| // Cellular network comes back. mTestPackageDefaultNetworkCallback should receive |
| // callback with cellular network. |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| mDefaultNetworkCallback.assertNoCallback(); |
| mTestPackageDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID)); |
| |
| final int cellNetId2 = mCellNetworkAgent.getNetwork().netId; |
| final NativeUidRangeConfig cellConfig2 = new NativeUidRangeConfig(cellNetId2, uidRanges, |
| PREFERENCE_PRIORITY_MOBILE_DATA_PREFERERRED); |
| inorder.verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical( |
| cellNetId2, INetd.PERMISSION_NONE)); |
| inorder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(cellConfig2); |
| inorder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(wifiConfig); |
| |
| // Wifi network disconnected. mTestPackageDefaultNetworkCallback should not receive |
| // any callback. |
| mWiFiNetworkAgent.disconnect(); |
| mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); |
| mTestPackageDefaultNetworkCallback.assertNoCallback(); |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID)); |
| waitForIdle(); |
| inorder.verify(mMockNetd, never()).networkAddUidRangesParcel(any()); |
| inorder.verify(mMockNetd, never()).networkRemoveUidRangesParcel(any()); |
| inorder.verify(mMockNetd).networkDestroy(wifiNetId); |
| |
| mCm.unregisterNetworkCallback(cellNetworkCallback); |
| } |
| |
| @Test |
| public void testMultilayerRequestsOfSetMobileDataPreferredUids() throws Exception { |
| // First set mobile data preferred uid to create a multi-layer requests: 1. request for |
| // cellular, 2. track the default network for fallback. |
| setAndUpdateMobileDataPreferredUids( |
| Set.of(PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID))); |
| |
| final HandlerThread handlerThread = new HandlerThread("MockFactory"); |
| handlerThread.start(); |
| final NetworkCapabilities cellFilter = new NetworkCapabilities() |
| .addTransportType(TRANSPORT_CELLULAR) |
| .addCapability(NET_CAPABILITY_INTERNET) |
| .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); |
| final MockNetworkFactory cellFactory = new MockNetworkFactory(handlerThread.getLooper(), |
| mServiceContext, "cellFactory", cellFilter, mCsHandlerThread); |
| cellFactory.setScoreFilter(40); |
| |
| try { |
| cellFactory.register(); |
| // Default internet request and the mobile data preferred request. |
| cellFactory.expectRequestAdds(2); |
| cellFactory.assertRequestCountEquals(2); |
| |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(true); |
| |
| // The cellFactory however is outscored, and should lose default internet request. |
| // But it should still see mobile data preferred request. |
| cellFactory.expectRequestRemove(); |
| cellFactory.assertRequestCountEquals(1); |
| |
| mWiFiNetworkAgent.disconnect(); |
| // The network satisfying the default internet request has disconnected, so the |
| // cellFactory sees the default internet requests again. |
| cellFactory.expectRequestAdd(); |
| cellFactory.assertRequestCountEquals(2); |
| } finally { |
| cellFactory.terminate(); |
| handlerThread.quitSafely(); |
| } |
| } |
| |
| /** |
| * Validate request counts are counted accurately on MOBILE_DATA_PREFERRED_UIDS change |
| * on set/replace. |
| */ |
| @Test |
| public void testMobileDataPreferredUidsChangedCountsRequestsCorrectlyOnSet() throws Exception { |
| ConnectivitySettingsManager.setMobileDataPreferredUids(mServiceContext, |
| Set.of(PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID))); |
| // 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(); |
| })); |
| } |
| |
| @Test |
| public void testAllNetworkPreferencesCanCoexist() |
| throws Exception { |
| final InOrder inorder = inOrder(mMockNetd); |
| @OemNetworkPreferences.OemNetworkPreference final int networkPref = |
| OEM_NETWORK_PREFERENCE_OEM_PAID; |
| final UserHandle testHandle = setupEnterpriseNetwork(); |
| |
| setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true); |
| final int cellNetId = mCellNetworkAgent.getNetwork().netId; |
| inorder.verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical( |
| cellNetId, INetd.PERMISSION_NONE)); |
| |
| // Set oem network preference |
| final int[] uids1 = new int[] { PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID) }; |
| final UidRangeParcel[] uidRanges1 = toUidRangeStableParcels(uidRangesForUids(uids1)); |
| final NativeUidRangeConfig config1 = new NativeUidRangeConfig(cellNetId, uidRanges1, |
| PREFERENCE_PRIORITY_OEM); |
| setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges1, TEST_PACKAGE_NAME); |
| inorder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(config1); |
| inorder.verify(mMockNetd, never()).networkRemoveUidRangesParcel(any()); |
| |
| // Set user profile network preference |
| final TestNetworkAgentWrapper workAgent = makeEnterpriseNetworkAgent(); |
| workAgent.connect(true); |
| |
| final TestOnCompleteListener listener = new TestOnCompleteListener(); |
| mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, |
| r -> r.run(), listener); |
| listener.expectOnComplete(); |
| final NativeUidRangeConfig config2 = new NativeUidRangeConfig(workAgent.getNetwork().netId, |
| uidRangeFor(testHandle), PREFERENCE_PRIORITY_PROFILE); |
| inorder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical( |
| workAgent.getNetwork().netId, INetd.PERMISSION_SYSTEM)); |
| inorder.verify(mMockNetd, never()).networkRemoveUidRangesParcel(any()); |
| inorder.verify(mMockNetd).networkAddUidRangesParcel(config2); |
| |
| // Set MOBILE_DATA_PREFERRED_UIDS setting |
| final Set<Integer> uids2 = Set.of(PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID2)); |
| final UidRangeParcel[] uidRanges2 = toUidRangeStableParcels(uidRangesForUids(uids2)); |
| final NativeUidRangeConfig config3 = new NativeUidRangeConfig(cellNetId, uidRanges2, |
| PREFERENCE_PRIORITY_MOBILE_DATA_PREFERERRED); |
| setAndUpdateMobileDataPreferredUids(uids2); |
| inorder.verify(mMockNetd, never()).networkRemoveUidRangesParcel(any()); |
| inorder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(config3); |
| |
| // Set oem network preference again with different uid. |
| final Set<Integer> uids3 = Set.of(PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID3)); |
| final UidRangeParcel[] uidRanges3 = toUidRangeStableParcels(uidRangesForUids(uids3)); |
| final NativeUidRangeConfig config4 = new NativeUidRangeConfig(cellNetId, uidRanges3, |
| PREFERENCE_PRIORITY_OEM); |
| setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges3, "com.android.test"); |
| inorder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(config1); |
| inorder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(config4); |
| |
| // Remove user profile network preference |
| mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_DEFAULT, |
| r -> r.run(), listener); |
| listener.expectOnComplete(); |
| inorder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(config2); |
| inorder.verify(mMockNetd, never()).networkAddUidRangesParcel(any()); |
| |
| // Set MOBILE_DATA_PREFERRED_UIDS setting again with same uid as oem network preference. |
| final NativeUidRangeConfig config6 = new NativeUidRangeConfig(cellNetId, uidRanges3, |
| PREFERENCE_PRIORITY_MOBILE_DATA_PREFERERRED); |
| setAndUpdateMobileDataPreferredUids(uids3); |
| inorder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(config3); |
| inorder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(config6); |
| } |
| |
| @Test |
| public void testNetworkCallbackAndActiveNetworkForUid_AllNetworkPreferencesEnabled() |
| throws Exception { |
| // File a request for cell to ensure it doesn't go down. |
| final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback(); |
| final NetworkRequest cellRequest = new NetworkRequest.Builder() |
| .addTransportType(TRANSPORT_CELLULAR).build(); |
| mCm.requestNetwork(cellRequest, cellNetworkCallback); |
| cellNetworkCallback.assertNoCallback(); |
| |
| // Register callbacks and have wifi network as default network. |
| registerDefaultNetworkCallbacks(); |
| mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI); |
| mWiFiNetworkAgent.connect(true); |
| mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); |
| mProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); |
| mTestPackageDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), |
| mCm.getActiveNetworkForUid(TEST_WORK_PROFILE_APP_UID)); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID)); |
| |
| // Set MOBILE_DATA_PREFERRED_UIDS setting with TEST_WORK_PROFILE_APP_UID and |
| // TEST_PACKAGE_UID. Both mProfileDefaultNetworkCallback and |
| // mTestPackageDefaultNetworkCallback should receive callback with cell network. |
| setAndUpdateMobileDataPreferredUids(Set.of(TEST_WORK_PROFILE_APP_UID, TEST_PACKAGE_UID)); |
| mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); |
| mCellNetworkAgent.connect(true); |
| cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| mDefaultNetworkCallback.assertNoCallback(); |
| mProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| mTestPackageDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); |
| assertEquals(mCellNetworkAgent.getNetwork(), |
| mCm.getActiveNetworkForUid(TEST_WORK_PROFILE_APP_UID)); |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID)); |
| |
| // Set user profile network preference with test profile. mProfileDefaultNetworkCallback |
| // should receive callback with higher priority network preference (enterprise network). |
| // The others should have no callbacks. |
| final UserHandle testHandle = setupEnterpriseNetwork(); |
| final TestNetworkAgentWrapper workAgent = makeEnterpriseNetworkAgent(); |
| workAgent.connect(true); |
| final TestOnCompleteListener listener = new TestOnCompleteListener(); |
| mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE, |
| r -> r.run(), listener); |
| listener.expectOnComplete(); |
| assertNoCallbacks(mDefaultNetworkCallback, mTestPackageDefaultNetworkCallback); |
| mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(workAgent); |
| assertEquals(workAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_WORK_PROFILE_APP_UID)); |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID)); |
| |
| // Set oem network preference with TEST_PACKAGE_UID. mTestPackageDefaultNetworkCallback |
| // should receive callback with higher priority network preference (current default network) |
| // and the others should have no callbacks. |
| @OemNetworkPreferences.OemNetworkPreference final int networkPref = |
| OEM_NETWORK_PREFERENCE_OEM_PAID; |
| final int[] uids1 = new int[] { TEST_PACKAGE_UID }; |
| final UidRangeParcel[] uidRanges1 = toUidRangeStableParcels(uidRangesForUids(uids1)); |
| setupSetOemNetworkPreferenceForPreferenceTest(networkPref, uidRanges1, TEST_PACKAGE_NAME); |
| assertNoCallbacks(mDefaultNetworkCallback, mProfileDefaultNetworkCallback); |
| mTestPackageDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID)); |
| assertEquals(workAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_WORK_PROFILE_APP_UID)); |
| |
| // Set oem network preference with TEST_WORK_PROFILE_APP_UID. Both |
| // mProfileDefaultNetworkCallback and mTestPackageDefaultNetworkCallback should receive |
| // callback. |
| final int[] uids2 = new int[] { TEST_WORK_PROFILE_APP_UID }; |
| final UidRangeParcel[] uidRanges2 = toUidRangeStableParcels(uidRangesForUids(uids2)); |
| when(mUserManager.getUserHandles(anyBoolean())).thenReturn(Arrays.asList(testHandle)); |
| setupSetOemNetworkPreferenceForPreferenceTest( |
| networkPref, uidRanges2, "com.android.test", testHandle); |
| mDefaultNetworkCallback.assertNoCallback(); |
| mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent); |
| mTestPackageDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); |
| assertEquals(mWiFiNetworkAgent.getNetwork(), |
| mCm.getActiveNetworkForUid(TEST_WORK_PROFILE_APP_UID)); |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID)); |
| |
| // Remove oem network preference, mProfileDefaultNetworkCallback should receive callback |
| // with current highest priority network preference (enterprise network) and the others |
| // should have no callbacks. |
| final TestOemListenerCallback oemPrefListener = new TestOemListenerCallback(); |
| mService.setOemNetworkPreference( |
| new OemNetworkPreferences.Builder().build(), oemPrefListener); |
| oemPrefListener.expectOnComplete(); |
| assertNoCallbacks(mDefaultNetworkCallback, mTestPackageDefaultNetworkCallback); |
| mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(workAgent); |
| assertEquals(workAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_WORK_PROFILE_APP_UID)); |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID)); |
| |
| // Remove user profile network preference. |
| mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_DEFAULT, |
| r -> r.run(), listener); |
| listener.expectOnComplete(); |
| assertNoCallbacks(mDefaultNetworkCallback, mTestPackageDefaultNetworkCallback); |
| mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); |
| assertEquals(mCellNetworkAgent.getNetwork(), |
| mCm.getActiveNetworkForUid(TEST_WORK_PROFILE_APP_UID)); |
| assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID)); |
| |
| // Disconnect wifi |
| mWiFiNetworkAgent.disconnect(); |
| assertNoCallbacks(mProfileDefaultNetworkCallback, mTestPackageDefaultNetworkCallback); |
| mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent); |
| mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent); |
| } |
| |
| @Test |
| public void testRequestRouteToHostAddress_PackageDoesNotBelongToCaller() { |
| assertThrows(SecurityException.class, () -> mService.requestRouteToHostAddress( |
| ConnectivityManager.TYPE_NONE, null /* hostAddress */, "com.not.package.owner", |
| null /* callingAttributionTag */)); |
| } |
| } |