blob: 6cde62a5ea3518ea347cf74fa825a1139a7d9a7b [file] [log] [blame]
/*
* 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.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.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
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, Supplier<Integer> ifAbsent) {
final Integer granted = mMockedPermissions.get(permission);
return granted != null ? granted : ifAbsent.get();
}
@Override
public int checkPermission(String permission, int pid, int uid) {
return checkMockedPermission(
permission, () -> super.checkPermission(permission, pid, uid));
}
@Override
public int checkCallingOrSelfPermission(String permission) {
return checkMockedPermission(
permission, () -> super.checkCallingOrSelfPermission(permission));
}
@Override
public void enforceCallingOrSelfPermission(String permission, String message) {
final Integer granted = mMockedPermissions.get(permission);
if (granted == null) {
super.enforceCallingOrSelfPermission(permission, message);
return;
}
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}.
*
* <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);
}
@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 {
if (mServiceContext.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
r.run();
return;
}
try {
mServiceContext.setPermission(permission, PERMISSION_GRANTED);
r.run();
} finally {
mServiceContext.setPermission(permission, PERMISSION_DENIED);
}
}
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 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();
testRequestCountLimits(() -> {
// Set initially to test the limit prior to having existing requests.
final TestOnCompleteListener listener = new TestOnCompleteListener();
mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE,
Runnable::run, listener);
listener.expectOnComplete();
// re-set so as to test the limit as part of replacing existing requests.
mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_ENTERPRISE,
Runnable::run, listener);
listener.expectOnComplete();
});
}
/**
* 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;
testRequestCountLimits(() -> {
// 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(@NonNull final Runnable 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.
final int maxCount =
ConnectivityService.MAX_NETWORK_REQUESTS_PER_SYSTEM_UID - requestCount;
// Need permission so registerDefaultNetworkCallback uses mSystemNetworkRequestCounter
withPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, () -> {
for (int i = 1; i < maxCount - 1; i++) {
final TestNetworkCallback cb = new TestNetworkCallback();
mCm.registerDefaultNetworkCallback(cb);
callbacks.add(cb);
}
// Code to run to check if it triggers a max request count limit error.
r.run();
});
} 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)));
testRequestCountLimits(() -> {
// 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);
}
}