/*
 * 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.MANAGE_TEST_NETWORKS;
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_ETHERNET;
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.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.FIREWALL_CHAIN_DOZABLE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_1;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_2;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_OEM_DENY_3;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
import static android.net.ConnectivityManager.FIREWALL_RULE_ALLOW;
import static android.net.ConnectivityManager.FIREWALL_RULE_DEFAULT;
import static android.net.ConnectivityManager.FIREWALL_RULE_DENY;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_DEFAULT;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE;
import static android.net.ConnectivityManager.PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK;
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_MMTEL;
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.NET_ENTERPRISE_ID_1;
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_2;
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_3;
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_4;
import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_5;
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_TEST;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
import static android.net.NetworkScore.KEEP_CONNECTED_FOR_HANDOVER;
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST;
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_TEST_ONLY;
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_UNINITIALIZED;
import static android.net.RouteInfo.RTN_UNREACHABLE;
import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_ADDED;
import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.PREFIX_OPERATION_REMOVED;
import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_FAILURE;
import static android.net.resolv.aidl.IDnsResolverUnsolicitedEventListener.VALIDATION_RESULT_SUCCESS;
import static android.os.Process.INVALID_UID;
import static android.system.OsConstants.IPPROTO_TCP;

import static com.android.server.ConnectivityService.MAX_NETWORK_REQUESTS_PER_SYSTEM_UID;
import static com.android.server.ConnectivityService.PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED;
import static com.android.server.ConnectivityService.PREFERENCE_ORDER_OEM;
import static com.android.server.ConnectivityService.PREFERENCE_ORDER_PROFILE;
import static com.android.server.ConnectivityService.PREFERENCE_ORDER_VPN;
import static com.android.server.ConnectivityServiceTestUtils.transportToLegacyType;
import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackRegister;
import static com.android.server.NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister;
import static com.android.testutils.ConcurrentUtils.await;
import static com.android.testutils.ConcurrentUtils.durationOf;
import static com.android.testutils.DevSdkIgnoreRule.IgnoreAfter;
import static com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2;
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.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
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.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 java.util.Arrays.asList;

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.admin.DevicePolicyManager;
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.ConnectivityDiagnosticsManager.DataStallReport;
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.NetworkProvider;
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.PacProxyManager;
import android.net.ProfileNetworkPreference;
import android.net.Proxy;
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.TelephonyNetworkSpecifier;
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.net.wifi.WifiInfo;
import android.os.BadParcelableException;
import android.os.BatteryStatsManager;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.INetworkManagementService;
import android.os.Looper;
import android.os.Messenger;
import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.PersistableBundle;
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.annotations.GuardedBy;
import com.android.internal.app.IBatteryStats;
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile;
import com.android.internal.util.WakeupMessage;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.modules.utils.build.SdkLevel;
import com.android.net.module.util.ArrayTrackRecord;
import com.android.net.module.util.CollectionUtils;
import com.android.net.module.util.LocationPermissionChecker;
import com.android.networkstack.apishim.NetworkAgentConfigShimImpl;
import com.android.networkstack.apishim.api29.ConstantsShim;
import com.android.server.ConnectivityService.ConnectivityDiagnosticsCallbackInfo;
import com.android.server.ConnectivityService.NetworkRequestInfo;
import com.android.server.ConnectivityServiceTest.ConnectivityServiceDependencies.ReportedInterfaces;
import com.android.server.connectivity.CarrierPrivilegeAuthenticator;
import com.android.server.connectivity.ClatCoordinator;
import com.android.server.connectivity.ConnectivityFlags;
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.UidRangeUtils;
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 com.android.testutils.TestableNetworkOfferCallback;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
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.Map;
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";

    @Rule
    public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();

    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 int TEST_APP_ID_2 = 104;
    private static final int TEST_WORK_PROFILE_APP_UID_2 =
            UserHandle.getUid(TEST_WORK_PROFILE_USER_ID, TEST_APP_ID_2);
    private static final int TEST_APP_ID_3 = 105;
    private static final int TEST_APP_ID_4 = 106;
    private static final int TEST_APP_ID_5 = 107;

    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 ConnectivityServiceDependencies 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 ProxyTracker mProxyTracker;
    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;
    private TestNetworkCallback mProfileDefaultNetworkCallbackAsAppUid2;
    private TestNetworkCallback mTestPackageDefaultNetworkCallback2;

    // 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 EthernetManager mEthernetManager;
    @Mock NetworkPolicyManager mNetworkPolicyManager;
    @Mock VpnProfileStore mVpnProfileStore;
    @Mock SystemConfigManager mSystemConfigManager;
    @Mock DevicePolicyManager mDevicePolicyManager;
    @Mock Resources mResources;
    @Mock ClatCoordinator mClatCoordinator;
    @Mock PacProxyManager mPacProxyManager;
    @Mock BpfNetMaps mBpfNetMaps;
    @Mock CarrierPrivilegeAuthenticator mCarrierPrivilegeAuthenticator;

    // BatteryStatsManager is final and cannot be mocked with regular mockito, so just mock the
    // underlying binder calls.
    final BatteryStatsManager mBatteryStatsManager =
            new BatteryStatsManager(mock(IBatteryStats.class));

    private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
            ArgumentCaptor.forClass(ResolverParamsParcel.class);

    // This class exists to test bindProcessToNetwork and getBoundNetworkForProcess. These methods
    // do not go through ConnectivityService but talk to netd directly, so they don't automatically
    // reflect the state of our test ConnectivityService.
    private class WrappedConnectivityManager extends ConnectivityManager {
        private Network mFakeBoundNetwork;

        public synchronized boolean bindProcessToNetwork(Network network) {
            mFakeBoundNetwork = network;
            return true;
        }

        public synchronized Network getBoundNetworkForProcess() {
            return mFakeBoundNetwork;
        }

        public WrappedConnectivityManager(Context context, ConnectivityService service) {
            super(context, service);
        }
    }

    private class MockContext extends BroadcastInterceptingContext {
        private final MockContentResolver mContentResolver;

        @Spy private Resources mInternalResources;
        private final LinkedBlockingQueue<Intent> mStartedActivities = new LinkedBlockingQueue<>();

        // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant
        // For permissions granted across the board, the key is only the permission name.
        // For permissions only granted to a combination of uid/pid, the key
        // is "<permission name>,<pid>,<uid>". PID+UID permissons have priority over generic ones.
        private final HashMap<String, Integer> mMockedPermissions = new HashMap<>();

        private void mockStringResource(int resId) {
            doAnswer((inv) -> {
                return "Mock string resource ID=" + inv.getArgument(0);
            }).when(mInternalResources).getString(resId);
        }

        MockContext(Context base, ContentProvider settingsProvider) {
            super(base);

            mInternalResources = spy(base.getResources());
            doReturn(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",
            }).when(mInternalResources)
                    .getStringArray(com.android.internal.R.array.networkAttributes);

            final int[] stringResourcesToMock = new int[] {
                com.android.internal.R.string.config_customVpnAlwaysOnDisconnectedDialogComponent,
                com.android.internal.R.string.vpn_lockdown_config,
                com.android.internal.R.string.vpn_lockdown_connected,
                com.android.internal.R.string.vpn_lockdown_connecting,
                com.android.internal.R.string.vpn_lockdown_disconnected,
                com.android.internal.R.string.vpn_lockdown_error,
            };
            for (int resId : stringResourcesToMock) {
                mockStringResource(resId);
            }

            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)
                    && !ConstantsShim.ACTION_VPN_MANAGER_EVENT.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.DEVICE_POLICY_SERVICE.equals(name)) return mDevicePolicyManager;
            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;
            if (Context.PAC_PROXY_SERVICE.equals(name)) return mPacProxyManager;
            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()));
        }

        public void setDeviceOwner(@NonNull final UserHandle userHandle, String value) {
            // This relies on all contexts for a given user returning the same UM mock
            final DevicePolicyManager dpmMock = createContextAsUser(userHandle, 0 /* flags */)
                    .getSystemService(DevicePolicyManager.class);
            doReturn(value).when(dpmMock).getDeviceOwner();
            doReturn(value).when(mDevicePolicyManager).getDeviceOwner();
        }

        @Override
        public ContentResolver getContentResolver() {
            return mContentResolver;
        }

        @Override
        public Resources getResources() {
            return mInternalResources;
        }

        @Override
        public PackageManager getPackageManager() {
            return mPackageManager;
        }

        private int checkMockedPermission(String permission, int pid, int uid,
                Supplier<Integer> ifAbsent) {
            final Integer granted = mMockedPermissions.get(permission + "," + pid + "," + uid);
            if (null != granted) {
                return granted;
            }
            final Integer allGranted = mMockedPermissions.get(permission);
            if (null != allGranted) {
                return allGranted;
            }
            return ifAbsent.get();
        }

        @Override
        public int checkPermission(String permission, int pid, int uid) {
            return checkMockedPermission(permission, pid, uid,
                    () -> super.checkPermission(permission, pid, uid));
        }

        @Override
        public int checkCallingOrSelfPermission(String permission) {
            return checkMockedPermission(permission, Process.myPid(), Process.myUid(),
                    () -> super.checkCallingOrSelfPermission(permission));
        }

        @Override
        public void enforceCallingOrSelfPermission(String permission, String message) {
            final Integer granted = checkMockedPermission(permission,
                    Process.myPid(), Process.myUid(),
                    () -> {
                        super.enforceCallingOrSelfPermission(permission, message);
                        // enforce will crash if the permission is not granted
                        return PERMISSION_GRANTED;
                    });

            if (!granted.equals(PERMISSION_GRANTED)) {
                throw new SecurityException("[Test] permission denied: " + permission);
            }
        }

        /**
         * Mock checks for the specified permission, and have them behave as per {@code granted}.
         *
         * This will apply across the board no matter what the checked UID and PID are.
         *
         * <p>Passing null reverts to default behavior, which does a real permission check on the
         * test package.
         * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or
         *                {@link PackageManager#PERMISSION_DENIED}.
         */
        public void setPermission(String permission, Integer granted) {
            mMockedPermissions.put(permission, granted);
        }

        /**
         * Mock checks for the specified permission, and have them behave as per {@code granted}.
         *
         * This will only apply to the passed UID and PID.
         *
         * <p>Passing null reverts to default behavior, which does a real permission check on the
         * test package.
         * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or
         *                {@link PackageManager#PERMISSION_DENIED}.
         */
        public void setPermission(String permission, int pid, int uid, Integer granted) {
            final String key = permission + "," + pid + "," + uid;
            mMockedPermissions.put(key, granted);
        }

        @Override
        public Intent registerReceiverForAllUsers(@Nullable BroadcastReceiver receiver,
                @NonNull IntentFilter filter, @Nullable String broadcastPermission,
                @Nullable Handler scheduler) {
            // TODO: ensure MultinetworkPolicyTracker's BroadcastReceiver is tested; just returning
            // null should not pass the test
            return null;
        }
    }

    // This was only added in the T SDK, but this test needs to build against the R+S SDKs, too.
    private static int toSdkSandboxUid(int appUid) {
        final int firstSdkSandboxUid = 20000;
        return appUid + (firstSdkSandboxUid - Process.FIRST_APPLICATION_UID);
    }

    // This function assumes the UID range for user 0 ([1, 99999])
    private static UidRangeParcel[] uidRangeParcelsExcludingUids(Integer... excludedUids) {
        int start = 1;
        Arrays.sort(excludedUids);
        List<UidRangeParcel> parcels = new ArrayList<UidRangeParcel>();
        for (int excludedUid : excludedUids) {
            if (excludedUid == start) {
                start++;
            } else {
                parcels.add(new UidRangeParcel(start, excludedUid - 1));
                start = excludedUid + 1;
            }
        }
        if (start <= 99999) {
            parcels.add(new UidRangeParcel(start, 99999));
        }

        return parcels.toArray(new UidRangeParcel[0]);
    }

    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 /* ncTemplate */, null /* provider */);
        }

        TestNetworkAgentWrapper(int transport, LinkProperties linkProperties)
                throws Exception {
            this(transport, linkProperties, null /* ncTemplate */, null /* provider */);
        }

        private TestNetworkAgentWrapper(int transport, LinkProperties linkProperties,
                NetworkCapabilities ncTemplate) throws Exception {
            this(transport, linkProperties, ncTemplate, null /* provider */);
        }

        private TestNetworkAgentWrapper(int transport, LinkProperties linkProperties,
                NetworkCapabilities ncTemplate, NetworkProvider provider) throws Exception {
            super(transport, linkProperties, ncTemplate, provider, 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);
        }

        class TestInstrumentedNetworkAgent extends InstrumentedNetworkAgent {
            TestInstrumentedNetworkAgent(NetworkAgentWrapper wrapper, LinkProperties lp,
                    NetworkAgentConfig nac, NetworkProvider provider) {
                super(wrapper, lp, nac, provider);
            }

            @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();
            }
        }

        @Override
        protected InstrumentedNetworkAgent makeNetworkAgent(LinkProperties linkProperties,
                NetworkAgentConfig nac, NetworkProvider provider) 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).notifyNetworkConnectedParcel(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 TestInstrumentedNetworkAgent(this, linkProperties, nac, provider);

            assertEquals(na.getNetwork().netId, nmNetworkCaptor.getValue().netId);
            mNmCallbacks = nmCbCaptor.getValue();

            mNmCallbacks.onNetworkMonitorCreated(mNetworkMonitor);

            return na;
        }

        private void onValidationRequested() throws Exception {
            if (SdkLevel.isAtLeastT()) {
                verify(mNetworkMonitor).notifyNetworkConnectedParcel(any());
            } else {
                verify(mNetworkMonitor).notifyNetworkConnected(any(), any());
            }
            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) {
            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());
        }

        // Trigger releasing the request as unfulfillable
        public void triggerUnfulfillable(NetworkRequest r) {
            super.releaseRequestAsUnfulfillableByAnyFactory(r);
        }

        public void assertNoRequestChanged() {
            // Make sure there are no remaining requests unaccounted for.
            HandlerUtils.waitForIdle(mHandlerSendingRequests, TIMEOUT_MS);
            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();
        }

        public NetworkAgentConfig getNetworkAgentConfig() {
            return null == mMockNetworkAgent ? null : mMockNetworkAgent.getNetworkAgentConfig();
        }

        @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_ORDER_VPN));
            verify(mMockNetd, never()).networkRemoveUidRangesParcel(argThat(config ->
                    mMockVpn.getNetwork().getNetId() == config.netId
                            && PREFERENCE_ORDER_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 UidRangeParcel[] intToUidRangeStableParcels(final @NonNull Set<Integer> ranges) {
        return ranges.stream().map(r -> new UidRangeParcel(r, r)).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) {
        mDeps.setCallingUid(uid);
        try {
            return what.get();
        } finally {
            mDeps.setCallingUid(null);
        }
    }

    private void doAsUid(final int uid, @NonNull final Runnable what) {
        doAsUid(uid, () -> {
            what.run(); return Void.TYPE;
        });
    }

    private void registerNetworkCallbackAsUid(NetworkRequest request, NetworkCallback callback,
            int uid) {
        doAsUid(uid, () -> {
            mCm.registerNetworkCallback(request, callback);
        });
    }

    private void registerDefaultNetworkCallbackAsUid(@NonNull final NetworkCallback callback,
            final int uid) {
        doAsUid(uid, () -> {
            mCm.registerDefaultNetworkCallback(callback);
            waitForIdle();
        });
    }

    private interface ExceptionalRunnable {
        void run() throws Exception;
    }

    private void withPermission(String permission, ExceptionalRunnable r) throws Exception {
        try {
            mServiceContext.setPermission(permission, PERMISSION_GRANTED);
            r.run();
        } finally {
            mServiceContext.setPermission(permission, null);
        }
    }

    private void withPermission(String permission, int pid, int uid, ExceptionalRunnable r)
            throws Exception {
        try {
            mServiceContext.setPermission(permission, pid, uid, PERMISSION_GRANTED);
            r.run();
        } finally {
            mServiceContext.setPermission(permission, pid, uid, null);
        }
    }

    private static final int PRIMARY_USER = 0;
    private static final int SECONDARY_USER = 10;
    private static final int TERTIARY_USER = 11;
    private static final UidRange PRIMARY_UIDRANGE =
            UidRange.createForUser(UserHandle.of(PRIMARY_USER));
    private static final int APP1_UID = UserHandle.getUid(PRIMARY_USER, 10100);
    private static final int APP2_UID = UserHandle.getUid(PRIMARY_USER, 10101);
    private static final int VPN_UID = UserHandle.getUid(PRIMARY_USER, 10043);
    private static final UserInfo PRIMARY_USER_INFO = new UserInfo(PRIMARY_USER, "",
            UserInfo.FLAG_PRIMARY);
    private static final UserHandle PRIMARY_USER_HANDLE = new UserHandle(PRIMARY_USER);
    private static final UserHandle SECONDARY_USER_HANDLE = new UserHandle(SECONDARY_USER);
    private static final UserHandle TERTIARY_USER_HANDLE = new UserHandle(TERTIARY_USER);

    private static final int RESTRICTED_USER = 1;
    private static final UserInfo RESTRICTED_USER_INFO = new UserInfo(RESTRICTED_USER, "",
            UserInfo.FLAG_RESTRICTED);
    static {
        RESTRICTED_USER_INFO.restrictedProfileParentId = PRIMARY_USER;
    }

    @Before
    public void setUp() throws Exception {
        mNetIdManager = new TestNetIdManager();

        mContext = InstrumentationRegistry.getContext();

        MockitoAnnotations.initMocks(this);

        doReturn(asList(PRIMARY_USER_INFO)).when(mUserManager).getAliveUsers();
        doReturn(asList(PRIMARY_USER_HANDLE)).when(mUserManager).getUserHandles(anyBoolean());
        doReturn(PRIMARY_USER_INFO).when(mUserManager).getUserInfo(PRIMARY_USER);
        // canHaveRestrictedProfile does not take a userId. It applies to the userId of the context
        // it was started from, i.e., PRIMARY_USER.
        doReturn(true).when(mUserManager).canHaveRestrictedProfile();
        doReturn(RESTRICTED_USER_INFO).when(mUserManager).getUserInfo(RESTRICTED_USER);

        final ApplicationInfo applicationInfo = new ApplicationInfo();
        applicationInfo.targetSdkVersion = Build.VERSION_CODES.Q;
        doReturn(applicationInfo).when(mPackageManager)
                .getApplicationInfoAsUser(anyString(), anyInt(), any());
        doReturn(applicationInfo.targetSdkVersion).when(mPackageManager)
                .getTargetSdkVersion(anyString());
        doReturn(new int[0]).when(mSystemConfigManager).getSystemPermissionUids(anyString());

        // 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);
        mockHasSystemFeature(FEATURE_ETHERNET, 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");
        mProxyTracker = new ProxyTracker(mServiceContext, mock(Handler.class),
                16 /* EVENT_PROXY_HAS_CHANGED */);

        initMockedResources();
        final Context mockResContext = mock(Context.class);
        doReturn(mResources).when(mockResContext).getResources();
        ConnectivityResources.setResourcesContextForTest(mockResContext);
        mDeps = new ConnectivityServiceDependencies(mockResContext);

        mService = new ConnectivityService(mServiceContext,
                mMockDnsResolver,
                mock(IpConnectivityLog.class),
                mMockNetd,
                mDeps);
        mService.mLingerDelayMs = TEST_LINGER_DELAY_MS;
        mService.mNascentDelayMs = TEST_NASCENT_DELAY_MS;

        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 initMockedResources() {
        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);
        doReturn(true).when(mResources)
                .getBoolean(R.bool.config_cellular_radio_timesharing_capable);
    }

    class ConnectivityServiceDependencies extends ConnectivityService.Dependencies {
        final ConnectivityResources mConnRes;
        @Mock final MockableSystemProperties mSystemProperties;

        ConnectivityServiceDependencies(final Context mockResContext) {
            mSystemProperties = mock(MockableSystemProperties.class);
            doReturn(false).when(mSystemProperties).getBoolean("ro.radio.noril", false);

            mConnRes = new ConnectivityResources(mockResContext);
        }

        @Override
        public MockableSystemProperties getSystemProperties() {
            return mSystemProperties;
        }

        @Override
        public HandlerThread makeHandlerThread() {
            return mCsHandlerThread;
        }

        @Override
        public NetworkStackClientBase getNetworkStack() {
            return mNetworkStack;
        }

        @Override
        public ProxyTracker makeProxyTracker(final Context context, final Handler handler) {
            return mProxyTracker;
        }

        @Override
        public NetIdManager makeNetIdManager() {
            return mNetIdManager;
        }

        @Override
        public boolean queryUserAccess(final int uid, final Network network,
                final ConnectivityService cs) {
            return true;
        }

        @Override
        public MultinetworkPolicyTracker makeMultinetworkPolicyTracker(final Context c,
                final Handler h, final Runnable r) {
            if (null != mPolicyTracker) {
                throw new IllegalStateException("Multinetwork policy tracker already initialized");
            }
            mPolicyTracker = new WrappedMultinetworkPolicyTracker(mServiceContext, h, r);
            return mPolicyTracker;
        }

        @Override
        public ConnectivityResources getResources(final Context ctx) {
            return mConnRes;
        }

        @Override
        public LocationPermissionChecker makeLocationPermissionChecker(final Context context) {
            return new LocationPermissionChecker(context) {
                @Override
                protected int getCurrentUser() {
                    return runAsShell(CREATE_USERS, () -> super.getCurrentUser());
                }
            };
        }

        @Override
        public CarrierPrivilegeAuthenticator makeCarrierPrivilegeAuthenticator(
                @NonNull final Context context, @NonNull final TelephonyManager tm) {
            return SdkLevel.isAtLeastT() ? mCarrierPrivilegeAuthenticator : null;
        }

        @Override
        public boolean intentFilterEquals(final PendingIntent a, final PendingIntent b) {
            return runAsShell(GET_INTENT_SENDER_INTENT, () -> a.intentFilterEquals(b));
        }

        @GuardedBy("this")
        private Integer mCallingUid = null;

        @Override
        public int getCallingUid() {
            synchronized (this) {
                if (null != mCallingUid) return mCallingUid;
                return super.getCallingUid();
            }
        }

        // Pass null for the real calling UID
        public void setCallingUid(final Integer uid) {
            synchronized (this) {
                mCallingUid = uid;
            }
        }

        @GuardedBy("this")
        private boolean mCellular464XlatEnabled = true;

        @Override
        public boolean getCellular464XlatEnabled() {
            synchronized (this) {
                return mCellular464XlatEnabled;
            }
        }

        public void setCellular464XlatEnabled(final boolean enabled) {
            synchronized (this) {
                mCellular464XlatEnabled = enabled;
            }
        }

        @GuardedBy("this")
        private Integer mConnectionOwnerUid = null;

        @Override
        public int getConnectionOwnerUid(final int protocol, final InetSocketAddress local,
                final InetSocketAddress remote) {
            synchronized (this) {
                if (null != mConnectionOwnerUid) return mConnectionOwnerUid;
                return super.getConnectionOwnerUid(protocol, local, remote);
            }
        }

        // Pass null to get the production implementation of getConnectionOwnerUid
        public void setConnectionOwnerUid(final Integer uid) {
            synchronized (this) {
                mConnectionOwnerUid = uid;
            }
        }

        final class ReportedInterfaces {
            public final Context context;
            public final String iface;
            public final int[] transportTypes;
            ReportedInterfaces(final Context c, final String i, final int[] t) {
                context = c;
                iface = i;
                transportTypes = t;
            }

            public boolean contentEquals(final Context c, final String i, final int[] t) {
                return Objects.equals(context, c) && Objects.equals(iface, i)
                        && Arrays.equals(transportTypes, t);
            }
        }

        final ArrayTrackRecord<ReportedInterfaces> mReportedInterfaceHistory =
                new ArrayTrackRecord<>();

        @Override
        public void reportNetworkInterfaceForTransports(final Context context, final String iface,
                final int[] transportTypes) {
            mReportedInterfaceHistory.add(new ReportedInterfaces(context, iface, transportTypes));
            super.reportNetworkInterfaceForTransports(context, iface, transportTypes);
        }

        @Override
        public boolean isFeatureEnabled(Context context, String name, boolean defaultEnabled) {
            switch (name) {
                case ConnectivityFlags.NO_REMATCH_ALL_REQUESTS_ON_REGISTER:
                    return true;
                default:
                    return super.isFeatureEnabled(context, name, defaultEnabled);
            }
        }

        @Override
        public BpfNetMaps getBpfNetMaps(INetd netd) {
            return mBpfNetMaps;
        }

        @Override
        public ClatCoordinator getClatCoordinator(INetd netd) {
            return mClatCoordinator;
        }

        final ArrayTrackRecord<Pair<String, Long>> mRateLimitHistory = new ArrayTrackRecord<>();
        final Map<String, Long> mActiveRateLimit = new HashMap<>();

        @Override
        public void enableIngressRateLimit(final String iface, final long rateInBytesPerSecond) {
            mRateLimitHistory.add(new Pair<>(iface, rateInBytesPerSecond));
            // Due to a TC limitation, the rate limit needs to be removed before it can be
            // updated. Check that this happened.
            assertEquals(-1L, (long) mActiveRateLimit.getOrDefault(iface, -1L));
            mActiveRateLimit.put(iface, rateInBytesPerSecond);
            // verify that clsact qdisc has already been created, otherwise attaching a tc police
            // filter will fail.
            try {
                verify(mMockNetd).networkAddInterface(anyInt(), eq(iface));
            } catch (RemoteException e) {
                fail(e.getMessage());
            }
        }

        @Override
        public void disableIngressRateLimit(final String iface) {
            mRateLimitHistory.add(new Pair<>(iface, -1L));
            assertNotEquals(-1L, (long) mActiveRateLimit.getOrDefault(iface, -1L));
            mActiveRateLimit.put(iface, -1L);
        }
    }

    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);
        doReturn(new String[] {myPackageName}).when(mPackageManager)
                .getPackagesForUid(Binder.getCallingUid());
        doReturn(myPackageInfo).when(mPackageManager).getPackageInfoAsUser(
                eq(myPackageName), anyInt(), eq(UserHandle.getCallingUserId()));

        doReturn(asList(new PackageInfo[] {
                buildPackageInfo(/* SYSTEM */ false, APP1_UID),
                buildPackageInfo(/* SYSTEM */ false, APP2_UID),
                buildPackageInfo(/* SYSTEM */ false, VPN_UID)
        })).when(mPackageManager).getInstalledPackagesAsUser(eq(GET_PERMISSIONS), anyInt());

        // 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+.
        doReturn(applicationInfo).when(mPackageManager).getApplicationInfoAsUser(
                eq(ALWAYS_ON_PACKAGE), anyInt(), eq(userId));

        // Minimal mocking to keep Vpn#isAlwaysOnPackageSupported happy.
        ResolveInfo rInfo = new ResolveInfo();
        rInfo.serviceInfo = new ServiceInfo();
        rInfo.serviceInfo.metaData = new Bundle();
        final List<ResolveInfo> services = asList(new ResolveInfo[]{rInfo});
        doReturn(services).when(mPackageManager).queryIntentServicesAsUser(
                any(), eq(PackageManager.GET_META_DATA), eq(userId));
        doReturn(Process.myUid()).when(mPackageManager).getPackageUidAsUser(
                TEST_PACKAGE_NAME, userId);
        doReturn(VPN_UID).when(mPackageManager).getPackageUidAsUser(ALWAYS_ON_PACKAGE, userId);
    }

    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 ExpectedBroadcast expectProxyChangeAction(ProxyInfo proxy) {
        return registerPacProxyBroadcastThat(intent -> {
            final ProxyInfo actualProxy = (ProxyInfo) intent.getExtra(Proxy.EXTRA_PROXY_INFO,
                    ProxyInfo.buildPacProxy(Uri.EMPTY));
            return proxy.equals(actualProxy);
        });
    }

    private ExpectedBroadcast registerPacProxyBroadcast() {
        return registerPacProxyBroadcastThat(intent -> true);
    }

    private ExpectedBroadcast registerPacProxyBroadcastThat(
            @NonNull final Predicate<Intent> filter) {
        final IntentFilter intentFilter = new IntentFilter(Proxy.PROXY_CHANGE_ACTION);
        // AtomicReference allows receiver to access expected even though it is constructed later.
        final AtomicReference<ExpectedBroadcast> expectedRef = new AtomicReference<>();
        final BroadcastReceiver receiver = new BroadcastReceiver() {
            public void onReceive(Context context, Intent intent) {
                final ProxyInfo proxy = (ProxyInfo) intent.getExtra(
                            Proxy.EXTRA_PROXY_INFO, ProxyInfo.buildPacProxy(Uri.EMPTY));
                Log.d(TAG, "Receive PROXY_CHANGE_ACTION, proxy = " + proxy);
                if (filter.test(intent)) {
                    expectedRef.get().complete(intent);
                }
            }
        };
        final ExpectedBroadcast expected = new ExpectedBroadcast(receiver);
        expectedRef.set(expected);
        mServiceContext.registerReceiver(receiver, intentFilter);
        return expected;
    }

    private boolean extraInfoInBroadcastHasExpectedNullness(NetworkInfo ni) {
        final DetailedState state = ni.getDetailedState();
        if (state == DetailedState.CONNECTED && ni.getExtraInfo() == null) return false;
        // Expect a null extraInfo if the network is CONNECTING, because a CONNECTIVITY_ACTION
        // broadcast with a state of CONNECTING only happens due to legacy VPN lockdown, which also
        // nulls out extraInfo.
        if (state == DetailedState.CONNECTING && ni.getExtraInfo() != null) return false;
        // Can't make any assertions about DISCONNECTED broadcasts. When a network actually
        // disconnects, disconnectAndDestroyNetwork sets its state to DISCONNECTED and its extraInfo
        // to null. But if the DISCONNECTED broadcast is just simulated by LegacyTypeTracker due to
        // a network switch, extraInfo will likely be populated.
        // This is likely a bug in CS, but likely not one we can fix without impacting apps.
        return true;
    }

    private ExpectedBroadcast expectConnectivityAction(int type, NetworkInfo.DetailedState state) {
        return registerConnectivityBroadcastThat(1, intent -> {
            final int actualType = intent.getIntExtra(EXTRA_NETWORK_TYPE, -1);
            final NetworkInfo ni = intent.getParcelableExtra(EXTRA_NETWORK_INFO);
            return type == actualType
                    && state == ni.getDetailedState()
                    && extraInfoInBroadcastHasExpectedNullness(ni);
        });
    }

    @Test
    public void testNetworkTypes() {
        // Ensure that our mocks for the networkAttributes config variable work as expected. If they
        // don't, then tests that depend on CONNECTIVITY_ACTION broadcasts for these network types
        // will fail. Failing here is much easier to debug.
        assertTrue(mCm.isNetworkSupported(TYPE_WIFI));
        assertTrue(mCm.isNetworkSupported(TYPE_MOBILE));
        assertTrue(mCm.isNetworkSupported(TYPE_MOBILE_MMS));
        assertTrue(mCm.isNetworkSupported(TYPE_MOBILE_FOTA));
        assertFalse(mCm.isNetworkSupported(TYPE_PROXY));

        // Check that TYPE_ETHERNET is supported. Unlike the asserts above, which only validate our
        // mocks, this assert exercises the ConnectivityService code path that ensures that
        // TYPE_ETHERNET is supported if the ethernet service is running.
        assertTrue(mCm.isNetworkSupported(TYPE_ETHERNET));
    }

    @Test
    public void testNetworkFeature() throws Exception {
        // Connect the cell agent and wait for the connected broadcast.
        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
        mCellNetworkAgent.addCapability(NET_CAPABILITY_SUPL);
        ExpectedBroadcast b = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED);
        mCellNetworkAgent.connect(true);
        b.expectBroadcast();

        // Build legacy request for SUPL.
        final NetworkCapabilities legacyCaps = new NetworkCapabilities();
        legacyCaps.addTransportType(TRANSPORT_CELLULAR);
        legacyCaps.addCapability(NET_CAPABILITY_SUPL);
        final NetworkRequest legacyRequest = new NetworkRequest(legacyCaps, TYPE_MOBILE_SUPL,
                ConnectivityManager.REQUEST_ID_UNSET, NetworkRequest.Type.REQUEST);

        // File request, withdraw it and make sure no broadcast is sent
        b = registerConnectivityBroadcast(1);
        final TestNetworkCallback callback = new TestNetworkCallback();
        mCm.requestNetwork(legacyRequest, callback);
        callback.expectCallback(CallbackEntry.AVAILABLE, mCellNetworkAgent);
        mCm.unregisterNetworkCallback(callback);
        b.expectNoBroadcast(800);  // 800ms long enough to at least flake if this is sent

        // Disconnect the network and expect mobile disconnected broadcast.
        b = expectConnectivityAction(TYPE_MOBILE, DetailedState.DISCONNECTED);
        mCellNetworkAgent.disconnect();
        b.expectBroadcast();
    }

    @Test
    public void testLingering() throws Exception {
        verifyNoNetwork();
        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
        assertNull(mCm.getActiveNetworkInfo());
        assertNull(mCm.getActiveNetwork());
        // Test bringing up validated cellular.
        ExpectedBroadcast b = expectConnectivityAction(TYPE_MOBILE, DetailedState.CONNECTED);
        mCellNetworkAgent.connect(true);
        b.expectBroadcast();
        verifyActiveNetwork(TRANSPORT_CELLULAR);
        assertLength(2, mCm.getAllNetworks());
        assertTrue(mCm.getAllNetworks()[0].equals(mCm.getActiveNetwork()) ||
                mCm.getAllNetworks()[1].equals(mCm.getActiveNetwork()));
        assertTrue(mCm.getAllNetworks()[0].equals(mWiFiNetworkAgent.getNetwork()) ||
                mCm.getAllNetworks()[1].equals(mWiFiNetworkAgent.getNetwork()));
        // Test bringing up validated WiFi.
        b = registerConnectivityBroadcast(2);
        mWiFiNetworkAgent.connect(true);
        b.expectBroadcast();
        verifyActiveNetwork(TRANSPORT_WIFI);
        assertLength(2, mCm.getAllNetworks());
        assertTrue(mCm.getAllNetworks()[0].equals(mCm.getActiveNetwork()) ||
                mCm.getAllNetworks()[1].equals(mCm.getActiveNetwork()));
        assertTrue(mCm.getAllNetworks()[0].equals(mCellNetworkAgent.getNetwork()) ||
                mCm.getAllNetworks()[1].equals(mCellNetworkAgent.getNetwork()));
        // Test cellular linger timeout.
        mCellNetworkAgent.expectDisconnected();
        waitForIdle();
        assertLength(1, mCm.getAllNetworks());
        verifyActiveNetwork(TRANSPORT_WIFI);
        assertLength(1, mCm.getAllNetworks());
        assertEquals(mCm.getAllNetworks()[0], mCm.getActiveNetwork());
        // Test WiFi disconnect.
        b = registerConnectivityBroadcast(1);
        mWiFiNetworkAgent.disconnect();
        b.expectBroadcast();
        verifyNoNetwork();
    }

    /**
     * Verify a newly created network will be inactive instead of torn down even if no one is
     * requesting.
     */
    @Test
    public void testNewNetworkInactive() throws Exception {
        // Create a callback that monitoring the testing network.
        final TestNetworkCallback listenCallback = new TestNetworkCallback();
        mCm.registerNetworkCallback(new NetworkRequest.Builder().build(), listenCallback);

        // 1. Create a network that is not requested by anyone, and does not satisfy any of the
        // default requests. Verify that the network will be inactive instead of torn down.
        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
        mWiFiNetworkAgent.connectWithoutInternet();
        listenCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
        listenCallback.assertNoCallback();

        // Verify that the network will be torn down after nascent expiry. A small period of time
        // is added in case of flakiness.
        final int nascentTimeoutMs =
                mService.mNascentDelayMs + mService.mNascentDelayMs / 4;
        listenCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent, nascentTimeoutMs);

        // 2. Create a network that is satisfied by a request comes later.
        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
        mWiFiNetworkAgent.connectWithoutInternet();
        listenCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
        final NetworkRequest wifiRequest = new NetworkRequest.Builder()
                .addTransportType(TRANSPORT_WIFI).build();
        final TestNetworkCallback wifiCallback = new TestNetworkCallback();
        mCm.requestNetwork(wifiRequest, wifiCallback);
        wifiCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);

        // Verify that the network will be kept since the request is still satisfied. And is able
        // to get disconnected as usual if the request is released after the nascent timer expires.
        listenCallback.assertNoCallback(nascentTimeoutMs);
        mCm.unregisterNetworkCallback(wifiCallback);
        listenCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);

        // 3. Create a network that is satisfied by a request comes later.
        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
        mWiFiNetworkAgent.connectWithoutInternet();
        listenCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
        mCm.requestNetwork(wifiRequest, wifiCallback);
        wifiCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);

        // Verify that the network will still be torn down after the request gets removed.
        mCm.unregisterNetworkCallback(wifiCallback);
        listenCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);

        // There is no need to ensure that LOSING is never sent in the common case that the
        // network immediately satisfies a request that was already present, because it is already
        // verified anywhere whenever {@code TestNetworkCallback#expectAvailable*} is called.

        mCm.unregisterNetworkCallback(listenCallback);
    }

    /**
     * Verify a newly created network will be inactive and switch to background if only background
     * request is satisfied.
     */
    @Test
    public void testNewNetworkInactive_bgNetwork() throws Exception {
        // Create a callback that monitoring the wifi network.
        final TestNetworkCallback wifiListenCallback = new TestNetworkCallback();
        mCm.registerNetworkCallback(new NetworkRequest.Builder()
                .addTransportType(TRANSPORT_WIFI).build(), wifiListenCallback);

        // Create callbacks that can monitor background and foreground mobile networks.
        // This is done by granting using background networks permission before registration. Thus,
        // the service will not add {@code NET_CAPABILITY_FOREGROUND} by default.
        grantUsingBackgroundNetworksPermissionForUid(Binder.getCallingUid());
        final TestNetworkCallback bgMobileListenCallback = new TestNetworkCallback();
        final TestNetworkCallback fgMobileListenCallback = new TestNetworkCallback();
        mCm.registerNetworkCallback(new NetworkRequest.Builder()
                .addTransportType(TRANSPORT_CELLULAR).build(), bgMobileListenCallback);
        mCm.registerNetworkCallback(new NetworkRequest.Builder()
                .addTransportType(TRANSPORT_CELLULAR)
                .addCapability(NET_CAPABILITY_FOREGROUND).build(), fgMobileListenCallback);

        // Connect wifi, which satisfies default request.
        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
        mWiFiNetworkAgent.connect(true);
        wifiListenCallback.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);

        // Connect a cellular network, verify that satisfies only the background callback.
        setAlwaysOnNetworks(true);
        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
        mCellNetworkAgent.connect(true);
        bgMobileListenCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
        fgMobileListenCallback.assertNoCallback();
        assertFalse(isForegroundNetwork(mCellNetworkAgent));

        mCellNetworkAgent.disconnect();
        bgMobileListenCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
        fgMobileListenCallback.assertNoCallback();

        mCm.unregisterNetworkCallback(wifiListenCallback);
        mCm.unregisterNetworkCallback(bgMobileListenCallback);
        mCm.unregisterNetworkCallback(fgMobileListenCallback);
    }

    @Test
    public void testBinderDeathAfterUnregister() throws Exception {
        final NetworkCapabilities caps = new NetworkCapabilities.Builder()
                .addTransportType(TRANSPORT_WIFI)
                .build();
        final Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
        final Messenger messenger = new Messenger(handler);
        final CompletableFuture<Binder.DeathRecipient> deathRecipient = new CompletableFuture<>();
        final Binder binder = new Binder() {
            private DeathRecipient mDeathRecipient;
            @Override
            public void linkToDeath(@NonNull final DeathRecipient recipient, final int flags) {
                synchronized (this) {
                    mDeathRecipient = recipient;
                }
                super.linkToDeath(recipient, flags);
                deathRecipient.complete(recipient);
            }

            @Override
            public boolean unlinkToDeath(@NonNull final DeathRecipient recipient, final int flags) {
                synchronized (this) {
                    if (null == mDeathRecipient) {
                        throw new IllegalStateException();
                    }
                    mDeathRecipient = null;
                }
                return super.unlinkToDeath(recipient, flags);
            }
        };
        final NetworkRequest request = mService.listenForNetwork(caps, messenger, binder,
                NetworkCallback.FLAG_NONE, mContext.getOpPackageName(),
                mContext.getAttributionTag());
        mService.releaseNetworkRequest(request);
        deathRecipient.get().binderDied();
        // Wait for the release message to be processed.
        waitForIdle();
        // After waitForIdle(), the message was processed and the service didn't crash.
    }

    // TODO : migrate to @Parameterized
    @Test
    public void testValidatedCellularOutscoresUnvalidatedWiFi_CanTimeShare() throws Exception {
        // The behavior of this test should be the same whether the radio can time share or not.
        doTestValidatedCellularOutscoresUnvalidatedWiFi(true);
    }

    // TODO : migrate to @Parameterized
    @Test
    public void testValidatedCellularOutscoresUnvalidatedWiFi_CannotTimeShare() throws Exception {
        doTestValidatedCellularOutscoresUnvalidatedWiFi(false);
    }

    public void doTestValidatedCellularOutscoresUnvalidatedWiFi(
            final boolean cellRadioTimesharingCapable) throws Exception {
        mService.mCellularRadioTimesharingCapable = cellRadioTimesharingCapable;
        // 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();
    }

    // TODO : migrate to @Parameterized
    @Test
    public void testUnvalidatedWifiOutscoresUnvalidatedCellular_CanTimeShare() throws Exception {
        doTestUnvalidatedWifiOutscoresUnvalidatedCellular(true);
    }

    // TODO : migrate to @Parameterized
    @Test
    public void testUnvalidatedWifiOutscoresUnvalidatedCellular_CannotTimeShare() throws Exception {
        doTestUnvalidatedWifiOutscoresUnvalidatedCellular(false);
    }

    public void doTestUnvalidatedWifiOutscoresUnvalidatedCellular(
            final boolean cellRadioTimesharingCapable) throws Exception {
        mService.mCellularRadioTimesharingCapable = cellRadioTimesharingCapable;
        // 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();
    }

    // TODO : migrate to @Parameterized
    @Test
    public void testUnlingeringDoesNotValidate_CanTimeShare() throws Exception {
        doTestUnlingeringDoesNotValidate(true);
    }

    // TODO : migrate to @Parameterized
    @Test
    public void testUnlingeringDoesNotValidate_CannotTimeShare() throws Exception {
        doTestUnlingeringDoesNotValidate(false);
    }

    public void doTestUnlingeringDoesNotValidate(
            final boolean cellRadioTimesharingCapable) throws Exception {
        mService.mCellularRadioTimesharingCapable = cellRadioTimesharingCapable;
        // 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));
    }

    // TODO : migrate to @Parameterized
    @Test
    public void testRequestMigrationToSameTransport_CanTimeShare() throws Exception {
        // Simulate a device where the cell radio is capable of time sharing
        mService.mCellularRadioTimesharingCapable = true;
        doTestRequestMigrationToSameTransport(TRANSPORT_CELLULAR, true);
        doTestRequestMigrationToSameTransport(TRANSPORT_WIFI, true);
        doTestRequestMigrationToSameTransport(TRANSPORT_ETHERNET, true);
    }

    // TODO : migrate to @Parameterized
    @Test
    public void testRequestMigrationToSameTransport_CannotTimeShare() throws Exception {
        // Simulate a device where the cell radio is not capable of time sharing
        mService.mCellularRadioTimesharingCapable = false;
        doTestRequestMigrationToSameTransport(TRANSPORT_CELLULAR, false);
        doTestRequestMigrationToSameTransport(TRANSPORT_WIFI, true);
        doTestRequestMigrationToSameTransport(TRANSPORT_ETHERNET, true);
    }

    public void doTestRequestMigrationToSameTransport(final int transport,
            final boolean expectLingering) throws Exception {
        // To speed up tests the linger delay is very short by default in tests but this
        // test needs to make sure the delay is not incurred so a longer value is safer (it
        // reduces the risk that a bug exists but goes undetected). The alarm manager in the test
        // throws and crashes CS if this is set to anything more than the below constant though.
        mService.mLingerDelayMs = UNREASONABLY_LONG_ALARM_WAIT_MS;

        final TestNetworkCallback generalCb = new TestNetworkCallback();
        final TestNetworkCallback defaultCb = new TestNetworkCallback();
        mCm.registerNetworkCallback(
                new NetworkRequest.Builder().addTransportType(transport | transport).build(),
                generalCb);
        mCm.registerDefaultNetworkCallback(defaultCb);

        // Bring up net agent 1
        final TestNetworkAgentWrapper net1 = new TestNetworkAgentWrapper(transport);
        net1.connect(true);
        // Make sure the default request is on net 1
        generalCb.expectAvailableThenValidatedCallbacks(net1);
        defaultCb.expectAvailableThenValidatedCallbacks(net1);

        // Bring up net 2 with primary and mms
        final TestNetworkAgentWrapper net2 = new TestNetworkAgentWrapper(transport);
        net2.addCapability(NET_CAPABILITY_MMS);
        net2.setScore(new NetworkScore.Builder().setTransportPrimary(true).build());
        net2.connect(true);

        // Make sure the default request goes to net 2
        generalCb.expectAvailableCallbacksUnvalidated(net2);
        if (expectLingering) {
            generalCb.expectCallback(CallbackEntry.LOSING, net1);
        }
        generalCb.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, net2);
        defaultCb.expectAvailableDoubleValidatedCallbacks(net2);

        // Make sure cell 1 is unwanted immediately if the radio can't time share, but only
        // after some delay if it can.
        if (expectLingering) {
            net1.assertNotDisconnected(TEST_CALLBACK_TIMEOUT_MS); // always incurs the timeout
            generalCb.assertNoCallback();
            // assertNotDisconnected waited for TEST_CALLBACK_TIMEOUT_MS, so waiting for the
            // linger period gives TEST_CALLBACK_TIMEOUT_MS time for the event to process.
            net1.expectDisconnected(UNREASONABLY_LONG_ALARM_WAIT_MS);
        } else {
            net1.expectDisconnected(TEST_CALLBACK_TIMEOUT_MS);
        }
        net1.disconnect();
        generalCb.expectCallback(CallbackEntry.LOST, net1);

        // Remove primary from net 2
        net2.setScore(new NetworkScore.Builder().build());
        // Request MMS
        final TestNetworkCallback mmsCallback = new TestNetworkCallback();
        mCm.requestNetwork(new NetworkRequest.Builder().addCapability(NET_CAPABILITY_MMS).build(),
                mmsCallback);
        mmsCallback.expectAvailableCallbacksValidated(net2);

        // Bring up net 3 with primary but without MMS
        final TestNetworkAgentWrapper net3 = new TestNetworkAgentWrapper(transport);
        net3.setScore(new NetworkScore.Builder().setTransportPrimary(true).build());
        net3.connect(true);

        // Make sure default goes to net 3, but the MMS request doesn't
        generalCb.expectAvailableThenValidatedCallbacks(net3);
        defaultCb.expectAvailableDoubleValidatedCallbacks(net3);
        mmsCallback.assertNoCallback();
        net2.assertNotDisconnected(TEST_CALLBACK_TIMEOUT_MS); // Always incurs the timeout

        // Revoke MMS request and make sure net 2 is torn down with the appropriate delay
        mCm.unregisterNetworkCallback(mmsCallback);
        if (expectLingering) {
            // If the radio can time share, the linger delay hasn't elapsed yet, so apps will
            // get LOSING. If the radio can't time share, this is a hard loss, since the last
            // request keeping up this network has been removed and the network isn't lingering
            // for any other request.
            generalCb.expectCallback(CallbackEntry.LOSING, net2);
            net2.assertNotDisconnected(TEST_CALLBACK_TIMEOUT_MS);
            generalCb.assertNoCallback();
            net2.expectDisconnected(UNREASONABLY_LONG_ALARM_WAIT_MS);
        } else {
            net2.expectDisconnected(TEST_CALLBACK_TIMEOUT_MS);
        }
        net2.disconnect();
        generalCb.expectCallback(CallbackEntry.LOST, net2);
        defaultCb.assertNoCallback();

        net3.disconnect();
        mCm.unregisterNetworkCallback(defaultCb);
        mCm.unregisterNetworkCallback(generalCb);
    }

    // TODO : migrate to @Parameterized
    @Test
    public void testCellularOutscoresWeakWifi_CanTimeShare() throws Exception {
        // The behavior of this test should be the same whether the radio can time share or not.
        doTestCellularOutscoresWeakWifi(true);
    }

    // TODO : migrate to @Parameterized
    @Test
    public void testCellularOutscoresWeakWifi_CannotTimeShare() throws Exception {
        doTestCellularOutscoresWeakWifi(false);
    }

    public void doTestCellularOutscoresWeakWifi(
            final boolean cellRadioTimesharingCapable) throws Exception {
        mService.mCellularRadioTimesharingCapable = cellRadioTimesharingCapable;
        // 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);
    }

    // TODO : migrate to @Parameterized
    @Test
    public void testReapingNetwork_CanTimeShare() throws Exception {
        doTestReapingNetwork(true);
    }

    // TODO : migrate to @Parameterized
    @Test
    public void testReapingNetwork_CannotTimeShare() throws Exception {
        doTestReapingNetwork(false);
    }

    public void doTestReapingNetwork(
            final boolean cellRadioTimesharingCapable) throws Exception {
        mService.mCellularRadioTimesharingCapable = cellRadioTimesharingCapable;
        // 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();
    }

    // TODO : migrate to @Parameterized
    @Test
    public void testCellularFallback_CanTimeShare() throws Exception {
        doTestCellularFallback(true);
    }

    // TODO : migrate to @Parameterized
    @Test
    public void testCellularFallback_CannotTimeShare() throws Exception {
        doTestCellularFallback(false);
    }

    public void doTestCellularFallback(
            final boolean cellRadioTimesharingCapable) throws Exception {
        mService.mCellularRadioTimesharingCapable = cellRadioTimesharingCapable;
        // 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);
    }

    // TODO : migrate to @Parameterized
    @Test
    public void testWiFiFallback_CanTimeShare() throws Exception {
        doTestWiFiFallback(true);
    }

    // TODO : migrate to @Parameterized
    @Test
    public void testWiFiFallback_CannotTimeShare() throws Exception {
        doTestWiFiFallback(false);
    }

    public void doTestWiFiFallback(
            final boolean cellRadioTimesharingCapable) throws Exception {
        mService.mCellularRadioTimesharingCapable = cellRadioTimesharingCapable;
        // 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(
                NetworkAgentConfigShimImpl.newInstance(null),
                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 {
        doReturn(buildPackageInfo(true /* hasSystemPermission */, uid)).when(mPackageManager)
                .getPackageInfo(eq(packageName), eq(GET_PERMISSIONS));
        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, /*excludeLocalRoutes=*/ false);
    }

    private NativeNetworkConfig nativeNetworkConfigVpn(int netId, boolean secure, int vpnType) {
        return new NativeNetworkConfig(netId, NativeNetworkType.VIRTUAL, INetd.PERMISSION_NONE,
                secure, vpnType, /*excludeLocalRoutes=*/ false);
    }

    @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 || capability == NET_CAPABILITY_MMTEL) {
            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();
        testFactory.assertNoRequestChanged();
        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_MMTEL);
        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();
            testFactory.assertNoRequestChanged();

            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);
        final NetworkRequest request1 = new NetworkRequest.Builder()
                .addCapability(NET_CAPABILITY_VALIDATED)
                .build();
        final NetworkRequest request2 = new NetworkRequest.Builder()
                .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL)
                .build();

        final 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 testNoAllowedUidsInNetworkRequests() throws Exception {
        final PendingIntent pendingIntent = PendingIntent.getBroadcast(
                mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE);
        final NetworkRequest r = new NetworkRequest.Builder().build();
        final ArraySet<Integer> allowedUids = new ArraySet<>();
        allowedUids.add(6);
        allowedUids.add(9);
        r.networkCapabilities.setAllowedUids(allowedUids);

        final Handler handler = new Handler(ConnectivityThread.getInstanceLooper());
        final NetworkCallback cb = new NetworkCallback();

        final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
        assertThrows(expected, () -> mCm.requestNetwork(r, cb));
        assertThrows(expected, () -> mCm.requestNetwork(r, pendingIntent));
        assertThrows(expected, () -> mCm.registerNetworkCallback(r, cb));
        assertThrows(expected, () -> mCm.registerNetworkCallback(r, cb, handler));
        assertThrows(expected, () -> mCm.registerNetworkCallback(r, pendingIntent));
        assertThrows(expected, () -> mCm.registerBestMatchingNetworkCallback(r, cb, handler));

        // Make sure that resetting the access UIDs to the empty set will allow calling
        // requestNetwork and registerNetworkCallback.
        r.networkCapabilities.setAllowedUids(Collections.emptySet());
        mCm.requestNetwork(r, cb);
        mCm.unregisterNetworkCallback(cb);
        mCm.registerNetworkCallback(r, cb);
        mCm.unregisterNetworkCallback(cb);
    }

    @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 void setIngressRateLimit(int rateLimitInBytesPerSec) {
        ConnectivitySettingsManager.setIngressRateLimitInBytesPerSecond(mServiceContext,
                rateLimitInBytesPerSec);
        mService.updateIngressRateLimit();
        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);
            // Wait for the network to be removed from internal structures before
            // calling synchronous getter
            waitForIdle();
            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);
            waitForIdle();
            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());
            testFactory.terminate();
            testFactory.assertNoRequestChanged();
        } finally {
            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 {
        doReturn(1).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
        testAvoidBadWifiConfig_ignoreSettings();

        doReturn(0).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
        testAvoidBadWifiConfig_controlledBySettings();
    }

    @Test
    public void testOffersAvoidsBadWifi() throws Exception {
        // Normal mode : the carrier doesn't restrict moving away from bad wifi.
        // This has getAvoidBadWifi return true.
        doReturn(1).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
        // Don't request cell separately for the purposes of this test.
        setAlwaysOnNetworks(false);

        final NetworkProvider cellProvider = new NetworkProvider(mServiceContext,
                mCsHandlerThread.getLooper(), "Cell provider");
        final NetworkProvider wifiProvider = new NetworkProvider(mServiceContext,
                mCsHandlerThread.getLooper(), "Wifi provider");

        mCm.registerNetworkProvider(cellProvider);
        mCm.registerNetworkProvider(wifiProvider);

        final NetworkScore cellScore = new NetworkScore.Builder().build();
        final NetworkScore wifiScore = new NetworkScore.Builder().build();
        final NetworkCapabilities defaultCaps = new NetworkCapabilities.Builder()
                .addCapability(NET_CAPABILITY_INTERNET)
                .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
                .build();
        final NetworkCapabilities cellCaps = new NetworkCapabilities.Builder()
                .addTransportType(TRANSPORT_CELLULAR)
                .addCapability(NET_CAPABILITY_INTERNET)
                .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
                .build();
        final NetworkCapabilities wifiCaps = new NetworkCapabilities.Builder()
                .addTransportType(TRANSPORT_WIFI)
                .addCapability(NET_CAPABILITY_INTERNET)
                .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
                .build();
        final TestableNetworkOfferCallback cellCallback = new TestableNetworkOfferCallback(
                TIMEOUT_MS /* timeout */, TEST_CALLBACK_TIMEOUT_MS /* noCallbackTimeout */);
        final TestableNetworkOfferCallback wifiCallback = new TestableNetworkOfferCallback(
                TIMEOUT_MS /* timeout */, TEST_CALLBACK_TIMEOUT_MS /* noCallbackTimeout */);

        // Offer callbacks will run on the CS handler thread in this test.
        cellProvider.registerNetworkOffer(cellScore, cellCaps, r -> r.run(), cellCallback);
        wifiProvider.registerNetworkOffer(wifiScore, wifiCaps, r -> r.run(), wifiCallback);

        // Both providers see the default request.
        cellCallback.expectOnNetworkNeeded(defaultCaps);
        wifiCallback.expectOnNetworkNeeded(defaultCaps);

        // Listen to cell and wifi to know when agents are finished processing
        final TestNetworkCallback cellNetworkCallback = new TestNetworkCallback();
        final NetworkRequest cellRequest = new NetworkRequest.Builder()
                .addTransportType(TRANSPORT_CELLULAR).build();
        mCm.registerNetworkCallback(cellRequest, cellNetworkCallback);
        final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback();
        final NetworkRequest wifiRequest = new NetworkRequest.Builder()
                .addTransportType(TRANSPORT_WIFI).build();
        mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback);

        // Cell connects and validates.
        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR,
                new LinkProperties(), null /* ncTemplate */, cellProvider);
        mCellNetworkAgent.connect(true);
        cellNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
        cellCallback.assertNoCallback();
        wifiCallback.assertNoCallback();

        // Bring up wifi. At first it's invalidated, so cell is still needed.
        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI,
                new LinkProperties(), null /* ncTemplate */, wifiProvider);
        mWiFiNetworkAgent.connect(false);
        wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
        cellCallback.assertNoCallback();
        wifiCallback.assertNoCallback();

        // Wifi validates. Cell is no longer needed, because it's outscored.
        mWiFiNetworkAgent.setNetworkValid(true /* isStrictMode */);
        // Have CS reconsider the network (see testPartialConnectivity)
        mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
        wifiNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
        cellCallback.expectOnNetworkUnneeded(defaultCaps);
        wifiCallback.assertNoCallback();

        // Wifi is no longer validated. Cell is needed again.
        mWiFiNetworkAgent.setNetworkInvalid(true /* isStrictMode */);
        mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
        wifiNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
        cellCallback.expectOnNetworkNeeded(defaultCaps);
        wifiCallback.assertNoCallback();

        // Disconnect wifi and pretend the carrier restricts moving away from bad wifi.
        mWiFiNetworkAgent.disconnect();
        wifiNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
        // This has getAvoidBadWifi return false. This test doesn't change the value of the
        // associated setting.
        doReturn(0).when(mResources).getInteger(R.integer.config_networkAvoidBadWifi);
        mPolicyTracker.reevaluate();
        waitForIdle();

        // Connect wifi again, cell is needed until wifi validates.
        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI,
                new LinkProperties(), null /* ncTemplate */, wifiProvider);
        mWiFiNetworkAgent.connect(false);
        wifiNetworkCallback.expectAvailableCallbacksUnvalidated(mWiFiNetworkAgent);
        cellCallback.assertNoCallback();
        wifiCallback.assertNoCallback();
        mWiFiNetworkAgent.setNetworkValid(true /* isStrictMode */);
        mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true);
        wifiNetworkCallback.expectCapabilitiesWith(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
        cellCallback.expectOnNetworkUnneeded(defaultCaps);
        wifiCallback.assertNoCallback();

        // Wifi loses validation. Because the device doesn't avoid bad wifis, cell is
        // not needed.
        mWiFiNetworkAgent.setNetworkInvalid(true /* isStrictMode */);
        mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
        wifiNetworkCallback.expectCapabilitiesWithout(NET_CAPABILITY_VALIDATED, mWiFiNetworkAgent);
        cellCallback.assertNoCallback();
        wifiCallback.assertNoCallback();
    }

    @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 : asList(0, 3, 2)) {
            for (String setting: 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();
        }
    }

    /**
     * Validate the service throws if request with CBS but without carrier privilege.
     */
    @Test
    public void testCBSRequestWithoutCarrierPrivilege() throws Exception {
        final NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                TRANSPORT_CELLULAR).addCapability(NET_CAPABILITY_CBS).build();
        final TestNetworkCallback networkCallback = new TestNetworkCallback();

        mServiceContext.setPermission(CONNECTIVITY_USE_RESTRICTED_NETWORKS, PERMISSION_DENIED);
        // Now file the test request and expect the service throws.
        assertThrows(SecurityException.class, () -> mCm.requestNetwork(nr, networkCallback));
    }

    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(), asList(myIpv4Address),
                asList(myIpv4DefaultRoute));
        checkDirectlyConnectedRoutes(mCm.getLinkProperties(networkAgent.getNetwork()),
                asList(myIpv4Address), 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(),
                asList(myIpv4Address, myIpv6Address1, myIpv6Address2),
                asList(myIpv4DefaultRoute));
        mCm.unregisterNetworkCallback(networkCallback);
    }

    private void expectNotifyNetworkStatus(List<Network> defaultNetworks, String defaultIface,
            Integer vpnUid, String vpnIfname, List<String> underlyingIfaces) throws Exception {
        ArgumentCaptor<List<Network>> defaultNetworksCaptor = ArgumentCaptor.forClass(List.class);
        ArgumentCaptor<List<UnderlyingNetworkInfo>> vpnInfosCaptor =
                ArgumentCaptor.forClass(List.class);

        verify(mStatsManager, atLeastOnce()).notifyNetworkStatus(defaultNetworksCaptor.capture(),
                any(List.class), eq(defaultIface), vpnInfosCaptor.capture());

        assertSameElements(defaultNetworks, defaultNetworksCaptor.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> defaultNetworks, String defaultIface) throws Exception {
        expectNotifyNetworkStatus(defaultNetworks, 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 testAdminUidsRedacted() throws Exception {
        final int[] adminUids = new int[] {Process.myUid() + 1};
        final NetworkCapabilities ncTemplate = new NetworkCapabilities();
        ncTemplate.setAdministratorUids(adminUids);
        mCellNetworkAgent =
                new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, new LinkProperties(), ncTemplate);
        mCellNetworkAgent.connect(false /* validated */);

        // Verify case where caller has permission
        mServiceContext.setPermission(
                NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, PERMISSION_GRANTED);
        TestNetworkCallback callback = new TestNetworkCallback();
        mCm.registerDefaultNetworkCallback(callback);
        callback.expectCallback(CallbackEntry.AVAILABLE, mCellNetworkAgent);
        callback.expectCapabilitiesThat(
                mCellNetworkAgent, nc -> Arrays.equals(adminUids, nc.getAdministratorUids()));
        mCm.unregisterNetworkCallback(callback);

        // Verify case where caller does NOT have permission
        mServiceContext.setPermission(
                NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, PERMISSION_DENIED);
        mServiceContext.setPermission(NETWORK_STACK, PERMISSION_DENIED);
        callback = new TestNetworkCallback();
        mCm.registerDefaultNetworkCallback(callback);
        callback.expectCallback(CallbackEntry.AVAILABLE, mCellNetworkAgent);
        callback.expectCapabilitiesThat(
                mCellNetworkAgent, nc -> nc.getAdministratorUids().length == 0);
    }

    @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(CollectionUtils.contains(resolvrParams.servers, "2001:db8::1"));
        // Opportunistic mode.
        assertTrue(CollectionUtils.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(new ArraySet<>(resolvrParams.servers).containsAll(
                asList("2001:db8::1", "192.0.2.1")));
        // Opportunistic mode.
        assertEquals(2, resolvrParams.tlsServers.length);
        assertTrue(new ArraySet<>(resolvrParams.tlsServers).containsAll(
                asList("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(new ArraySet<>(resolvrParams.servers).containsAll(
                asList("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(new ArraySet<>(resolvrParams.tlsServers).containsAll(
                asList("2001:db8::1", "192.0.2.1")));
        // Opportunistic mode.
        assertEquals(2, resolvrParams.tlsServers.length);
        assertTrue(new ArraySet<>(resolvrParams.tlsServers).containsAll(
                asList("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(new ArraySet<>(resolvrParams.servers).containsAll(
                asList("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(new ArraySet<>(resolvrParams.servers).containsAll(
                asList("2001:db8::1", "192.0.2.1")));
        assertEquals(2, resolvrParams.tlsServers.length);
        assertTrue(new ArraySet<>(resolvrParams.tlsServers).containsAll(
                asList("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 ArrayList<Network> emptyUnderlyingNetworks = new ArrayList<Network>();
        final ArrayList<Network> underlyingNetworksContainMobile = new ArrayList<Network>();
        underlyingNetworksContainMobile.add(mobile);
        final ArrayList<Network> underlyingNetworksContainWifi = new ArrayList<Network>();
        underlyingNetworksContainWifi.add(wifi);
        final ArrayList<Network> underlyingNetworksContainMobileAndMobile =
                new ArrayList<Network>();
        underlyingNetworksContainMobileAndMobile.add(mobile);
        underlyingNetworksContainMobileAndMobile.add(wifi);

        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);
        withNoUnderlying.setUnderlyingNetworks(emptyUnderlyingNetworks);

        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);
        withMobileUnderlying.setUnderlyingNetworks(underlyingNetworksContainMobile);

        final NetworkCapabilities withWifiUnderlying = new NetworkCapabilities(withNoUnderlying);
        withWifiUnderlying.addTransportType(TRANSPORT_WIFI);
        withWifiUnderlying.addCapability(NET_CAPABILITY_NOT_METERED);
        withWifiUnderlying.setLinkUpstreamBandwidthKbps(20);
        withWifiUnderlying.setUnderlyingNetworks(underlyingNetworksContainWifi);

        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);
        withWifiAndMobileUnderlying.setUnderlyingNetworks(underlyingNetworksContainMobileAndMobile);

        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);
        assertEquals(0, new ArrayList<>(caps.getUnderlyingNetworks()).size());

        caps = new NetworkCapabilities(initialCaps);
        mService.applyUnderlyingCapabilities(new Network[]{null}, initialCapsNotMetered, caps);
        assertEquals(withNoUnderlying, caps);
        assertEquals(0, new ArrayList<>(caps.getUnderlyingNetworks()).size());

        caps = new NetworkCapabilities(initialCaps);
        mService.applyUnderlyingCapabilities(new Network[]{mobile}, initialCapsNotMetered, caps);
        assertEquals(withMobileUnderlying, caps);
        assertEquals(1, new ArrayList<>(caps.getUnderlyingNetworks()).size());
        assertEquals(mobile, new ArrayList<>(caps.getUnderlyingNetworks()).get(0));

        caps = new NetworkCapabilities(initialCaps);
        mService.applyUnderlyingCapabilities(new Network[]{wifi}, initialCapsNotMetered, caps);
        assertEquals(withWifiUnderlying, caps);
        assertEquals(1, new ArrayList<>(caps.getUnderlyingNetworks()).size());
        assertEquals(wifi, new ArrayList<>(caps.getUnderlyingNetworks()).get(0));

        withWifiUnderlying.removeCapability(NET_CAPABILITY_NOT_METERED);
        caps = new NetworkCapabilities(initialCaps);
        mService.applyUnderlyingCapabilities(new Network[]{wifi}, initialCaps, caps);
        assertEquals(withWifiUnderlying, caps);
        assertEquals(1, new ArrayList<>(caps.getUnderlyingNetworks()).size());
        assertEquals(wifi, new ArrayList<>(caps.getUnderlyingNetworks()).get(0));

        caps = new NetworkCapabilities(initialCaps);
        mService.applyUnderlyingCapabilities(new Network[]{mobile, wifi}, initialCaps, caps);
        assertEquals(withWifiAndMobileUnderlying, caps);
        assertEquals(2, new ArrayList<>(caps.getUnderlyingNetworks()).size());
        assertEquals(mobile, new ArrayList<>(caps.getUnderlyingNetworks()).get(0));
        assertEquals(wifi, new ArrayList<>(caps.getUnderlyingNetworks()).get(1));

        withWifiUnderlying.addCapability(NET_CAPABILITY_NOT_METERED);
        caps = new NetworkCapabilities(initialCaps);
        mService.applyUnderlyingCapabilities(new Network[]{null, mobile, null, wifi},
                initialCapsNotMetered, caps);
        assertEquals(withWifiAndMobileUnderlying, caps);
        assertEquals(2, new ArrayList<>(caps.getUnderlyingNetworks()).size());
        assertEquals(mobile, new ArrayList<>(caps.getUnderlyingNetworks()).get(0));
        assertEquals(wifi, new ArrayList<>(caps.getUnderlyingNetworks()).get(1));

        caps = new NetworkCapabilities(initialCaps);
        mService.applyUnderlyingCapabilities(new Network[]{null, mobile, null, wifi},
                initialCapsNotMetered, caps);
        assertEquals(withWifiAndMobileUnderlying, caps);
        assertEquals(2, new ArrayList<>(caps.getUnderlyingNetworks()).size());
        assertEquals(mobile, new ArrayList<>(caps.getUnderlyingNetworks()).get(0));
        assertEquals(wifi, new ArrayList<>(caps.getUnderlyingNetworks()).get(1));

        caps = new NetworkCapabilities(initialCaps);
        mService.applyUnderlyingCapabilities(null, initialCapsNotMetered, caps);
        assertEquals(withWifiUnderlying, caps);
        assertEquals(1, new ArrayList<>(caps.getUnderlyingNetworks()).size());
        assertEquals(wifi, new ArrayList<>(caps.getUnderlyingNetworks()).get(0));
    }

    @Test
    public void testVpnConnectDisconnectUnderlyingNetwork() throws Exception {
        final TestNetworkCallback callback = new TestNetworkCallback();
        final NetworkRequest request = new NetworkRequest.Builder()
                .removeCapability(NET_CAPABILITY_NOT_VPN).build();

        runAsShell(NETWORK_SETTINGS, () -> {
            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);
            callback.expectAvailableCallbacksUnvalidated(mMockVpn);
            assertUidRangesUpdatedForMyUid(true);
            final Network wifiNetwork = new Network(mNetIdManager.peekNextNetId());
            mMockVpn.setUnderlyingNetworks(new Network[]{wifiNetwork});
            // onCapabilitiesChanged() should be called because
            // NetworkCapabilities#mUnderlyingNetworks is updated.
            CallbackEntry ce = callback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED,
                    mMockVpn);
            final NetworkCapabilities vpnNc1 = ((CallbackEntry.CapabilitiesChanged) ce).getCaps();
            // Since the wifi network hasn't brought up,
            // ConnectivityService#applyUnderlyingCapabilities cannot find it. Update
            // NetworkCapabilities#mUnderlyingNetworks to an empty array, and it will be updated to
            // the correct underlying networks once the wifi network brings up. But this case
            // shouldn't happen in reality since no one could get the network which hasn't brought
            // up. For the empty array of underlying networks, it should be happened for 2 cases,
            // the first one is that the VPN app declares an empty array for its underlying
            // networks, the second one is that the underlying networks are torn down.
            //
            // It shouldn't be null since the null value means the underlying networks of this
            // network should follow the default network.
            final ArrayList<Network> underlyingNetwork = new ArrayList<>();
            assertEquals(underlyingNetwork, vpnNc1.getUnderlyingNetworks());
            // Since the wifi network isn't exist, applyUnderlyingCapabilities()
            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.
            ce = callback.expectCallback(CallbackEntry.NETWORK_CAPS_UPDATED, mMockVpn);
            final NetworkCapabilities vpnNc2 = ((CallbackEntry.CapabilitiesChanged) ce).getCaps();
            // The wifi network is brought up, NetworkCapabilities#mUnderlyingNetworks is updated to
            // it.
            underlyingNetwork.add(wifiNetwork);
            assertEquals(underlyingNetwork, vpnNc2.getUnderlyingNetworks());
            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(
                NetworkAgentConfigShimImpl.newInstance(mMockVpn.getNetworkAgentConfig()),
                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(
                NetworkAgentConfigShimImpl.newInstance(mMockVpn.getNetworkAgentConfig()),
                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));

        doReturn(UserHandle.getUid(RESTRICTED_USER, VPN_UID)).when(mPackageManager)
                .getPackageUidAsUser(ALWAYS_ON_PACKAGE, RESTRICTED_USER);

        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.
        doReturn(UserHandle.getUid(RESTRICTED_USER, VPN_UID)).when(mPackageManager)
                .getPackageUidAsUser(ALWAYS_ON_PACKAGE, RESTRICTED_USER);
        doReturn(asList(PRIMARY_USER_INFO, RESTRICTED_USER_INFO)).when(mUserManager)
                .getAliveUsers();
        // 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.
        doReturn(asList(PRIMARY_USER_INFO)).when(mUserManager).getAliveUsers();

        // 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 = 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();

        final Set<Integer> excludedUids = new ArraySet<Integer>();
        excludedUids.add(VPN_UID);
        if (SdkLevel.isAtLeastT()) {
            // On T onwards, the corresponding SDK sandbox UID should also be excluded
            excludedUids.add(toSdkSandboxUid(VPN_UID));
        }
        final UidRangeParcel[] uidRangeParcels = uidRangeParcelsExcludingUids(
                excludedUids.toArray(new Integer[0]));
        InOrder inOrder = inOrder(mMockNetd);
        expectNetworkRejectNonSecureVpn(inOrder, true, uidRangeParcels);

        // 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, uidRangeParcels);
        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();

        excludedUids.add(uid);
        if (SdkLevel.isAtLeastT()) {
            // On T onwards, the corresponding SDK sandbox UID should also be excluded
            excludedUids.add(toSdkSandboxUid(uid));
        }
        final UidRangeParcel[] uidRangeParcelsAlsoExcludingUs = uidRangeParcelsExcludingUids(
                excludedUids.toArray(new Integer[0]));
        expectNetworkRejectNonSecureVpn(inOrder, true, uidRangeParcelsAlsoExcludingUs);
        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, uidRangeParcelsAlsoExcludingUs);
        allowList.clear();
        mVpnManagerService.setAlwaysOnVpnPackage(userId, ALWAYS_ON_PACKAGE, true /* lockdown */,
                allowList);
        waitForIdle();
        expectNetworkRejectNonSecureVpn(inOrder, true, uidRangeParcels);
        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);
    }

    @Test
    public void testVpnExcludesOwnUid() throws Exception {
        // required for registerDefaultNetworkCallbackForUid.
        mServiceContext.setPermission(NETWORK_SETTINGS, PERMISSION_GRANTED);

        // Connect Wi-Fi.
        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
        mWiFiNetworkAgent.connect(true /* validated */);

        // Connect a VPN that excludes its UID from its UID ranges.
        final LinkProperties lp = new LinkProperties();
        lp.setInterfaceName(VPN_IFNAME);
        final int myUid = Process.myUid();
        final Set<UidRange> ranges = new ArraySet<>();
        ranges.add(new UidRange(0, myUid - 1));
        ranges.add(new UidRange(myUid + 1, UserHandle.PER_USER_RANGE - 1));
        mMockVpn.setUnderlyingNetworks(new Network[]{mWiFiNetworkAgent.getNetwork()});
        mMockVpn.establish(lp, myUid, ranges);

        // Wait for validation before registering callbacks.
        waitForIdle();

        final int otherUid = myUid + 1;
        final Handler h = new Handler(ConnectivityThread.getInstanceLooper());
        final TestNetworkCallback otherUidCb = new TestNetworkCallback();
        final TestNetworkCallback defaultCb = new TestNetworkCallback();
        final TestNetworkCallback perUidCb = new TestNetworkCallback();
        registerDefaultNetworkCallbackAsUid(otherUidCb, otherUid);
        mCm.registerDefaultNetworkCallback(defaultCb, h);
        doAsUid(Process.SYSTEM_UID,
                () -> mCm.registerDefaultNetworkCallbackForUid(myUid, perUidCb, h));

        otherUidCb.expectAvailableCallbacksValidated(mMockVpn);
        // BUG (b/195265065): the default network for the VPN app is actually Wi-Fi, not the VPN.
        defaultCb.expectAvailableCallbacksValidated(mMockVpn);
        perUidCb.expectAvailableCallbacksValidated(mMockVpn);
        // getActiveNetwork is not affected by this bug.
        assertEquals(mMockVpn.getNetwork(), mCm.getActiveNetworkForUid(myUid + 1));
        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork());
        assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(myUid));

        doAsUid(otherUid, () -> mCm.unregisterNetworkCallback(otherUidCb));
        mCm.unregisterNetworkCallback(defaultCb);
        doAsUid(Process.SYSTEM_UID, () -> mCm.unregisterNetworkCallback(perUidCb));
    }

    private void setupLegacyLockdownVpn() {
        final String profileName = "testVpnProfile";
        final byte[] profileTag = profileName.getBytes(StandardCharsets.UTF_8);
        doReturn(profileTag).when(mVpnProfileStore).get(Credentials.LOCKDOWN_VPN);

        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();
        doReturn(encodedProfile).when(mVpnProfileStore).get(Credentials.VPN + profileName);
    }

    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 @IgnoreUpTo(Build.VERSION_CODES.S_V2)
    public void testLockdownSetFirewallUidRule() throws Exception {
        final Set<Range<Integer>> lockdownRange = UidRange.toIntRanges(Set.of(PRIMARY_UIDRANGE));
        // Enable Lockdown
        mCm.setRequireVpnForUids(true /* requireVpn */, lockdownRange);
        waitForIdle();

        // Lockdown rule is set to apps uids
        verify(mBpfNetMaps, times(3)).updateUidLockdownRule(anyInt(), eq(true) /* add */);
        verify(mBpfNetMaps).updateUidLockdownRule(APP1_UID, true /* add */);
        verify(mBpfNetMaps).updateUidLockdownRule(APP2_UID, true /* add */);
        verify(mBpfNetMaps).updateUidLockdownRule(VPN_UID, true /* add */);

        reset(mBpfNetMaps);

        // Disable lockdown
        mCm.setRequireVpnForUids(false /* requireVPN */, lockdownRange);
        waitForIdle();

        // Lockdown rule is removed from apps uids
        verify(mBpfNetMaps, times(3)).updateUidLockdownRule(anyInt(), eq(false) /* add */);
        verify(mBpfNetMaps).updateUidLockdownRule(APP1_UID, false /* add */);
        verify(mBpfNetMaps).updateUidLockdownRule(APP2_UID, false /* add */);
        verify(mBpfNetMaps).updateUidLockdownRule(VPN_UID, false /* add */);

        // Interface rules are not changed by Lockdown mode enable/disable
        verify(mBpfNetMaps, never()).addUidInterfaceRules(any(), any());
        verify(mBpfNetMaps, never()).removeUidInterfaceRules(any());
    }

    private void doTestSetUidFirewallRule(final int chain, final int defaultRule) {
        final int uid = 1001;
        mCm.setUidFirewallRule(chain, uid, FIREWALL_RULE_ALLOW);
        verify(mBpfNetMaps).setUidRule(chain, uid, FIREWALL_RULE_ALLOW);
        reset(mBpfNetMaps);

        mCm.setUidFirewallRule(chain, uid, FIREWALL_RULE_DENY);
        verify(mBpfNetMaps).setUidRule(chain, uid, FIREWALL_RULE_DENY);
        reset(mBpfNetMaps);

        mCm.setUidFirewallRule(chain, uid, FIREWALL_RULE_DEFAULT);
        verify(mBpfNetMaps).setUidRule(chain, uid, defaultRule);
        reset(mBpfNetMaps);
    }

    @Test @IgnoreUpTo(SC_V2)
    public void testSetUidFirewallRule() throws Exception {
        doTestSetUidFirewallRule(FIREWALL_CHAIN_DOZABLE, FIREWALL_RULE_DENY);
        doTestSetUidFirewallRule(FIREWALL_CHAIN_STANDBY, FIREWALL_RULE_ALLOW);
        doTestSetUidFirewallRule(FIREWALL_CHAIN_POWERSAVE, FIREWALL_RULE_DENY);
        doTestSetUidFirewallRule(FIREWALL_CHAIN_RESTRICTED, FIREWALL_RULE_DENY);
        doTestSetUidFirewallRule(FIREWALL_CHAIN_LOW_POWER_STANDBY, FIREWALL_RULE_DENY);
        doTestSetUidFirewallRule(FIREWALL_CHAIN_OEM_DENY_1, FIREWALL_RULE_ALLOW);
        doTestSetUidFirewallRule(FIREWALL_CHAIN_OEM_DENY_2, FIREWALL_RULE_ALLOW);
        doTestSetUidFirewallRule(FIREWALL_CHAIN_OEM_DENY_3, FIREWALL_RULE_ALLOW);
    }

    @Test @IgnoreUpTo(SC_V2)
    public void testSetFirewallChainEnabled() throws Exception {
        final List<Integer> firewallChains = Arrays.asList(
                FIREWALL_CHAIN_DOZABLE,
                FIREWALL_CHAIN_STANDBY,
                FIREWALL_CHAIN_POWERSAVE,
                FIREWALL_CHAIN_RESTRICTED,
                FIREWALL_CHAIN_LOW_POWER_STANDBY,
                FIREWALL_CHAIN_OEM_DENY_1,
                FIREWALL_CHAIN_OEM_DENY_2,
                FIREWALL_CHAIN_OEM_DENY_3);
        for (final int chain: firewallChains) {
            mCm.setFirewallChainEnabled(chain, true /* enabled */);
            verify(mBpfNetMaps).setChildChain(chain, true /* enable */);
            reset(mBpfNetMaps);

            mCm.setFirewallChainEnabled(chain, false /* enabled */);
            verify(mBpfNetMaps).setChildChain(chain, false /* enable */);
            reset(mBpfNetMaps);
        }
    }

    private void doTestReplaceFirewallChain(final int chain, final String chainName,
            final boolean allowList) {
        final int[] uids = new int[] {1001, 1002};
        mCm.replaceFirewallChain(chain, uids);
        verify(mBpfNetMaps).replaceUidChain(chainName, allowList, uids);
        reset(mBpfNetMaps);
    }

    @Test @IgnoreUpTo(SC_V2)
    public void testReplaceFirewallChain() {
        doTestReplaceFirewallChain(FIREWALL_CHAIN_DOZABLE, "fw_dozable", true);
        doTestReplaceFirewallChain(FIREWALL_CHAIN_STANDBY, "fw_standby", false);
        doTestReplaceFirewallChain(FIREWALL_CHAIN_POWERSAVE, "fw_powersave",  true);
        doTestReplaceFirewallChain(FIREWALL_CHAIN_RESTRICTED, "fw_restricted", true);
        doTestReplaceFirewallChain(FIREWALL_CHAIN_LOW_POWER_STANDBY, "fw_low_power_standby", true);
        doTestReplaceFirewallChain(FIREWALL_CHAIN_OEM_DENY_1, "fw_oem_deny_1", false);
        doTestReplaceFirewallChain(FIREWALL_CHAIN_OEM_DENY_2, "fw_oem_deny_2", false);
        doTestReplaceFirewallChain(FIREWALL_CHAIN_OEM_DENY_3, "fw_oem_deny_3", false);
    }

    @Test @IgnoreUpTo(SC_V2)
    public void testInvalidFirewallChain() throws Exception {
        final int uid = 1001;
        final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
        assertThrows(expected,
                () -> mCm.setUidFirewallRule(-1 /* chain */, uid, FIREWALL_RULE_ALLOW));
        assertThrows(expected,
                () -> mCm.setUidFirewallRule(100 /* chain */, uid, FIREWALL_RULE_ALLOW));
        assertThrows(expected, () -> mCm.replaceFirewallChain(-1 /* chain */, new int[]{uid}));
        assertThrows(expected, () -> mCm.replaceFirewallChain(100 /* chain */, new int[]{uid}));
    }

    @Test @IgnoreUpTo(SC_V2)
    public void testInvalidFirewallRule() throws Exception {
        final Class<IllegalArgumentException> expected = IllegalArgumentException.class;
        assertThrows(expected,
                () -> mCm.setUidFirewallRule(FIREWALL_CHAIN_DOZABLE,
                        1001 /* uid */, -1 /* rule */));
        assertThrows(expected,
                () -> mCm.setUidFirewallRule(FIREWALL_CHAIN_DOZABLE,
                        1001 /* uid */, 100 /* rule */));
    }

    /**
     * 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();
        final ArrayTrackRecord<ReportedInterfaces>.ReadHead readHead =
                mDeps.mReportedInterfaceHistory.newReadHead();
        assertNotNull(readHead.poll(TIMEOUT_MS, ri -> ri.contentEquals(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();
        assertNotNull(readHead.poll(TIMEOUT_MS, ri -> ri.contentEquals(mServiceContext,
                wifiLp.getInterfaceName(),
                new int[] { TRANSPORT_WIFI })));

        mCellNetworkAgent.disconnect();
        mWiFiNetworkAgent.disconnect();

        cellLp.setInterfaceName("wifi0");
        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
        mCellNetworkAgent.connect(true);
        waitForIdle();
        assertNotNull(readHead.poll(TIMEOUT_MS, ri -> ri.contentEquals(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;
    }

    private <T> T verifyWithOrder(@Nullable InOrder inOrder, @NonNull T t) {
        if (inOrder != null) {
            return inOrder.verify(t);
        } else {
            return verify(t);
        }
    }

    private <T> T verifyNeverWithOrder(@Nullable InOrder inOrder, @NonNull T t) {
        if (inOrder != null) {
            return inOrder.verify(t, never());
        } else {
            return verify(t, never());
        }
    }

    private void verifyClatdStart(@Nullable InOrder inOrder, @NonNull String iface, int netId,
            @NonNull String nat64Prefix) throws Exception {
        if (SdkLevel.isAtLeastT()) {
            verifyWithOrder(inOrder, mClatCoordinator)
                .clatStart(eq(iface), eq(netId), eq(new IpPrefix(nat64Prefix)));
        } else {
            verifyWithOrder(inOrder, mMockNetd).clatdStart(eq(iface), eq(nat64Prefix));
        }
    }

    private void verifyNeverClatdStart(@Nullable InOrder inOrder, @NonNull String iface)
            throws Exception {
        if (SdkLevel.isAtLeastT()) {
            verifyNeverWithOrder(inOrder, mClatCoordinator).clatStart(eq(iface), anyInt(), any());
        } else {
            verifyNeverWithOrder(inOrder, mMockNetd).clatdStart(eq(iface), anyString());
        }
    }

    private void verifyClatdStop(@Nullable InOrder inOrder, @NonNull String iface)
            throws Exception {
        if (SdkLevel.isAtLeastT()) {
            verifyWithOrder(inOrder, mClatCoordinator).clatStop();
        } else {
            verifyWithOrder(inOrder, mMockNetd).clatdStop(eq(iface));
        }
    }

    private void verifyNeverClatdStop(@Nullable InOrder inOrder, @NonNull String iface)
            throws Exception {
        if (SdkLevel.isAtLeastT()) {
            verifyNeverWithOrder(inOrder, mClatCoordinator).clatStop();
        } else {
            verifyNeverWithOrder(inOrder, mMockNetd).clatdStop(eq(iface));
        }
    }

    @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);
        reset(mClatCoordinator);

        // 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);
        final ArrayTrackRecord<ReportedInterfaces>.ReadHead readHead =
                mDeps.mReportedInterfaceHistory.newReadHead();
        assertNotNull(readHead.poll(TIMEOUT_MS, ri -> ri.contentEquals(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();
        assertNull(readHead.poll(0 /* timeout */, ri -> mServiceContext.equals(ri.context)
                && ri.iface != null && ri.iface.startsWith("v4-")));

        verifyNoMoreInteractions(mMockNetd);
        verifyNoMoreInteractions(mClatCoordinator);
        verifyNoMoreInteractions(mMockDnsResolver);
        reset(mMockNetd);
        reset(mClatCoordinator);
        reset(mMockDnsResolver);
        doReturn(getClatInterfaceConfigParcel(myIpv4)).when(mMockNetd)
                .interfaceGetCfg(CLAT_MOBILE_IFNAME);

        // 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());
        verifyClatdStart(null /* inOrder */, MOBILE_IFNAME, cellNetId, 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(CollectionUtils.contains(resolvrParams.servers, "8.8.8.8"));

        for (final LinkProperties stackedLp : stackedLpsAfterChange) {
            assertNotNull(readHead.poll(TIMEOUT_MS, ri -> ri.contentEquals(mServiceContext,
                    stackedLp.getInterfaceName(),
                    new int[] { TRANSPORT_CELLULAR })));
        }
        reset(mMockNetd);
        reset(mClatCoordinator);
        doReturn(getClatInterfaceConfigParcel(myIpv4)).when(mMockNetd)
                .interfaceGetCfg(CLAT_MOBILE_IFNAME);
        // 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);
        verifyClatdStop(null /* inOrder */, MOBILE_IFNAME);
        assertRoutesRemoved(cellNetId, stackedDefault);
        verify(mMockNetd, times(1)).networkRemoveInterface(cellNetId, CLAT_MOBILE_IFNAME);

        verifyClatdStart(null /* inOrder */, MOBILE_IFNAME, cellNetId,
                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);
        reset(mClatCoordinator);

        // 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);
        verifyClatdStop(null /* inOrder */, 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(mClatCoordinator);
        verifyNoMoreInteractions(mMockDnsResolver);
        reset(mMockNetd);
        reset(mClatCoordinator);
        reset(mMockDnsResolver);
        doReturn(getClatInterfaceConfigParcel(myIpv4)).when(mMockNetd)
                .interfaceGetCfg(CLAT_MOBILE_IFNAME);

        // 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);
        verifyClatdStart(null /* inOrder */, MOBILE_IFNAME, cellNetId, 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.
        verifyClatdStop(null /* inOrder */, 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);
        verifyNoMoreInteractions(mClatCoordinator);
        reset(mMockNetd);
        reset(mClatCoordinator);

        // Test disconnecting a network that is running 464xlat.

        // Connect a network with a NAT64 prefix.
        doReturn(getClatInterfaceConfigParcel(myIpv4)).when(mMockNetd)
                .interfaceGetCfg(CLAT_MOBILE_IFNAME);
        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.
        verifyClatdStart(null /* inOrder */, MOBILE_IFNAME, cellNetId, 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);
        reset(mClatCoordinator);

        // Disconnect the network. clat is stopped and the network is destroyed.
        mCellNetworkAgent.disconnect();
        networkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
        networkCallback.assertNoCallback();
        verifyClatdStop(null /* inOrder */, MOBILE_IFNAME);
        verify(mMockNetd).idletimerRemoveInterface(eq(MOBILE_IFNAME), anyInt(),
                eq(Integer.toString(TRANSPORT_CELLULAR)));
        verify(mMockNetd).networkDestroy(cellNetId);
        verifyNoMoreInteractions(mMockNetd);
        verifyNoMoreInteractions(mClatCoordinator);

        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, mClatCoordinator);

        // 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);
        verifyClatdStart(inOrder, iface, netId, 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);
        verifyClatdStop(inOrder, 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);
        verifyClatdStart(inOrder, iface, netId, 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);
        verifyClatdStop(inOrder, 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);
        verifyClatdStart(inOrder, iface, netId, 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();
        verifyNeverClatdStop(inOrder, iface);
        verifyNeverClatdStart(inOrder, iface);
        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();
        verifyNeverClatdStop(inOrder, iface);
        verifyNeverClatdStart(inOrder, iface);
        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);
        verifyClatdStop(inOrder, 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));

        verifyClatdStart(inOrder, iface, netId, 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);
        verifyClatdStop(inOrder, iface);
        inOrder.verify(mMockDnsResolver).setPrefix64(netId, "");
        verifyClatdStart(inOrder, iface, netId, 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());
        verifyNeverClatdStop(inOrder, iface);
        verifyNeverClatdStart(inOrder, iface);
        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);
        verifyClatdStop(inOrder, 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);
        verifyClatdStart(inOrder, iface, netId, pref64FromDns.toString());
        inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), any());

        lp.setNat64Prefix(pref64FromDns);
        mWiFiNetworkAgent.sendLinkProperties(lp);
        callback.assertNoCallback();
        verifyNeverClatdStop(inOrder, iface);
        verifyNeverClatdStart(inOrder, iface);
        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();

        verifyClatdStop(inOrder, iface);
        inOrder.verify(mMockDnsResolver).stopPrefix64Discovery(netId);
        inOrder.verify(mMockDnsResolver, never()).setPrefix64(eq(netId), anyString());

        mCm.unregisterNetworkCallback(callback);
    }

    @Test
    public void testWith464XlatDisable() throws Exception {
        mDeps.setCellular464XlatEnabled(false);

        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();
        mProxyTracker.setGlobalProxy(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(mBpfNetMaps, times(2)).addUidInterfaceRules(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.getVpnInterfaceUidRanges("tun0").equals(vpnRange));

        mMockVpn.disconnect();
        waitForIdle();

        // Disconnected VPN should have interface rules removed
        verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
        assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
        assertNull(mService.mPermissionMonitor.getVpnInterfaceUidRanges("tun0"));
    }

    private void checkInterfaceFilteringRuleWithNullInterface(final LinkProperties lp,
            final int uid) throws Exception {
        // 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, uid, vpnRange);
        assertVpnUidRangesUpdated(true, vpnRange, uid);

        if (SdkLevel.isAtLeastT()) {
            // On T and above, VPN should have rules for null interface. Null Interface is a
            // wildcard and this accepts traffic from all the interfaces.
            // 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(mBpfNetMaps, times(2)).addUidInterfaceRules(
                    eq(null) /* iface */, uidCaptor.capture());
            if (uid == VPN_UID) {
                assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID);
                assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID);
            } else {
                assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID, VPN_UID);
                assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID, VPN_UID);
            }
            assertEquals(mService.mPermissionMonitor.getVpnInterfaceUidRanges(null /* iface */),
                    vpnRange);

            mMockVpn.disconnect();
            waitForIdle();

            // Disconnected VPN should have interface rules removed
            verify(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
            if (uid == VPN_UID) {
                assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
            } else {
                assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID, VPN_UID);
            }
            assertNull(mService.mPermissionMonitor.getVpnInterfaceUidRanges(null /* iface */));
        } else {
            // Before T, rules are not configured for null interface.
            verify(mBpfNetMaps, never()).addUidInterfaceRules(any(), any());
        }
    }

    @Test
    public void testLegacyVpnInterfaceFilteringRule() 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));
        // Legacy VPN should have interface filtering with null interface.
        checkInterfaceFilteringRuleWithNullInterface(lp, Process.SYSTEM_UID);
    }

    @Test
    public void testLocalIpv4OnlyVpnInterfaceFilteringRule() 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));
        // VPN that does not provide a default route should have interface filtering with null
        // interface.
        checkInterfaceFilteringRuleWithNullInterface(lp, VPN_UID);
    }

    @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(mBpfNetMaps, times(2)).addUidInterfaceRules(eq("tun0"), uidCaptor.capture());
        assertContainsExactly(uidCaptor.getAllValues().get(0), APP1_UID, APP2_UID);
        assertContainsExactly(uidCaptor.getAllValues().get(1), APP1_UID, APP2_UID);

        reset(mBpfNetMaps);
        InOrder inOrder = inOrder(mBpfNetMaps);
        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(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
        assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
        inOrder.verify(mBpfNetMaps).addUidInterfaceRules(eq("tun1"), uidCaptor.capture());
        assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);

        reset(mBpfNetMaps);
        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(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
        assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);

        reset(mBpfNetMaps);
        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(mBpfNetMaps).addUidInterfaceRules(eq("tun1"), uidCaptor.capture());
        assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
    }

    @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(mBpfNetMaps);
        InOrder inOrder = inOrder(mBpfNetMaps);

        // Update to new range which is old range minus APP1, i.e. only APP2
        final Set<UidRange> newRanges = new HashSet<>(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(mBpfNetMaps).removeUidInterfaceRules(uidCaptor.capture());
        assertContainsExactly(uidCaptor.getValue(), APP1_UID, APP2_UID);
        inOrder.verify(mBpfNetMaps).addUidInterfaceRules(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;
        doReturn(applicationInfo).when(mPackageManager)
                .getApplicationInfoAsUser(anyString(), anyInt(), any());
        doReturn(targetSdk).when(mPackageManager).getTargetSdkVersion(any());

        doReturn(locationToggle).when(mLocationManager).isLocationEnabledForUser(any());

        if (op != null) {
            doReturn(AppOpsManager.MODE_ALLOWED).when(mAppOpsManager).noteOp(
                    eq(op), eq(Process.myUid()), eq(mContext.getPackageName()),
                    eq(getAttributionTag()), anyString());
        }

        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);
        doReturn(REDACT_FOR_ACCESS_FINE_LOCATION).when(transportInfo).getApplicableRedactions();
        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);
        doReturn(REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_LOCAL_MAC_ADDRESS)
                .when(transportInfo).getApplicableRedactions();
        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);
        doReturn(REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_LOCAL_MAC_ADDRESS)
                .when(transportInfo).getApplicableRedactions();
        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);
        doReturn(REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_NETWORK_SETTINGS)
                .when(transportInfo).getApplicableRedactions();
        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);
        doReturn(REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_NETWORK_SETTINGS)
                .when(transportInfo).getApplicableRedactions();
        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 {
        doReturn(Build.VERSION_CODES.S).when(mPackageManager).getTargetSdkVersion(anyString());
        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<>());
        mMockVpn.setUnderlyingNetworkInfo(underlyingNetworkInfo);
        mDeps.setConnectionOwnerUid(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();
        doReturn(mIBinder).when(mConnectivityDiagnosticsCallback).asBinder();

        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();
        doReturn(mIBinder).when(mConnectivityDiagnosticsCallback).asBinder();

        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));
    }

    @Test(expected = NullPointerException.class)
    public void testRegisterConnectivityDiagnosticsCallbackNullCallback() {
        mService.registerConnectivityDiagnosticsCallback(
                null /* callback */,
                new NetworkRequest.Builder().build(),
                mContext.getPackageName());
    }

    @Test(expected = NullPointerException.class)
    public void testRegisterConnectivityDiagnosticsCallbackNullNetworkRequest() {
        mService.registerConnectivityDiagnosticsCallback(
                mConnectivityDiagnosticsCallback,
                null /* request */,
                mContext.getPackageName());
    }

    @Test(expected = NullPointerException.class)
    public void testRegisterConnectivityDiagnosticsCallbackNullPackageName() {
        mService.registerConnectivityDiagnosticsCallback(
                mConnectivityDiagnosticsCallback,
                new NetworkRequest.Builder().build(),
                null /* callingPackageName */);
    }

    @Test(expected = NullPointerException.class)
    public void testUnregisterConnectivityDiagnosticsCallbackNullPackageName() {
        mService.unregisterConnectivityDiagnosticsCallback(null /* callback */);
    }

    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 fakeVpnNai(NetworkCapabilities nc) {
        final NetworkCapabilities vpnNc = new NetworkCapabilities.Builder(nc)
                .addTransportType(TRANSPORT_VPN).build();
        final NetworkInfo info = new NetworkInfo(TYPE_VPN, 0 /* subtype */,
                ConnectivityManager.getNetworkTypeName(TYPE_VPN), "" /* subtypeName */);
        return fakeNai(vpnNc, 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 testUnderlyingNetworksWillBeSetInNetworkAgentInfoConstructor() throws Exception {
        assumeTrue(SdkLevel.isAtLeastT());
        final Network network1 = new Network(100);
        final Network network2 = new Network(101);
        final List<Network> underlyingNetworks = new ArrayList<>();
        final NetworkCapabilities ncWithEmptyUnderlyingNetworks = new NetworkCapabilities.Builder()
                .setUnderlyingNetworks(underlyingNetworks)
                .build();
        final NetworkAgentInfo vpnNaiWithEmptyUnderlyingNetworks =
                fakeVpnNai(ncWithEmptyUnderlyingNetworks);
        assertEquals(underlyingNetworks,
                Arrays.asList(vpnNaiWithEmptyUnderlyingNetworks.declaredUnderlyingNetworks));

        underlyingNetworks.add(network1);
        underlyingNetworks.add(network2);
        final NetworkCapabilities ncWithUnderlyingNetworks = new NetworkCapabilities.Builder()
                .setUnderlyingNetworks(underlyingNetworks)
                .build();
        final NetworkAgentInfo vpnNaiWithUnderlyingNetwokrs = fakeVpnNai(ncWithUnderlyingNetworks);
        assertEquals(underlyingNetworks,
                Arrays.asList(vpnNaiWithUnderlyingNetwokrs.declaredUnderlyingNetworks));

        final NetworkCapabilities ncWithoutUnderlyingNetworks = new NetworkCapabilities.Builder()
                .build();
        final NetworkAgentInfo vpnNaiWithoutUnderlyingNetwokrs =
                fakeVpnNai(ncWithoutUnderlyingNetworks);
        assertNull(vpnNaiWithoutUnderlyingNetwokrs.declaredUnderlyingNetworks);
    }

    @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();
        doReturn(mIBinder).when(mConnectivityDiagnosticsCallback).asBinder();

        mServiceContext.setPermission(NETWORK_STACK, PERMISSION_GRANTED);

        mService.registerConnectivityDiagnosticsCallback(
                mConnectivityDiagnosticsCallback, request, mContext.getPackageName());

        verify(mConnectivityDiagnosticsCallback, timeout(TIMEOUT_MS))
                .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();
        doReturn(mIBinder).when(mConnectivityDiagnosticsCallback).asBinder();

        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();

        // Make sure a report is sent and that the caps are suitably redacted.
        verify(mConnectivityDiagnosticsCallback, timeout(TIMEOUT_MS))
                .onConnectivityReportAvailable(argThat(report ->
                        areConnDiagCapsRedacted(report.getNetworkCapabilities())));
        reset(mConnectivityDiagnosticsCallback);
    }

    private boolean areConnDiagCapsRedacted(NetworkCapabilities nc) {
        TestTransportInfo ti = getTestTransportInfo(nc);
        return nc.getUids() == null
                && nc.getAdministratorUids().length == 0
                && nc.getOwnerUid() == Process.INVALID_UID
                && ti.locationRedacted
                && ti.localMacAddressRedacted
                && ti.settingsRedacted;
    }

    @Test
    public void testConnectivityDiagnosticsCallbackOnDataStallSuspected() throws Exception {
        setUpConnectivityDiagnosticsCallback();

        // Trigger notifyDataStallSuspected() on the INetworkMonitorCallbacks instance in the
        // cellular network agent
        mCellNetworkAgent.notifyDataStallSuspected();

        // Verify onDataStallSuspected fired
        verify(mConnectivityDiagnosticsCallback, timeout(TIMEOUT_MS)).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);

        // Verify onNetworkConnectivityReported fired
        verify(mConnectivityDiagnosticsCallback, timeout(TIMEOUT_MS))
                .onNetworkConnectivityReported(eq(n), eq(hasConnectivity));
        verify(mConnectivityDiagnosticsCallback, timeout(TIMEOUT_MS))
                .onConnectivityReportAvailable(
                        argThat(report ->
                                areConnDiagCapsRedacted(report.getNetworkCapabilities())));

        final boolean noConnectivity = false;
        mService.reportNetworkConnectivity(n, noConnectivity);

        // Wait for onNetworkConnectivityReported to fire
        verify(mConnectivityDiagnosticsCallback, timeout(TIMEOUT_MS))
                .onNetworkConnectivityReported(eq(n), eq(noConnectivity));

        // Also expect a ConnectivityReport after NetworkMonitor asynchronously re-validates
        verify(mConnectivityDiagnosticsCallback, timeout(TIMEOUT_MS).times(2))
                .onConnectivityReportAvailable(
                        argThat(report ->
                                areConnDiagCapsRedacted(report.getNetworkCapabilities())));
    }

    @Test
    public void testConnectivityDiagnosticsCallbackOnConnectivityReportedSeparateUid()
            throws Exception {
        setUpConnectivityDiagnosticsCallback();

        // report known Connectivity from a different uid. Verify that network is not re-validated
        // and this callback is not notified.
        final Network n = mCellNetworkAgent.getNetwork();
        final boolean hasConnectivity = true;
        doAsUid(Process.myUid() + 1, () -> mService.reportNetworkConnectivity(n, hasConnectivity));

        // Block until all other events are done processing.
        HandlerUtils.waitForIdle(mCsHandlerThread, TIMEOUT_MS);

        // Verify onNetworkConnectivityReported did not fire
        verify(mConnectivityDiagnosticsCallback, never())
                .onNetworkConnectivityReported(any(), anyBoolean());
        verify(mConnectivityDiagnosticsCallback, never())
                .onConnectivityReportAvailable(any());

        // report different Connectivity from a different uid. Verify that network is re-validated
        // and that this callback is notified.
        final boolean noConnectivity = false;
        doAsUid(Process.myUid() + 1, () -> mService.reportNetworkConnectivity(n, noConnectivity));

        // Wait for onNetworkConnectivityReported to fire
        verify(mConnectivityDiagnosticsCallback, timeout(TIMEOUT_MS))
                .onNetworkConnectivityReported(eq(n), eq(noConnectivity));

        // Also expect a ConnectivityReport after NetworkMonitor asynchronously re-validates
        verify(mConnectivityDiagnosticsCallback, timeout(TIMEOUT_MS))
                .onConnectivityReportAvailable(
                        argThat(report ->
                                areConnDiagCapsRedacted(report.getNetworkCapabilities())));
    }

    @Test(expected = NullPointerException.class)
    public void testSimulateDataStallNullNetwork() {
        mService.simulateDataStall(
                DataStallReport.DETECTION_METHOD_DNS_EVENTS,
                0L /* timestampMillis */,
                null /* network */,
                new PersistableBundle());
    }

    @Test(expected = NullPointerException.class)
    public void testSimulateDataStallNullPersistableBundle() {
        mService.simulateDataStall(
                DataStallReport.DETECTION_METHOD_DNS_EVENTS,
                0L /* timestampMillis */,
                mock(Network.class),
                null /* extras */);
    }

    @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);
    }

    private void verifyDump(String[] args) {
        final StringWriter stringWriter = new StringWriter();
        mService.dump(new FileDescriptor(), new PrintWriter(stringWriter), args);
        assertFalse(stringWriter.toString().isEmpty());
    }

    @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);

        verifyDump(new String[0]);

        // Verify dump with arguments.
        final String dumpPrio = "--dump-priority";
        final String[] dumpArgs = {dumpPrio};
        verifyDump(dumpArgs);

        final String[] highDumpArgs = {dumpPrio, "HIGH"};
        verifyDump(highDumpArgs);

        final String[] normalDumpArgs = {dumpPrio, "NORMAL"};
        verifyDump(normalDumpArgs);

        // Invalid args should do dumpNormal w/o exception
        final String[] unknownDumpArgs = {dumpPrio, "UNKNOWN"};
        verifyDump(unknownDumpArgs);

        final String[] invalidDumpArgs = {"UNKNOWN"};
        verifyDump(invalidDumpArgs);
    }

    @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_ORDER_VPN));
        } else {
            inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(
                    new NativeUidRangeConfig(mMockVpn.getNetwork().getNetId(),
                            toUidRangeStableParcels(vpnRanges), PREFERENCE_ORDER_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 ExpectedBroadcast b1 = registerPacProxyBroadcast();
        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.
        b1.expectNoBroadcast(500);

        // Update to new range which is old range minus APP1, i.e. only APP2
        final ExpectedBroadcast b2 = registerPacProxyBroadcast();
        final Set<UidRange> newRanges = new HashSet<>(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.
        b2.expectNoBroadcast(500);

        final ProxyInfo testProxyInfo = ProxyInfo.buildDirectProxy("test", 8888);
        final ExpectedBroadcast b3 = registerPacProxyBroadcast();
        lp.setHttpProxy(testProxyInfo);
        mMockVpn.sendLinkProperties(lp);
        waitForIdle();
        // Proxy is set, so send a proxy broadcast.
        b3.expectBroadcast();

        final ExpectedBroadcast b4 = registerPacProxyBroadcast();
        mMockVpn.setUids(vpnRanges);
        waitForIdle();
        // Uid has changed and proxy is already set, so send a proxy broadcast.
        b4.expectBroadcast();

        final ExpectedBroadcast b5 = registerPacProxyBroadcast();
        // Proxy is removed, send a proxy broadcast.
        lp.setHttpProxy(null);
        mMockVpn.sendLinkProperties(lp);
        waitForIdle();
        b5.expectBroadcast();

        // Proxy is added in WiFi(default network), setDefaultProxy will be called.
        final LinkProperties wifiLp = mCm.getLinkProperties(mWiFiNetworkAgent.getNetwork());
        assertNotNull(wifiLp);
        final ExpectedBroadcast b6 = expectProxyChangeAction(testProxyInfo);
        wifiLp.setHttpProxy(testProxyInfo);
        mWiFiNetworkAgent.sendLinkProperties(wifiLp);
        waitForIdle();
        b6.expectBroadcast();
    }

    @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);
        final ExpectedBroadcast b1 = registerPacProxyBroadcast();
        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.
        b1.expectNoBroadcast(500);

        final ExpectedBroadcast b2 = registerPacProxyBroadcast();
        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.
        b2.expectBroadcast();
    }

    @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);
        final ExpectedBroadcast b = registerPacProxyBroadcast();
        cellularLp.setHttpProxy(testProxyInfo);
        mCellNetworkAgent.sendLinkProperties(cellularLp);
        b.expectBroadcast();
    }

    @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;

            doReturn(network).when(mFilter).getNetwork();
            doReturn(QosCallbackException.EX_TYPE_FILTER_NONE).when(mFilter).validate();
            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);
        doReturn(binder).when(callback).asBinder();
        doReturn(true).when(binder).isBinderAlive();
        return new Pair<>(callback, binder);
    }


    @Test
    public void testQosCallbackRegistration() throws Exception {
        mQosCallbackMockHelper = new QosCallbackMockHelper();
        final NetworkAgentWrapper wrapper = mQosCallbackMockHelper.mAgentWrapper;

        doReturn(QosCallbackException.EX_TYPE_FILTER_NONE)
                .when(mQosCallbackMockHelper.mFilter).validate();
        mQosCallbackMockHelper.registerQosCallback(
                mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback);

        final OnQosCallbackRegister cbRegister1 =
                (OnQosCallbackRegister) wrapper.getCallbackHistory().poll(1000, x -> true);
        assertNotNull(cbRegister1);

        final int registerCallbackId = cbRegister1.mQosCallbackId;
        mService.unregisterQosCallback(mQosCallbackMockHelper.mCallback);
        final OnQosCallbackUnregister cbUnregister =
                (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();

        doReturn(QosCallbackException.EX_TYPE_FILTER_NETWORK_RELEASED)
                .when(mQosCallbackMockHelper.mFilter).validate();
        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;

        doReturn(QosCallbackException.EX_TYPE_FILTER_NONE)
                .when(mQosCallbackMockHelper.mFilter).validate();
        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;

        doReturn(QosCallbackException.EX_TYPE_FILTER_NONE)
                .when(mQosCallbackMockHelper.mFilter).validate();
        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 @IgnoreUpTo(SC_V2)
    public void testQosCallbackAvailableOnValidationError() throws Exception {
        mQosCallbackMockHelper = new QosCallbackMockHelper();
        final NetworkAgentWrapper wrapper = mQosCallbackMockHelper.mAgentWrapper;
        final int sessionId = 10;
        final int qosCallbackId = 1;

        doReturn(QosCallbackException.EX_TYPE_FILTER_NONE)
                .when(mQosCallbackMockHelper.mFilter).validate();
        mQosCallbackMockHelper.registerQosCallback(
                mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback);
        OnQosCallbackRegister cbRegister1 =
                (OnQosCallbackRegister) wrapper.getCallbackHistory().poll(1000, x -> true);
        assertNotNull(cbRegister1);
        final int registerCallbackId = cbRegister1.mQosCallbackId;

        waitForIdle();

        doReturn(QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED)
                .when(mQosCallbackMockHelper.mFilter).validate();
        final EpsBearerQosSessionAttributes attributes = new EpsBearerQosSessionAttributes(
                1, 2, 3, 4, 5, new ArrayList<>());
        mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent()
                .sendQosSessionAvailable(qosCallbackId, sessionId, attributes);
        waitForIdle();

        final NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister cbUnregister;
        cbUnregister = (NetworkAgentWrapper.CallbackType.OnQosCallbackUnregister)
                wrapper.getCallbackHistory().poll(1000, x -> true);
        assertNotNull(cbUnregister);
        assertEquals(registerCallbackId, cbUnregister.mQosCallbackId);
        waitForIdle();
        verify(mQosCallbackMockHelper.mCallback)
                .onError(eq(QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED));
    }

    @Test @IgnoreUpTo(SC_V2)
    public void testQosCallbackLostOnValidationError() throws Exception {
        mQosCallbackMockHelper = new QosCallbackMockHelper();
        final int sessionId = 10;
        final int qosCallbackId = 1;

        doReturn(QosCallbackException.EX_TYPE_FILTER_NONE)
                .when(mQosCallbackMockHelper.mFilter).validate();
        mQosCallbackMockHelper.registerQosCallback(
                mQosCallbackMockHelper.mFilter, mQosCallbackMockHelper.mCallback);
        waitForIdle();
        EpsBearerQosSessionAttributes attributes =
                sendQosSessionEvent(qosCallbackId, sessionId, true);
        waitForIdle();

        verify(mQosCallbackMockHelper.mCallback).onQosEpsBearerSessionAvailable(argThat(session ->
                session.getSessionId() == sessionId
                        && session.getSessionType() == QosSession.TYPE_EPS_BEARER), eq(attributes));

        doReturn(QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED)
                .when(mQosCallbackMockHelper.mFilter).validate();

        sendQosSessionEvent(qosCallbackId, sessionId, false);
        waitForIdle();
        verify(mQosCallbackMockHelper.mCallback)
                .onError(eq(QosCallbackException.EX_TYPE_FILTER_SOCKET_REMOTE_ADDRESS_CHANGED));
    }

    private EpsBearerQosSessionAttributes sendQosSessionEvent(
            int qosCallbackId, int sessionId, boolean available) {
        if (available) {
            final EpsBearerQosSessionAttributes attributes = new EpsBearerQosSessionAttributes(
                    1, 2, 3, 4, 5, new ArrayList<>());
            mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent()
                    .sendQosSessionAvailable(qosCallbackId, sessionId, attributes);
            return attributes;
        } else {
            mQosCallbackMockHelper.mAgentWrapper.getNetworkAgent()
                    .sendQosSessionLost(qosCallbackId, sessionId, QosSession.TYPE_EPS_BEARER);
            return null;
        }

    }

    @Test
    public void testQosCallbackTooManyRequests() throws Exception {
        mQosCallbackMockHelper = new QosCallbackMockHelper();

        doReturn(QosCallbackException.EX_TYPE_FILTER_NONE)
                .when(mQosCallbackMockHelper.mFilter).validate();
        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 {
            doReturn(applicationInfo).when(mPackageManager).getApplicationInfoAsUser(
                    eq(packageName), anyInt(), eq(user));
        } catch (Exception e) {
            fail(e.getMessage());
        }
    }

    private void mockGetApplicationInfoThrowsNameNotFound(@NonNull final String packageName,
            @NonNull final UserHandle user)
            throws Exception {
        doThrow(new PackageManager.NameNotFoundException(packageName)).when(
                mPackageManager).getApplicationInfoAsUser(eq(packageName), anyInt(), eq(user));
    }

    private void mockHasSystemFeature(@NonNull final String featureName, final boolean hasFeature) {
        doReturn(hasFeature).when(mPackageManager).hasSystemFeature(eq(featureName));
    }

    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_ORDER_OEM, nri.mPreferenceOrder);
        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_ORDER_OEM, nri.mPreferenceOrder);
        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_ORDER_OEM, nri.mPreferenceOrder);
        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_ORDER_OEM, nri.mPreferenceOrder);
        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);
        doReturn(asList(PRIMARY_USER_HANDLE, SECONDARY_USER_HANDLE, TERTIARY_USER_HANDLE))
                .when(mUserManager).getUserHandles(anyBoolean());

        // 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
                || mProfileDefaultNetworkCallbackAsAppUid2 != null
                || mTestPackageDefaultNetworkCallback2 != 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();
        mProfileDefaultNetworkCallbackAsAppUid2 = new TestNetworkCallback();
        mTestPackageDefaultNetworkCallback2 = new TestNetworkCallback();
        mCm.registerSystemDefaultNetworkCallback(mSystemDefaultNetworkCallback,
                new Handler(ConnectivityThread.getInstanceLooper()));
        mCm.registerDefaultNetworkCallback(mDefaultNetworkCallback);
        registerDefaultNetworkCallbackAsUid(mProfileDefaultNetworkCallback,
                TEST_WORK_PROFILE_APP_UID);
        registerDefaultNetworkCallbackAsUid(mTestPackageDefaultNetworkCallback, TEST_PACKAGE_UID);
        registerDefaultNetworkCallbackAsUid(mProfileDefaultNetworkCallbackAsAppUid2,
                TEST_WORK_PROFILE_APP_UID_2);
        registerDefaultNetworkCallbackAsUid(mTestPackageDefaultNetworkCallback2,
                TEST_PACKAGE_UID2);
        // 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);
        }
        if (null != mProfileDefaultNetworkCallbackAsAppUid2) {
            mCm.unregisterNetworkCallback(mProfileDefaultNetworkCallbackAsAppUid2);
        }
        if (null != mTestPackageDefaultNetworkCallback2) {
            mCm.unregisterNetworkCallback(mTestPackageDefaultNetworkCallback2);
        }
    }

    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_ORDER_OEM == config.subPriority));
        verify(mMockNetd, times(removeUidRangesTimes)).networkRemoveUidRangesParcel(
                argThat(config -> (useAnyIdForRemove ? true : removeUidRangesNetId == config.netId)
                        && Arrays.equals(removedUidRanges, config.uidRanges)
                        && PREFERENCE_ORDER_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);
        doReturn(asList(PRIMARY_USER_HANDLE, secondUserHandle)).when(mUserManager)
                .getUserHandles(anyBoolean());

        // 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);
        doReturn(asList(PRIMARY_USER_HANDLE)).when(mUserManager).getUserHandles(anyBoolean());

        // 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.
        doReturn(asList(PRIMARY_USER_HANDLE, secondUserHandle)).when(mUserManager)
                .getUserHandles(anyBoolean());
        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.
        doReturn(asList(PRIMARY_USER_HANDLE)).when(mUserManager).getUserHandles(anyBoolean());
        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 contain suspended networks.
        mCellNetworkAgent.suspend();
        waitForIdle();
        final NetworkCapabilities cellSuspendedNc =
                mCm.getNetworkCapabilities(mCellNetworkAgent.getNetwork());
        assertFalse(cellSuspendedNc.hasCapability(NET_CAPABILITY_NOT_SUSPENDED));
        final NetworkStateSnapshot cellSuspendedSnapshot = new NetworkStateSnapshot(
                mCellNetworkAgent.getNetwork(), cellSuspendedNc, cellLp,
                null, ConnectivityManager.TYPE_MOBILE);
        snapshots = mCm.getAllNetworkStateSnapshots();
        assertLength(2, snapshots);
        assertContainsAll(snapshots, cellSuspendedSnapshot, wifiSnapshot);

        // Disconnect wifi, verify the snapshots contain only cellular.
        mWiFiNetworkAgent.disconnect();
        waitForIdle();
        snapshots = mCm.getAllNetworkStateSnapshots();
        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork());
        assertLength(1, snapshots);
        assertEquals(cellSuspendedSnapshot, snapshots.get(0));

        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) {
        final UidRange range = UidRange.createForUser(handle);
        return new UidRangeParcel[] { new UidRangeParcel(range.start, range.stop) };
    }

    private UidRangeParcel[] uidRangeFor(final UserHandle handle,
            ProfileNetworkPreference profileNetworkPreference) {
        final Set<UidRange> uidRangeSet;
        UidRange range = UidRange.createForUser(handle);
        if (profileNetworkPreference.getIncludedUids().length != 0) {
            uidRangeSet = UidRangeUtils.convertArrayToUidRange(
                    profileNetworkPreference.getIncludedUids());

        } else if (profileNetworkPreference.getExcludedUids().length != 0)  {
            uidRangeSet = UidRangeUtils.removeRangeSetFromUidRange(
                    range, UidRangeUtils.convertArrayToUidRange(
                            profileNetworkPreference.getExcludedUids()));
        } else {
            uidRangeSet = new ArraySet<>();
            uidRangeSet.add(range);
        }
        UidRangeParcel[] uidRangeParcels = new UidRangeParcel[uidRangeSet.size()];
        int i = 0;
        for (UidRange range1 : uidRangeSet) {
            uidRangeParcels[i] = new UidRangeParcel(range1.start, range1.stop);
            i++;
        }
        return uidRangeParcels;
    }

    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 TestNetworkAgentWrapper makeEnterpriseNetworkAgent(int enterpriseId) throws Exception {
        final NetworkCapabilities workNc = new NetworkCapabilities();
        workNc.addCapability(NET_CAPABILITY_ENTERPRISE);
        workNc.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
        workNc.addEnterpriseId(enterpriseId);
        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 network preferences behave as expected for a given
     * profile network preference.
     */
    public void testPreferenceForUserNetworkUpDownForGivenPreference(
            ProfileNetworkPreference profileNetworkPreference,
            boolean connectWorkProfileAgentAhead,
            UserHandle testHandle,
            TestNetworkCallback profileDefaultNetworkCallback,
            TestNetworkCallback disAllowProfileDefaultNetworkCallback) throws Exception {
        final InOrder inOrder = inOrder(mMockNetd);

        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
        mCellNetworkAgent.connect(true);

        mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
        profileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
        if (disAllowProfileDefaultNetworkCallback != null) {
            disAllowProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(
                    mCellNetworkAgent);
        }
        inOrder.verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
                mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE));

        final TestNetworkAgentWrapper workAgent =
                makeEnterpriseNetworkAgent(profileNetworkPreference.getPreferenceEnterpriseId());
        if (connectWorkProfileAgentAhead) {
            workAgent.connect(false);
        }

        final TestOnCompleteListener listener = new TestOnCompleteListener();
        mCm.setProfileNetworkPreferences(testHandle, List.of(profileNetworkPreference),
                r -> r.run(), listener);
        listener.expectOnComplete();
        boolean allowFallback = true;
        if (profileNetworkPreference.getPreference()
                == PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK) {
            allowFallback = false;
        }
        if (allowFallback && !connectWorkProfileAgentAhead) {
            // Setting a network preference for this user will create a new set of routing rules for
            // the UID range that corresponds to this user, inorder 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, profileNetworkPreference),
                    PREFERENCE_ORDER_PROFILE));
        }

        // The enterprise network is not ready yet.
        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
        if (allowFallback && !connectWorkProfileAgentAhead) {
            assertNoCallbacks(profileDefaultNetworkCallback);
        } else if (!connectWorkProfileAgentAhead) {
            profileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
            if (disAllowProfileDefaultNetworkCallback != null) {
                assertNoCallbacks(disAllowProfileDefaultNetworkCallback);
            }
        }

        if (!connectWorkProfileAgentAhead) {
            workAgent.connect(false);
        }

        profileDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(workAgent);
        if (disAllowProfileDefaultNetworkCallback != null) {
            disAllowProfileDefaultNetworkCallback.assertNoCallback();
        }
        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, profileNetworkPreference),
                PREFERENCE_ORDER_PROFILE));

        if (allowFallback && !connectWorkProfileAgentAhead) {
            inOrder.verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig(
                    mCellNetworkAgent.getNetwork().netId,
                    uidRangeFor(testHandle, profileNetworkPreference),
                    PREFERENCE_ORDER_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());
        profileDefaultNetworkCallback.expectCapabilitiesThat(workAgent,
                nc -> nc.hasCapability(NET_CAPABILITY_VALIDATED)
                        && nc.hasCapability(NET_CAPABILITY_ENTERPRISE)
                        && nc.hasEnterpriseId(
                                profileNetworkPreference.getPreferenceEnterpriseId())
                        && nc.getEnterpriseIds().length == 1);
        if (disAllowProfileDefaultNetworkCallback != null) {
            assertNoCallbacks(disAllowProfileDefaultNetworkCallback);
        }
        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);

        workAgent.addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
        profileDefaultNetworkCallback.expectCapabilitiesThat(workAgent, nc ->
                nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
        if (disAllowProfileDefaultNetworkCallback != null) {
            assertNoCallbacks(disAllowProfileDefaultNetworkCallback);
        }
        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));
        if (disAllowProfileDefaultNetworkCallback != null) {
            disAllowProfileDefaultNetworkCallback.expectCapabilitiesThat(mCellNetworkAgent, nc ->
                    nc.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED));
        }
        profileDefaultNetworkCallback.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);
        if (disAllowProfileDefaultNetworkCallback != null) {
            disAllowProfileDefaultNetworkCallback.expectCallback(
                    CallbackEntry.LOST, mCellNetworkAgent);
        }
        profileDefaultNetworkCallback.assertNoCallback();
        inOrder.verify(mMockNetd).networkDestroy(mCellNetworkAgent.getNetwork().netId);

        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
        mCellNetworkAgent.connect(true);
        mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
        if (disAllowProfileDefaultNetworkCallback != null) {
            disAllowProfileDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(
                    mCellNetworkAgent);

        }
        profileDefaultNetworkCallback.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();
        profileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, workAgent);
        if (allowFallback) {
            profileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
            if (disAllowProfileDefaultNetworkCallback != null) {
                assertNoCallbacks(disAllowProfileDefaultNetworkCallback);
            }
        }
        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
        if (allowFallback) {
            inOrder.verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
                    mCellNetworkAgent.getNetwork().netId,
                    uidRangeFor(testHandle, profileNetworkPreference),
                    PREFERENCE_ORDER_PROFILE));
        }
        inOrder.verify(mMockNetd).networkDestroy(workAgent.getNetwork().netId);

        mCellNetworkAgent.disconnect();
        mSystemDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
        mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mCellNetworkAgent);
        if (disAllowProfileDefaultNetworkCallback != null) {
            disAllowProfileDefaultNetworkCallback.expectCallback(
                    CallbackEntry.LOST, mCellNetworkAgent);
        }
        if (allowFallback) {
            profileDefaultNetworkCallback.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(profileNetworkPreference.getPreferenceEnterpriseId());
        workAgent2.connect(false);

        profileDefaultNetworkCallback.expectAvailableCallbacksUnvalidated(workAgent2);
        if (disAllowProfileDefaultNetworkCallback != null) {
            assertNoCallbacks(disAllowProfileDefaultNetworkCallback);
        }
        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, profileNetworkPreference), PREFERENCE_ORDER_PROFILE));

        workAgent2.setNetworkValid(true /* isStrictMode */);
        workAgent2.mNetworkMonitor.forceReevaluation(Process.myUid());
        profileDefaultNetworkCallback.expectCapabilitiesThat(workAgent2,
                nc -> nc.hasCapability(NET_CAPABILITY_ENTERPRISE)
                        && !nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)
                        && nc.hasEnterpriseId(
                        profileNetworkPreference.getPreferenceEnterpriseId())
                        && nc.getEnterpriseIds().length == 1);
        if (disAllowProfileDefaultNetworkCallback != null) {
            assertNoCallbacks(disAllowProfileDefaultNetworkCallback);
        }
        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
        inOrder.verify(mMockNetd, never()).networkAddUidRangesParcel(any());

        // When the agent disconnects, test that the app on the work profile fall back to the
        // default network.
        workAgent2.disconnect();
        profileDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, workAgent2);
        if (disAllowProfileDefaultNetworkCallback != null) {
            assertNoCallbacks(disAllowProfileDefaultNetworkCallback);
        }
        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
        inOrder.verify(mMockNetd).networkDestroy(workAgent2.getNetwork().netId);

        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback,
                profileDefaultNetworkCallback);

        // Callbacks will be unregistered by tearDown()
    }

    /**
     * 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 UserHandle testHandle = setupEnterpriseNetwork();
        registerDefaultNetworkCallbacks();
        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
                new ProfileNetworkPreference.Builder();
        profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
        profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
        testPreferenceForUserNetworkUpDownForGivenPreference(
                profileNetworkPreferenceBuilder.build(), false,
                testHandle, mProfileDefaultNetworkCallback, null);
    }

    /**
     * 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 when configured to not fallback to default network.
     */
    @Test
    public void testPreferenceForUserNetworkUpDownWithNoFallback() throws Exception {
        final UserHandle testHandle = setupEnterpriseNetwork();
        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
                new ProfileNetworkPreference.Builder();
        profileNetworkPreferenceBuilder.setPreference(
                PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
        profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
        registerDefaultNetworkCallbacks();
        testPreferenceForUserNetworkUpDownForGivenPreference(
                profileNetworkPreferenceBuilder.build(), false,
                testHandle, mProfileDefaultNetworkCallback, null);
    }

    /**
     * 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 when configured to not fallback to default network
     * along with already connected enterprise work agent
     */
    @Test
    public void testPreferenceForUserNetworkUpDownWithNoFallbackWithAlreadyConnectedWorkAgent()
            throws Exception {
        final UserHandle testHandle = setupEnterpriseNetwork();
        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
                new ProfileNetworkPreference.Builder();
        profileNetworkPreferenceBuilder.setPreference(
                PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
        profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
        registerDefaultNetworkCallbacks();
        testPreferenceForUserNetworkUpDownForGivenPreference(
                profileNetworkPreferenceBuilder.build(), true, testHandle,
                mProfileDefaultNetworkCallback, null);
    }

    /**
     * Make sure per-profile networking preference for specific uid of test handle
     * behaves as expected
     */
    @Test
    public void testPreferenceForDefaultUidOfTestHandle() throws Exception {
        final UserHandle testHandle = setupEnterpriseNetwork();
        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
                new ProfileNetworkPreference.Builder();
        profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
        profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
        profileNetworkPreferenceBuilder.setIncludedUids(
                new int[]{testHandle.getUid(TEST_WORK_PROFILE_APP_UID)});
        registerDefaultNetworkCallbacks();
        testPreferenceForUserNetworkUpDownForGivenPreference(
                profileNetworkPreferenceBuilder.build(), false, testHandle,
                mProfileDefaultNetworkCallback, null);
    }

    /**
     * Make sure per-profile networking preference for specific uid of test handle
     * behaves as expected
     */
    @Test
    public void testPreferenceForSpecificUidOfOnlyOneApp() throws Exception {
        final UserHandle testHandle = setupEnterpriseNetwork();
        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
                new ProfileNetworkPreference.Builder();
        profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
        profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
        profileNetworkPreferenceBuilder.setIncludedUids(
                new int[]{testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2)});
        registerDefaultNetworkCallbacks();
        testPreferenceForUserNetworkUpDownForGivenPreference(
                profileNetworkPreferenceBuilder.build(), false,
                testHandle, mProfileDefaultNetworkCallbackAsAppUid2, null);
    }

    /**
     * Make sure per-profile networking preference for specific uid of test handle
     * behaves as expected
     */
    @Test
    public void testPreferenceForDisallowSpecificUidOfApp() throws Exception {
        final UserHandle testHandle = setupEnterpriseNetwork();
        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
                new ProfileNetworkPreference.Builder();
        profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
        profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
        profileNetworkPreferenceBuilder.setExcludedUids(
                new int[]{testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2)});
        registerDefaultNetworkCallbacks();
        testPreferenceForUserNetworkUpDownForGivenPreference(
                profileNetworkPreferenceBuilder.build(), false,
                testHandle, mProfileDefaultNetworkCallback,
                mProfileDefaultNetworkCallbackAsAppUid2);
    }

    /**
     * Make sure per-profile networking preference for specific uid of test handle
     * invalid uid inputs
     */
    @Test
    public void testPreferenceForInvalidUids() throws Exception {
        final UserHandle testHandle = setupEnterpriseNetwork();
        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
                new ProfileNetworkPreference.Builder();
        profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
        profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
        profileNetworkPreferenceBuilder.setExcludedUids(
                new int[]{testHandle.getUid(0) - 1});
        final TestOnCompleteListener listener = new TestOnCompleteListener();
        Assert.assertThrows(IllegalArgumentException.class, () -> mCm.setProfileNetworkPreferences(
                testHandle, List.of(profileNetworkPreferenceBuilder.build()),
                r -> r.run(), listener));

        profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
        profileNetworkPreferenceBuilder.setIncludedUids(
                new int[]{testHandle.getUid(0) - 1});
        Assert.assertThrows(IllegalArgumentException.class,
                () -> mCm.setProfileNetworkPreferences(
                        testHandle, List.of(profileNetworkPreferenceBuilder.build()),
                        r -> r.run(), listener));


        profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
        profileNetworkPreferenceBuilder.setIncludedUids(
                new int[]{testHandle.getUid(0) - 1});
        profileNetworkPreferenceBuilder.setExcludedUids(
                new int[]{testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2)});
        Assert.assertThrows(IllegalArgumentException.class,
                () -> mCm.setProfileNetworkPreferences(
                        testHandle, List.of(profileNetworkPreferenceBuilder.build()),
                        r -> r.run(), listener));

        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder2 =
                new ProfileNetworkPreference.Builder();
        profileNetworkPreferenceBuilder2.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
        profileNetworkPreferenceBuilder2.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
        profileNetworkPreferenceBuilder2.setIncludedUids(
                new int[]{testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2)});
        profileNetworkPreferenceBuilder.setIncludedUids(
                new int[]{testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2)});
        Assert.assertThrows(IllegalArgumentException.class,
                () -> mCm.setProfileNetworkPreferences(
                        testHandle, List.of(profileNetworkPreferenceBuilder.build(),
                                profileNetworkPreferenceBuilder2.build()),
                        r -> r.run(), listener));

        profileNetworkPreferenceBuilder2.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
        profileNetworkPreferenceBuilder2.setExcludedUids(
                new int[]{testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2)});
        profileNetworkPreferenceBuilder.setExcludedUids(
                new int[]{testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2)});
        Assert.assertThrows(IllegalArgumentException.class,
                () -> mCm.setProfileNetworkPreferences(
                        testHandle, List.of(profileNetworkPreferenceBuilder.build(),
                                profileNetworkPreferenceBuilder2.build()),
                        r -> r.run(), listener));

        profileNetworkPreferenceBuilder2.setPreference(
                PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
        profileNetworkPreferenceBuilder2.setExcludedUids(
                new int[]{testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2)});
        profileNetworkPreferenceBuilder.setExcludedUids(
                new int[]{testHandle.getUid(TEST_WORK_PROFILE_APP_UID_2)});
        Assert.assertThrows(IllegalArgumentException.class,
                () -> mCm.setProfileNetworkPreferences(
                        testHandle, List.of(profileNetworkPreferenceBuilder.build(),
                                profileNetworkPreferenceBuilder2.build()),
                        r -> r.run(), listener));
    }

    /**
     * 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 when configured to fallback to default network
     * along with already connected enterprise work agent
     */
    @Test
    public void testPreferenceForUserNetworkUpDownWithFallbackWithAlreadyConnectedWorkAgent()
            throws Exception {
        final UserHandle testHandle = setupEnterpriseNetwork();
        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
                new ProfileNetworkPreference.Builder();
        profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
        profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
        registerDefaultNetworkCallbacks();
        testPreferenceForUserNetworkUpDownForGivenPreference(
                profileNetworkPreferenceBuilder.build(), true,
                testHandle, mProfileDefaultNetworkCallback,
                null);
    }

    /**
     * Make sure per-profile networking preference behaves as expected when the enterprise network
     * goes up and down while the preference is active for a given enterprise identifier
     */
    @Test
    public void testPreferenceForUserNetworkUpDownWithDefaultEnterpriseId()
            throws Exception {
        final UserHandle testHandle = setupEnterpriseNetwork();
        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
                new ProfileNetworkPreference.Builder();
        profileNetworkPreferenceBuilder.setPreference(
                PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
        profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
        registerDefaultNetworkCallbacks();
        testPreferenceForUserNetworkUpDownForGivenPreference(
                profileNetworkPreferenceBuilder.build(), true,
                testHandle, mProfileDefaultNetworkCallback,
                null);
    }

    /**
     * Make sure per-profile networking preference behaves as expected when the enterprise network
     * goes up and down while the preference is active for a given enterprise identifier
     */
    @Test
    public void testPreferenceForUserNetworkUpDownWithId2()
            throws Exception {
        final UserHandle testHandle = setupEnterpriseNetwork();
        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
                new ProfileNetworkPreference.Builder();
        profileNetworkPreferenceBuilder.setPreference(
                PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
        profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(
                NET_ENTERPRISE_ID_2);
        registerDefaultNetworkCallbacks();
        testPreferenceForUserNetworkUpDownForGivenPreference(
                profileNetworkPreferenceBuilder.build(), true,
                testHandle, mProfileDefaultNetworkCallback, null);
    }

    /**
     * Make sure per-profile networking preference behaves as expected when the enterprise network
     * goes up and down while the preference is active for a given enterprise identifier
     */
    @Test
    public void testPreferenceForUserNetworkUpDownWithInvalidId()
            throws Exception {
        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
                new ProfileNetworkPreference.Builder();
        profileNetworkPreferenceBuilder.setPreference(
                PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
        profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(0);
        registerDefaultNetworkCallbacks();
        assertThrows("Should not be able to set invalid enterprise id",
                IllegalStateException.class, () -> profileNetworkPreferenceBuilder.build());
    }

    /**
     * Make sure per-profile networking preference throws exception when default preference
     * is set along with enterprise preference.
     */
    @Test
    public void testPreferenceWithInvalidPreferenceDefaultAndEnterpriseTogether()
            throws Exception {
        final UserHandle testHandle = setupEnterpriseNetwork();
        mServiceContext.setWorkProfile(testHandle, true);

        final int testWorkProfileAppUid1 =
                UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID);
        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder1 =
                new ProfileNetworkPreference.Builder();
        profileNetworkPreferenceBuilder1.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
        profileNetworkPreferenceBuilder1.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
        profileNetworkPreferenceBuilder1.setIncludedUids(new int[]{testWorkProfileAppUid1});

        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder2 =
                new ProfileNetworkPreference.Builder();
        profileNetworkPreferenceBuilder2.setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT);
        final TestOnCompleteListener listener = new TestOnCompleteListener();
        Assert.assertThrows(IllegalArgumentException.class,
                () -> mCm.setProfileNetworkPreferences(
                        testHandle, List.of(profileNetworkPreferenceBuilder1.build(),
                                profileNetworkPreferenceBuilder2.build()),
                        r -> r.run(), listener));
        Assert.assertThrows(IllegalArgumentException.class,
                () -> mCm.setProfileNetworkPreferences(
                        testHandle, List.of(profileNetworkPreferenceBuilder2.build(),
                                profileNetworkPreferenceBuilder1.build()),
                        r -> r.run(), listener));
    }

    /**
     * Make sure per profile network preferences behave as expected when two slices with
     * two different apps within same user profile is configured
     * Make sure per profile network preferences overrides with latest preference when
     * same user preference is set twice
     */
    @Test
    public void testSetPreferenceWithOverridingPreference()
            throws Exception {
        final InOrder inOrder = inOrder(mMockNetd);
        final UserHandle testHandle = setupEnterpriseNetwork();
        mServiceContext.setWorkProfile(testHandle, true);
        registerDefaultNetworkCallbacks();

        final TestNetworkCallback appCb1 = new TestNetworkCallback();
        final TestNetworkCallback appCb2 = new TestNetworkCallback();
        final TestNetworkCallback appCb3 = new TestNetworkCallback();

        final int testWorkProfileAppUid1 =
                UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID);
        final int testWorkProfileAppUid2 =
                UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID_2);
        final int testWorkProfileAppUid3 =
                UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID_3);

        registerDefaultNetworkCallbackAsUid(appCb1, testWorkProfileAppUid1);
        registerDefaultNetworkCallbackAsUid(appCb2, testWorkProfileAppUid2);
        registerDefaultNetworkCallbackAsUid(appCb3, testWorkProfileAppUid3);

        // Connect both a regular cell agent and an enterprise network first.
        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
        mCellNetworkAgent.connect(true);

        final TestNetworkAgentWrapper workAgent1 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_1);
        final TestNetworkAgentWrapper workAgent2 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_2);
        workAgent1.connect(true);
        workAgent2.connect(true);

        mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);

        appCb1.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
        appCb2.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
        appCb3.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);

        verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
                mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE));
        verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
                workAgent1.getNetwork().netId, INetd.PERMISSION_SYSTEM));
        verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
                workAgent2.getNetwork().netId, INetd.PERMISSION_SYSTEM));

        final TestOnCompleteListener listener = new TestOnCompleteListener();

        // Set preferences for testHandle to map testWorkProfileAppUid1 to
        // NET_ENTERPRISE_ID_1 and testWorkProfileAppUid2 to NET_ENTERPRISE_ID_2.
        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder1 =
                new ProfileNetworkPreference.Builder();
        profileNetworkPreferenceBuilder1.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
        profileNetworkPreferenceBuilder1.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
        profileNetworkPreferenceBuilder1.setIncludedUids(new int[]{testWorkProfileAppUid1});

        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder2 =
                new ProfileNetworkPreference.Builder();
        profileNetworkPreferenceBuilder2.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
        profileNetworkPreferenceBuilder2.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_2);
        profileNetworkPreferenceBuilder2.setIncludedUids(new int[]{testWorkProfileAppUid2});

        mCm.setProfileNetworkPreferences(testHandle,
                List.of(profileNetworkPreferenceBuilder1.build(),
                        profileNetworkPreferenceBuilder2.build()),
                r -> r.run(), listener);
        listener.expectOnComplete();
        verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
                workAgent2.getNetwork().netId,
                uidRangeFor(testHandle, profileNetworkPreferenceBuilder2.build()),
                PREFERENCE_ORDER_PROFILE));
        verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
                workAgent1.getNetwork().netId,
                uidRangeFor(testHandle, profileNetworkPreferenceBuilder1.build()),
                PREFERENCE_ORDER_PROFILE));

        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
        appCb1.expectAvailableCallbacksValidated(workAgent1);
        appCb2.expectAvailableCallbacksValidated(workAgent2);

        // Set preferences for testHandle to map testWorkProfileAppUid3 to
        // to NET_ENTERPRISE_ID_1.
        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder3 =
                new ProfileNetworkPreference.Builder();
        profileNetworkPreferenceBuilder3.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
        profileNetworkPreferenceBuilder3.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
        profileNetworkPreferenceBuilder3.setIncludedUids(new int[]{testWorkProfileAppUid3});

        mCm.setProfileNetworkPreferences(testHandle,
                List.of(profileNetworkPreferenceBuilder3.build()),
                r -> r.run(), listener);
        listener.expectOnComplete();
        verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
                workAgent1.getNetwork().netId,
                uidRangeFor(testHandle, profileNetworkPreferenceBuilder3.build()),
                PREFERENCE_ORDER_PROFILE));
        verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig(
                workAgent2.getNetwork().netId,
                uidRangeFor(testHandle, profileNetworkPreferenceBuilder2.build()),
                PREFERENCE_ORDER_PROFILE));
        verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig(
                workAgent1.getNetwork().netId,
                uidRangeFor(testHandle, profileNetworkPreferenceBuilder1.build()),
                PREFERENCE_ORDER_PROFILE));

        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
        appCb3.expectAvailableCallbacksValidated(workAgent1);
        appCb2.expectAvailableCallbacksValidated(mCellNetworkAgent);
        appCb1.expectAvailableCallbacksValidated(mCellNetworkAgent);

        // Set the preferences for testHandle to default.
        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
                new ProfileNetworkPreference.Builder();
        profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT);

        mCm.setProfileNetworkPreferences(testHandle,
                List.of(profileNetworkPreferenceBuilder.build()),
                r -> r.run(), listener);
        listener.expectOnComplete();
        verify(mMockNetd).networkRemoveUidRangesParcel(new NativeUidRangeConfig(
                workAgent1.getNetwork().netId,
                uidRangeFor(testHandle, profileNetworkPreferenceBuilder3.build()),
                PREFERENCE_ORDER_PROFILE));

        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, appCb1, appCb2);
        appCb3.expectAvailableCallbacksValidated(mCellNetworkAgent);
        workAgent2.disconnect();
        mCellNetworkAgent.disconnect();

        mCm.unregisterNetworkCallback(appCb1);
        mCm.unregisterNetworkCallback(appCb2);
        mCm.unregisterNetworkCallback(appCb3);
        // Other callbacks will be unregistered by tearDown()
    }

    /**
     * Make sure per profile network preferences behave as expected when multiple slices with
     * multiple different apps within same user profile is configured.
     */
    @Test
    public void testSetPreferenceWithMultiplePreferences()
            throws Exception {
        final InOrder inOrder = inOrder(mMockNetd);

        final UserHandle testHandle = setupEnterpriseNetwork();
        mServiceContext.setWorkProfile(testHandle, true);
        registerDefaultNetworkCallbacks();

        final TestNetworkCallback appCb1 = new TestNetworkCallback();
        final TestNetworkCallback appCb2 = new TestNetworkCallback();
        final TestNetworkCallback appCb3 = new TestNetworkCallback();
        final TestNetworkCallback appCb4 = new TestNetworkCallback();
        final TestNetworkCallback appCb5 = new TestNetworkCallback();

        final int testWorkProfileAppUid1 =
                UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID);
        final int testWorkProfileAppUid2 =
                UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID_2);
        final int testWorkProfileAppUid3 =
                UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID_3);
        final int testWorkProfileAppUid4 =
                UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID_4);
        final int testWorkProfileAppUid5 =
                UserHandle.getUid(testHandle.getIdentifier(), TEST_APP_ID_5);

        registerDefaultNetworkCallbackAsUid(appCb1, testWorkProfileAppUid1);
        registerDefaultNetworkCallbackAsUid(appCb2, testWorkProfileAppUid2);
        registerDefaultNetworkCallbackAsUid(appCb3, testWorkProfileAppUid3);
        registerDefaultNetworkCallbackAsUid(appCb4, testWorkProfileAppUid4);
        registerDefaultNetworkCallbackAsUid(appCb5, testWorkProfileAppUid5);

        // Connect both a regular cell agent and an enterprise network first.
        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
        mCellNetworkAgent.connect(true);

        final TestNetworkAgentWrapper workAgent1 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_1);
        final TestNetworkAgentWrapper workAgent2 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_2);
        final TestNetworkAgentWrapper workAgent3 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_3);
        final TestNetworkAgentWrapper workAgent4 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_4);
        final TestNetworkAgentWrapper workAgent5 = makeEnterpriseNetworkAgent(NET_ENTERPRISE_ID_5);

        workAgent1.connect(true);
        workAgent2.connect(true);
        workAgent3.connect(true);
        workAgent4.connect(true);
        workAgent5.connect(true);

        mSystemDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
        mDefaultNetworkCallback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
        appCb1.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
        appCb2.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
        appCb3.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
        appCb4.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
        appCb5.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);

        verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
                mCellNetworkAgent.getNetwork().netId, INetd.PERMISSION_NONE));
        verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
                workAgent1.getNetwork().netId, INetd.PERMISSION_SYSTEM));
        verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
                workAgent2.getNetwork().netId, INetd.PERMISSION_SYSTEM));
        verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
                workAgent3.getNetwork().netId, INetd.PERMISSION_SYSTEM));
        verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
                workAgent4.getNetwork().netId, INetd.PERMISSION_SYSTEM));
        verify(mMockNetd).networkCreate(nativeNetworkConfigPhysical(
                workAgent5.getNetwork().netId, INetd.PERMISSION_SYSTEM));

        final TestOnCompleteListener listener = new TestOnCompleteListener();

        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder1 =
                new ProfileNetworkPreference.Builder();
        profileNetworkPreferenceBuilder1.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
        profileNetworkPreferenceBuilder1.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
        profileNetworkPreferenceBuilder1.setIncludedUids(new int[]{testWorkProfileAppUid1});

        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder2 =
                new ProfileNetworkPreference.Builder();
        profileNetworkPreferenceBuilder2.setPreference(
                PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
        profileNetworkPreferenceBuilder2.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_2);
        profileNetworkPreferenceBuilder2.setIncludedUids(new int[]{testWorkProfileAppUid2});

        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder3 =
                new ProfileNetworkPreference.Builder();
        profileNetworkPreferenceBuilder3.setPreference(
                PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
        profileNetworkPreferenceBuilder3.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_3);
        profileNetworkPreferenceBuilder3.setIncludedUids(new int[]{testWorkProfileAppUid3});

        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder4 =
                new ProfileNetworkPreference.Builder();
        profileNetworkPreferenceBuilder4.setPreference(
                PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK);
        profileNetworkPreferenceBuilder4.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_4);
        profileNetworkPreferenceBuilder4.setIncludedUids(new int[]{testWorkProfileAppUid4});

        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder5 =
                new ProfileNetworkPreference.Builder();
        profileNetworkPreferenceBuilder5.setPreference(
                PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
        profileNetworkPreferenceBuilder5.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_5);
        profileNetworkPreferenceBuilder5.setIncludedUids(new int[]{testWorkProfileAppUid5});

        mCm.setProfileNetworkPreferences(testHandle,
                List.of(profileNetworkPreferenceBuilder1.build(),
                        profileNetworkPreferenceBuilder2.build(),
                        profileNetworkPreferenceBuilder3.build(),
                        profileNetworkPreferenceBuilder4.build(),
                        profileNetworkPreferenceBuilder5.build()),
                r -> r.run(), listener);

        listener.expectOnComplete();

        verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
                workAgent1.getNetwork().netId,
                uidRangeFor(testHandle, profileNetworkPreferenceBuilder1.build()),
                PREFERENCE_ORDER_PROFILE));
        verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
                workAgent2.getNetwork().netId,
                uidRangeFor(testHandle, profileNetworkPreferenceBuilder2.build()),
                PREFERENCE_ORDER_PROFILE));
        verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
                workAgent3.getNetwork().netId,
                uidRangeFor(testHandle, profileNetworkPreferenceBuilder3.build()),
                PREFERENCE_ORDER_PROFILE));
        verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
                workAgent4.getNetwork().netId,
                uidRangeFor(testHandle, profileNetworkPreferenceBuilder4.build()),
                PREFERENCE_ORDER_PROFILE));
        verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
                workAgent5.getNetwork().netId,
                uidRangeFor(testHandle, profileNetworkPreferenceBuilder5.build()),
                PREFERENCE_ORDER_PROFILE));

        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback);
        appCb1.expectAvailableCallbacksValidated(workAgent1);
        appCb2.expectAvailableCallbacksValidated(workAgent2);
        appCb3.expectAvailableCallbacksValidated(workAgent3);
        appCb4.expectAvailableCallbacksValidated(workAgent4);
        appCb5.expectAvailableCallbacksValidated(workAgent5);

        workAgent1.disconnect();
        workAgent2.disconnect();
        workAgent3.disconnect();
        workAgent4.disconnect();
        workAgent5.disconnect();

        appCb1.expectCallback(CallbackEntry.LOST, workAgent1);
        appCb2.expectCallback(CallbackEntry.LOST, workAgent2);
        appCb3.expectCallback(CallbackEntry.LOST, workAgent3);
        appCb4.expectCallback(CallbackEntry.LOST, workAgent4);
        appCb5.expectCallback(CallbackEntry.LOST, workAgent5);

        appCb1.expectAvailableCallbacksValidated(mCellNetworkAgent);
        appCb2.assertNoCallback();
        appCb3.expectAvailableCallbacksValidated(mCellNetworkAgent);
        appCb4.assertNoCallback();
        appCb5.expectAvailableCallbacksValidated(mCellNetworkAgent);

        verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
                mCellNetworkAgent.getNetwork().netId,
                uidRangeFor(testHandle, profileNetworkPreferenceBuilder1.build()),
                PREFERENCE_ORDER_PROFILE));
        verify(mMockNetd, never()).networkAddUidRangesParcel(new NativeUidRangeConfig(
                mCellNetworkAgent.getNetwork().netId,
                uidRangeFor(testHandle, profileNetworkPreferenceBuilder2.build()),
                PREFERENCE_ORDER_PROFILE));
        verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
                mCellNetworkAgent.getNetwork().netId,
                uidRangeFor(testHandle, profileNetworkPreferenceBuilder3.build()),
                PREFERENCE_ORDER_PROFILE));
        verify(mMockNetd, never()).networkAddUidRangesParcel(new NativeUidRangeConfig(
                mCellNetworkAgent.getNetwork().netId,
                uidRangeFor(testHandle, profileNetworkPreferenceBuilder4.build()),
                PREFERENCE_ORDER_PROFILE));
        verify(mMockNetd).networkAddUidRangesParcel(new NativeUidRangeConfig(
                mCellNetworkAgent.getNetwork().netId,
                uidRangeFor(testHandle, profileNetworkPreferenceBuilder5.build()),
                PREFERENCE_ORDER_PROFILE));

        mSystemDefaultNetworkCallback.assertNoCallback();
        mDefaultNetworkCallback.assertNoCallback();

        // Set the preferences for testHandle to default.
        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
                new ProfileNetworkPreference.Builder();
        profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_DEFAULT);

        mCm.setProfileNetworkPreferences(testHandle,
                List.of(profileNetworkPreferenceBuilder.build()),
                r -> r.run(), listener);
        listener.expectOnComplete();
        assertNoCallbacks(mSystemDefaultNetworkCallback, mDefaultNetworkCallback, appCb1, appCb3,
                appCb5);
        appCb2.expectAvailableCallbacksValidated(mCellNetworkAgent);
        appCb4.expectAvailableCallbacksValidated(mCellNetworkAgent);
        mCellNetworkAgent.disconnect();

        mCm.unregisterNetworkCallback(appCb1);
        mCm.unregisterNetworkCallback(appCb2);
        mCm.unregisterNetworkCallback(appCb3);
        mCm.unregisterNetworkCallback(appCb4);
        mCm.unregisterNetworkCallback(appCb5);
        // Other 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_ORDER_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_ORDER_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_ORDER_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_ORDER_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_ORDER_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_ORDER_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_ORDER_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);
        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
                new ProfileNetworkPreference.Builder();
        profileNetworkPreferenceBuilder.setPreference(
                PROFILE_NETWORK_PREFERENCE_ENTERPRISE_NO_FALLBACK + 1);
        profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
        assertThrows("Should not be able to set an illegal preference",
                IllegalArgumentException.class,
                () -> mCm.setProfileNetworkPreferences(testHandle,
                        List.of(profileNetworkPreferenceBuilder.build()),
                        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);
        mServiceContext.setDeviceOwner(testHandle, null);
        assertThrows("Should not be able to set a user pref for a non-work profile "
                + "and non device owner",
                IllegalArgumentException.class , () ->
                        mCm.setProfileNetworkPreference(testHandle,
                                PROFILE_NETWORK_PREFERENCE_ENTERPRISE, null, null));
    }

    /**
     * Make sure requests for per-profile default networking for a device owner is
     * accepted on T and not accepted on S
     */
    @Test
    public void testProfileNetworkDeviceOwner() throws Exception {
        final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID);
        mServiceContext.setWorkProfile(testHandle, false);
        mServiceContext.setDeviceOwner(testHandle, "deviceOwnerPackage");
        ProfileNetworkPreference.Builder profileNetworkPreferenceBuilder =
                new ProfileNetworkPreference.Builder();
        profileNetworkPreferenceBuilder.setPreference(PROFILE_NETWORK_PREFERENCE_ENTERPRISE);
        profileNetworkPreferenceBuilder.setPreferenceEnterpriseId(NET_ENTERPRISE_ID_1);
        final TestOnCompleteListener listener = new TestOnCompleteListener();
        if (SdkLevel.isAtLeastT()) {
            mCm.setProfileNetworkPreferences(testHandle,
                    List.of(profileNetworkPreferenceBuilder.build()),
                    r -> r.run(), listener);
        } else {
            // S should not allow setting preference on device owner
            assertThrows("Should not be able to set a user pref for a non-work profile on S",
                    IllegalArgumentException.class , () ->
                            mCm.setProfileNetworkPreferences(testHandle,
                                    List.of(profileNetworkPreferenceBuilder.build()),
                                    r -> r.run(), listener));
        }
    }

    @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()));
    }

    @Test
    public void testAllowedUids() throws Exception {
        final int preferenceOrder =
                ConnectivityService.PREFERENCE_ORDER_IRRELEVANT_BECAUSE_NOT_DEFAULT;
        mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
        mServiceContext.setPermission(MANAGE_TEST_NETWORKS, PERMISSION_GRANTED);
        final TestNetworkCallback cb = new TestNetworkCallback();
        mCm.requestNetwork(new NetworkRequest.Builder()
                        .clearCapabilities()
                        .addTransportType(TRANSPORT_TEST)
                        .build(),
                cb);

        final ArraySet<Integer> uids = new ArraySet<>();
        uids.add(200);
        final NetworkCapabilities nc = new NetworkCapabilities.Builder()
                .addTransportType(TRANSPORT_TEST)
                .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
                .setAllowedUids(uids)
                .build();
        final TestNetworkAgentWrapper agent = new TestNetworkAgentWrapper(TRANSPORT_TEST,
                new LinkProperties(), nc);
        agent.connect(true);
        cb.expectAvailableThenValidatedCallbacks(agent);

        final InOrder inOrder = inOrder(mMockNetd);
        final NativeUidRangeConfig uids200Parcel = new NativeUidRangeConfig(
                agent.getNetwork().getNetId(),
                intToUidRangeStableParcels(uids),
                preferenceOrder);
        if (SdkLevel.isAtLeastT()) {
            inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids200Parcel);
        }

        uids.add(300);
        uids.add(400);
        nc.setAllowedUids(uids);
        agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
        if (SdkLevel.isAtLeastT()) {
            cb.expectCapabilitiesThat(agent, caps -> caps.getAllowedUids().equals(uids));
        } else {
            cb.assertNoCallback();
        }

        uids.remove(200);
        final NativeUidRangeConfig uids300400Parcel = new NativeUidRangeConfig(
                agent.getNetwork().getNetId(),
                intToUidRangeStableParcels(uids),
                preferenceOrder);
        if (SdkLevel.isAtLeastT()) {
            inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids300400Parcel);
        }

        nc.setAllowedUids(uids);
        agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
        if (SdkLevel.isAtLeastT()) {
            cb.expectCapabilitiesThat(agent, caps -> caps.getAllowedUids().equals(uids));
            inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids200Parcel);
        } else {
            cb.assertNoCallback();
        }

        uids.clear();
        uids.add(600);
        nc.setAllowedUids(uids);
        agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
        if (SdkLevel.isAtLeastT()) {
            cb.expectCapabilitiesThat(agent, caps -> caps.getAllowedUids().equals(uids));
        } else {
            cb.assertNoCallback();
        }
        final NativeUidRangeConfig uids600Parcel = new NativeUidRangeConfig(
                agent.getNetwork().getNetId(),
                intToUidRangeStableParcels(uids),
                preferenceOrder);
        if (SdkLevel.isAtLeastT()) {
            inOrder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(uids600Parcel);
            inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids300400Parcel);
        }

        uids.clear();
        nc.setAllowedUids(uids);
        agent.setNetworkCapabilities(nc, true /* sendToConnectivityService */);
        if (SdkLevel.isAtLeastT()) {
            cb.expectCapabilitiesThat(agent, caps -> caps.getAllowedUids().isEmpty());
            inOrder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(uids600Parcel);
        } else {
            cb.assertNoCallback();
            verify(mMockNetd, never()).networkAddUidRangesParcel(any());
            verify(mMockNetd, never()).networkRemoveUidRangesParcel(any());
        }

    }

    @Test
    public void testAutomotiveEthernetAllowedUids() throws Exception {
        mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
        mServiceContext.setPermission(MANAGE_TEST_NETWORKS, PERMISSION_GRANTED);

        // In this test the automotive feature will be enabled.
        mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, true);

        // Simulate a restricted ethernet network.
        final NetworkCapabilities.Builder agentNetCaps = new NetworkCapabilities.Builder()
                .addTransportType(TRANSPORT_ETHERNET)
                .addCapability(NET_CAPABILITY_INTERNET)
                .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
                .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
                .removeCapability(NET_CAPABILITY_NOT_RESTRICTED);

        mEthernetNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_ETHERNET,
                new LinkProperties(), agentNetCaps.build());
        validateAllowedUids(mEthernetNetworkAgent, TRANSPORT_ETHERNET, agentNetCaps, true);
    }

    @Test
    public void testCbsAllowedUids() throws Exception {
        mServiceContext.setPermission(NETWORK_FACTORY, PERMISSION_GRANTED);
        mServiceContext.setPermission(MANAGE_TEST_NETWORKS, PERMISSION_GRANTED);

        // In this test TEST_PACKAGE_UID will be the UID of the carrier service UID.
        doReturn(true).when(mCarrierPrivilegeAuthenticator)
                .hasCarrierPrivilegeForNetworkCapabilities(eq(TEST_PACKAGE_UID), any());

        // Simulate a restricted telephony network. The telephony factory is entitled to set
        // the access UID to the service package on any of its restricted networks.
        final NetworkCapabilities.Builder agentNetCaps = new NetworkCapabilities.Builder()
                .addTransportType(TRANSPORT_CELLULAR)
                .addCapability(NET_CAPABILITY_INTERNET)
                .addCapability(NET_CAPABILITY_NOT_SUSPENDED)
                .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED)
                .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
                .setNetworkSpecifier(new TelephonyNetworkSpecifier(1 /* subid */));

        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR,
                new LinkProperties(), agentNetCaps.build());
        validateAllowedUids(mCellNetworkAgent, TRANSPORT_CELLULAR, agentNetCaps, false);
    }

    private void validateAllowedUids(final TestNetworkAgentWrapper testAgent,
            @NetworkCapabilities.Transport final int transportUnderTest,
            final NetworkCapabilities.Builder ncb, final boolean forAutomotive) throws Exception {
        final ArraySet<Integer> serviceUidSet = new ArraySet<>();
        serviceUidSet.add(TEST_PACKAGE_UID);
        final ArraySet<Integer> nonServiceUidSet = new ArraySet<>();
        nonServiceUidSet.add(TEST_PACKAGE_UID2);
        final ArraySet<Integer> serviceUidSetPlus = new ArraySet<>();
        serviceUidSetPlus.add(TEST_PACKAGE_UID);
        serviceUidSetPlus.add(TEST_PACKAGE_UID2);

        final TestNetworkCallback cb = new TestNetworkCallback();

        /* Test setting UIDs */
        // Cell gets to set the service UID as access UID
        mCm.requestNetwork(new NetworkRequest.Builder()
                .addTransportType(transportUnderTest)
                .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
                .build(), cb);
        testAgent.connect(true);
        cb.expectAvailableThenValidatedCallbacks(testAgent);
        ncb.setAllowedUids(serviceUidSet);
        testAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
        if (SdkLevel.isAtLeastT()) {
            cb.expectCapabilitiesThat(testAgent,
                    caps -> caps.getAllowedUids().equals(serviceUidSet));
        } else {
            // S must ignore access UIDs.
            cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
        }

        /* Test setting UIDs is rejected when expected */
        if (forAutomotive) {
            mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, false);
        }

        // ...but not to some other UID. Rejection sets UIDs to the empty set
        ncb.setAllowedUids(nonServiceUidSet);
        testAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
        if (SdkLevel.isAtLeastT()) {
            cb.expectCapabilitiesThat(testAgent,
                    caps -> caps.getAllowedUids().isEmpty());
        } else {
            // S must ignore access UIDs.
            cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
        }

        // ...and also not to multiple UIDs even including the service UID
        ncb.setAllowedUids(serviceUidSetPlus);
        testAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
        cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);

        testAgent.disconnect();
        cb.expectCallback(CallbackEntry.LOST, testAgent);
        mCm.unregisterNetworkCallback(cb);

        // Must be unset before touching the transports, because remove and add transport types
        // check the specifier on the builder immediately, contradicting normal builder semantics
        // TODO : fix the builder
        ncb.setNetworkSpecifier(null);
        ncb.removeTransportType(transportUnderTest);
        ncb.addTransportType(TRANSPORT_WIFI);
        // Wifi does not get to set access UID, even to the correct UID
        mCm.requestNetwork(new NetworkRequest.Builder()
                .addTransportType(TRANSPORT_WIFI)
                .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
                .build(), cb);
        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI,
                new LinkProperties(), ncb.build());
        mWiFiNetworkAgent.connect(true);
        cb.expectAvailableThenValidatedCallbacks(mWiFiNetworkAgent);
        ncb.setAllowedUids(serviceUidSet);
        mWiFiNetworkAgent.setNetworkCapabilities(ncb.build(), true /* sendToCS */);
        cb.assertNoCallback(TEST_CALLBACK_TIMEOUT_MS);
        mCm.unregisterNetworkCallback(cb);
    }

    /**
     * Validate request counts are counted accurately on setProfileNetworkPreference on set/replace.
     */
    @Test
    public void testProfileNetworkPrefCountsRequestsCorrectlyOnSet() throws Exception {
        final UserHandle testHandle = setupEnterpriseNetwork();
        final TestOnCompleteListener listener = new TestOnCompleteListener();
        // Leave one request available so the profile preference can be set.
        testRequestCountLimits(1 /* countToLeaveAvailable */, () -> {
            withPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
                    Process.myPid(), Process.myUid(), () -> {
                        // Set initially to test the limit prior to having existing requests.
                        mCm.setProfileNetworkPreference(testHandle,
                                PROFILE_NETWORK_PREFERENCE_ENTERPRISE,
                                Runnable::run, listener);
                    });
            listener.expectOnComplete();

            // Simulate filing requests as some app on the work profile
            final int otherAppUid = UserHandle.getUid(TEST_WORK_PROFILE_USER_ID,
                    UserHandle.getAppId(Process.myUid() + 1));
            final int remainingCount = ConnectivityService.MAX_NETWORK_REQUESTS_PER_UID
                    - mService.mNetworkRequestCounter.mUidToNetworkRequestCount.get(otherAppUid)
                    - 1;
            final NetworkCallback[] callbacks = new NetworkCallback[remainingCount];
            doAsUid(otherAppUid, () -> {
                for (int i = 0; i < remainingCount; ++i) {
                    callbacks[i] = new TestableNetworkCallback();
                    mCm.registerDefaultNetworkCallback(callbacks[i]);
                }
            });

            withPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
                    Process.myPid(), Process.myUid(), () -> {
                        // re-set so as to test the limit as part of replacing existing requests.
                        mCm.setProfileNetworkPreference(testHandle,
                                PROFILE_NETWORK_PREFERENCE_ENTERPRISE, Runnable::run, listener);
                    });
            listener.expectOnComplete();

            doAsUid(otherAppUid, () -> {
                for (final NetworkCallback callback : callbacks) {
                    mCm.unregisterNetworkCallback(callback);
                }
            });
        });
    }

    /**
     * Validate request counts are counted accurately on setOemNetworkPreference on set/replace.
     */
    @Test
    public void testSetOemNetworkPreferenceCountsRequestsCorrectlyOnSet() throws Exception {
        mockHasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, true);
        @OemNetworkPreferences.OemNetworkPreference final int networkPref =
                OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
        // Leave one request available so the OEM preference can be set.
        testRequestCountLimits(1 /* countToLeaveAvailable */, () ->
                withPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, () -> {
                    // Set initially to test the limit prior to having existing requests.
                    final TestOemListenerCallback listener = new TestOemListenerCallback();
                    mService.setOemNetworkPreference(
                            createDefaultOemNetworkPreferences(networkPref), listener);
                    listener.expectOnComplete();

                    // re-set so as to test the limit as part of replacing existing requests.
                    mService.setOemNetworkPreference(
                            createDefaultOemNetworkPreferences(networkPref), listener);
                    listener.expectOnComplete();
                }));
    }

    private void testRequestCountLimits(final int countToLeaveAvailable,
            @NonNull final ExceptionalRunnable r) throws Exception {
        final ArraySet<TestNetworkCallback> callbacks = new ArraySet<>();
        try {
            final int requestCount = mService.mSystemNetworkRequestCounter
                    .mUidToNetworkRequestCount.get(Process.myUid());
            // The limit is hit when total requests = limit - 1, and exceeded with a crash when
            // total requests >= limit.
            final int countToFile =
                    MAX_NETWORK_REQUESTS_PER_SYSTEM_UID - requestCount - countToLeaveAvailable;
            // Need permission so registerDefaultNetworkCallback uses mSystemNetworkRequestCounter
            withPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, () -> {
                for (int i = 1; i < countToFile; i++) {
                    final TestNetworkCallback cb = new TestNetworkCallback();
                    mCm.registerDefaultNetworkCallback(cb);
                    callbacks.add(cb);
                }
                assertEquals(MAX_NETWORK_REQUESTS_PER_SYSTEM_UID - 1 - countToLeaveAvailable,
                        mService.mSystemNetworkRequestCounter
                              .mUidToNetworkRequestCount.get(Process.myUid()));
            });
            // Code to run to check if it triggers a max request count limit error.
            r.run();
        } finally {
            for (final TestNetworkCallback cb : callbacks) {
                mCm.unregisterNetworkCallback(cb);
            }
        }
    }

    private void assertCreateNrisFromMobileDataPreferredUids(Set<Integer> uids) {
        final Set<NetworkRequestInfo> nris =
                mService.createNrisFromMobileDataPreferredUids(uids);
        final NetworkRequestInfo nri = nris.iterator().next();
        // Verify that one NRI is created with multilayer requests. Because one NRI can contain
        // multiple uid ranges, so it only need create one NRI here.
        assertEquals(1, nris.size());
        assertTrue(nri.isMultilayerRequest());
        assertEquals(nri.getUids(), uidRangesForUids(uids));
        assertEquals(PREFERENCE_ORDER_MOBILE_DATA_PREFERERRED, nri.mPreferenceOrder);
    }

    /**
     * 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_ORDER_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_ORDER_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_ORDER_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_ORDER_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_ORDER_MOBILE_DATA_PREFERERRED);
        inorder.verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical(
                cellNetId2, INetd.PERMISSION_NONE));
        inorder.verify(mMockNetd, times(1)).networkAddUidRangesParcel(cellConfig2);
        inorder.verify(mMockNetd, times(1)).networkRemoveUidRangesParcel(wifiConfig);

        // Wifi network disconnected. mTestPackageDefaultNetworkCallback should not receive
        // any callback.
        mWiFiNetworkAgent.disconnect();
        mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
        mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
        mTestPackageDefaultNetworkCallback.assertNoCallback();
        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));
        waitForIdle();
        inorder.verify(mMockNetd, never()).networkAddUidRangesParcel(any());
        inorder.verify(mMockNetd, never()).networkRemoveUidRangesParcel(any());
        inorder.verify(mMockNetd).networkDestroy(wifiNetId);

        mCm.unregisterNetworkCallback(cellNetworkCallback);
    }

    @Test
    public void testMultilayerRequestsOfSetMobileDataPreferredUids() throws Exception {
        // First set mobile data preferred uid to create a multi-layer requests: 1. request for
        // cellular, 2. track the default network for fallback.
        setAndUpdateMobileDataPreferredUids(
                Set.of(PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)));

        final HandlerThread handlerThread = new HandlerThread("MockFactory");
        handlerThread.start();
        final NetworkCapabilities cellFilter = new NetworkCapabilities()
                .addTransportType(TRANSPORT_CELLULAR)
                .addCapability(NET_CAPABILITY_INTERNET)
                .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED);
        final MockNetworkFactory cellFactory = new MockNetworkFactory(handlerThread.getLooper(),
                mServiceContext, "cellFactory", cellFilter, mCsHandlerThread);
        cellFactory.setScoreFilter(40);

        try {
            cellFactory.register();
            // Default internet request and the mobile data preferred request.
            cellFactory.expectRequestAdds(2);
            cellFactory.assertRequestCountEquals(2);

            mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI);
            mWiFiNetworkAgent.connect(true);

            // The cellFactory however is outscored, and should lose default internet request.
            // But it should still see mobile data preferred request.
            cellFactory.expectRequestRemove();
            cellFactory.assertRequestCountEquals(1);

            mWiFiNetworkAgent.disconnect();
            // The network satisfying the default internet request has disconnected, so the
            // cellFactory sees the default internet requests again.
            cellFactory.expectRequestAdd();
            cellFactory.assertRequestCountEquals(2);
        } finally {
            cellFactory.terminate();
            handlerThread.quitSafely();
        }
    }

    /**
     * Validate request counts are counted accurately on MOBILE_DATA_PREFERRED_UIDS change
     * on set/replace.
     */
    @Test
    public void testMobileDataPreferredUidsChangedCountsRequestsCorrectlyOnSet() throws Exception {
        ConnectivitySettingsManager.setMobileDataPreferredUids(mServiceContext,
                Set.of(PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID)));
        // Leave one request available so MDO preference set up above can be set.
        testRequestCountLimits(1 /* countToLeaveAvailable */, () ->
                withPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK,
                        Process.myPid(), Process.myUid(), () -> {
                            // Set initially to test the limit prior to having existing requests.
                            mService.updateMobileDataPreferredUids();
                            waitForIdle();

                            // re-set so as to test the limit as part of replacing existing requests
                            mService.updateMobileDataPreferredUids();
                            waitForIdle();
                        }));
    }

    @Test
    public void testAllNetworkPreferencesCanCoexist()
            throws Exception {
        final InOrder inorder = inOrder(mMockNetd);
        @OemNetworkPreferences.OemNetworkPreference final int networkPref =
                OEM_NETWORK_PREFERENCE_OEM_PAID;
        final UserHandle testHandle = setupEnterpriseNetwork();

        setOemNetworkPreferenceAgentConnected(TRANSPORT_CELLULAR, true);
        final int cellNetId = mCellNetworkAgent.getNetwork().netId;
        inorder.verify(mMockNetd, times(1)).networkCreate(nativeNetworkConfigPhysical(
                cellNetId, INetd.PERMISSION_NONE));

        // Set oem network preference
        final int[] uids1 = new int[] { PRIMARY_USER_HANDLE.getUid(TEST_PACKAGE_UID) };
        final UidRangeParcel[] uidRanges1 = toUidRangeStableParcels(uidRangesForUids(uids1));
        final NativeUidRangeConfig config1 = new NativeUidRangeConfig(cellNetId, uidRanges1,
                PREFERENCE_ORDER_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_ORDER_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_ORDER_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_ORDER_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_ORDER_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));
        doReturn(Arrays.asList(testHandle)).when(mUserManager).getUserHandles(anyBoolean());
        setupSetOemNetworkPreferenceForPreferenceTest(
                networkPref, uidRanges2, "com.android.test", testHandle);
        mDefaultNetworkCallback.assertNoCallback();
        mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
        mTestPackageDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
        assertEquals(mWiFiNetworkAgent.getNetwork(),
                mCm.getActiveNetworkForUid(TEST_WORK_PROFILE_APP_UID));
        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));

        // Remove oem network preference, mProfileDefaultNetworkCallback should receive callback
        // with current highest priority network preference (enterprise network) and the others
        // should have no callbacks.
        final TestOemListenerCallback oemPrefListener = new TestOemListenerCallback();
        mService.setOemNetworkPreference(
                new OemNetworkPreferences.Builder().build(), oemPrefListener);
        oemPrefListener.expectOnComplete();
        assertNoCallbacks(mDefaultNetworkCallback, mTestPackageDefaultNetworkCallback);
        mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(workAgent);
        assertEquals(workAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_WORK_PROFILE_APP_UID));
        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));

        // Remove user profile network preference.
        mCm.setProfileNetworkPreference(testHandle, PROFILE_NETWORK_PREFERENCE_DEFAULT,
                r -> r.run(), listener);
        listener.expectOnComplete();
        assertNoCallbacks(mDefaultNetworkCallback, mTestPackageDefaultNetworkCallback);
        mProfileDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
        assertEquals(mCellNetworkAgent.getNetwork(),
                mCm.getActiveNetworkForUid(TEST_WORK_PROFILE_APP_UID));
        assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetworkForUid(TEST_PACKAGE_UID));

        // Disconnect wifi
        mWiFiNetworkAgent.disconnect();
        assertNoCallbacks(mProfileDefaultNetworkCallback, mTestPackageDefaultNetworkCallback);
        mDefaultNetworkCallback.expectCallback(CallbackEntry.LOST, mWiFiNetworkAgent);
        mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
    }

    @Test
    public void testRequestRouteToHostAddress_PackageDoesNotBelongToCaller() {
        assertThrows(SecurityException.class, () -> mService.requestRouteToHostAddress(
                ConnectivityManager.TYPE_NONE, null /* hostAddress */, "com.not.package.owner",
                null /* callingAttributionTag */));
    }

    @Test @IgnoreUpTo(SC_V2)
    public void testUpdateRateLimit_EnableDisable() throws Exception {
        final LinkProperties wifiLp = new LinkProperties();
        wifiLp.setInterfaceName(WIFI_IFNAME);
        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
        mWiFiNetworkAgent.connect(true);

        final LinkProperties cellLp = new LinkProperties();
        cellLp.setInterfaceName(MOBILE_IFNAME);
        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
        mCellNetworkAgent.connect(false);

        waitForIdle();

        final ArrayTrackRecord<Pair<String, Long>>.ReadHead readHeadWifi =
                mDeps.mRateLimitHistory.newReadHead();
        final ArrayTrackRecord<Pair<String, Long>>.ReadHead readHeadCell =
                mDeps.mRateLimitHistory.newReadHead();

        // set rate limit to 8MBit/s => 1MB/s
        final int rateLimitInBytesPerSec = 1 * 1000 * 1000;
        setIngressRateLimit(rateLimitInBytesPerSec);

        assertNotNull(readHeadWifi.poll(TIMEOUT_MS,
                it -> it.first == wifiLp.getInterfaceName()
                        && it.second == rateLimitInBytesPerSec));
        assertNotNull(readHeadCell.poll(TIMEOUT_MS,
                it -> it.first == cellLp.getInterfaceName()
                        && it.second == rateLimitInBytesPerSec));

        // disable rate limiting
        setIngressRateLimit(-1);

        assertNotNull(readHeadWifi.poll(TIMEOUT_MS,
                it -> it.first == wifiLp.getInterfaceName() && it.second == -1));
        assertNotNull(readHeadCell.poll(TIMEOUT_MS,
                it -> it.first == cellLp.getInterfaceName() && it.second == -1));
    }

    @Test @IgnoreUpTo(SC_V2)
    public void testUpdateRateLimit_WhenNewNetworkIsAdded() throws Exception {
        final LinkProperties wifiLp = new LinkProperties();
        wifiLp.setInterfaceName(WIFI_IFNAME);
        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
        mWiFiNetworkAgent.connect(true);

        waitForIdle();

        final ArrayTrackRecord<Pair<String, Long>>.ReadHead readHead =
                mDeps.mRateLimitHistory.newReadHead();

        // set rate limit to 8MBit/s => 1MB/s
        final int rateLimitInBytesPerSec = 1 * 1000 * 1000;
        setIngressRateLimit(rateLimitInBytesPerSec);
        assertNotNull(readHead.poll(TIMEOUT_MS, it -> it.first == wifiLp.getInterfaceName()
                && it.second == rateLimitInBytesPerSec));

        final LinkProperties cellLp = new LinkProperties();
        cellLp.setInterfaceName(MOBILE_IFNAME);
        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR, cellLp);
        mCellNetworkAgent.connect(false);
        assertNotNull(readHead.poll(TIMEOUT_MS, it -> it.first == cellLp.getInterfaceName()
                && it.second == rateLimitInBytesPerSec));
    }

    @Test @IgnoreUpTo(SC_V2)
    public void testUpdateRateLimit_OnlyAffectsInternetCapableNetworks() throws Exception {
        final LinkProperties wifiLp = new LinkProperties();
        wifiLp.setInterfaceName(WIFI_IFNAME);

        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
        mWiFiNetworkAgent.connectWithoutInternet();

        waitForIdle();

        setIngressRateLimit(1000);
        setIngressRateLimit(-1);

        final ArrayTrackRecord<Pair<String, Long>>.ReadHead readHeadWifi =
                mDeps.mRateLimitHistory.newReadHead();
        assertNull(readHeadWifi.poll(TIMEOUT_MS, it -> it.first == wifiLp.getInterfaceName()));
    }

    @Test @IgnoreUpTo(SC_V2)
    public void testUpdateRateLimit_DisconnectingResetsRateLimit()
            throws Exception {
        // Steps:
        // - connect network
        // - set rate limit
        // - disconnect network (interface still exists)
        // - disable rate limit
        // - connect network
        // - ensure network interface is not rate limited
        final LinkProperties wifiLp = new LinkProperties();
        wifiLp.setInterfaceName(WIFI_IFNAME);
        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
        mWiFiNetworkAgent.connect(true);
        waitForIdle();

        final ArrayTrackRecord<Pair<String, Long>>.ReadHead readHeadWifi =
                mDeps.mRateLimitHistory.newReadHead();

        int rateLimitInBytesPerSec = 1000;
        setIngressRateLimit(rateLimitInBytesPerSec);
        assertNotNull(readHeadWifi.poll(TIMEOUT_MS,
                it -> it.first == wifiLp.getInterfaceName()
                        && it.second == rateLimitInBytesPerSec));

        mWiFiNetworkAgent.disconnect();
        assertNotNull(readHeadWifi.poll(TIMEOUT_MS,
                it -> it.first == wifiLp.getInterfaceName() && it.second == -1));

        setIngressRateLimit(-1);

        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
        mWiFiNetworkAgent.connect(true);
        assertNull(readHeadWifi.poll(TIMEOUT_MS, it -> it.first == wifiLp.getInterfaceName()));
    }

    @Test @IgnoreUpTo(SC_V2)
    public void testUpdateRateLimit_UpdateExistingRateLimit() throws Exception {
        final LinkProperties wifiLp = new LinkProperties();
        wifiLp.setInterfaceName(WIFI_IFNAME);
        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
        mWiFiNetworkAgent.connect(true);
        waitForIdle();

        final ArrayTrackRecord<Pair<String, Long>>.ReadHead readHeadWifi =
                mDeps.mRateLimitHistory.newReadHead();

        // update an active ingress rate limit
        setIngressRateLimit(1000);
        setIngressRateLimit(2000);

        // verify the following order of execution:
        // 1. ingress rate limit set to 1000.
        // 2. ingress rate limit disabled (triggered by updating active rate limit).
        // 3. ingress rate limit set to 2000.
        assertNotNull(readHeadWifi.poll(TIMEOUT_MS,
                it -> it.first == wifiLp.getInterfaceName()
                        && it.second == 1000));
        assertNotNull(readHeadWifi.poll(TIMEOUT_MS,
                it -> it.first == wifiLp.getInterfaceName()
                        && it.second == -1));
        assertNotNull(readHeadWifi.poll(TIMEOUT_MS,
                it -> it.first == wifiLp.getInterfaceName()
                        && it.second == 2000));
    }

    @Test @IgnoreAfter(SC_V2)
    public void testUpdateRateLimit_DoesNothingBeforeT() throws Exception {
        final LinkProperties wifiLp = new LinkProperties();
        wifiLp.setInterfaceName(WIFI_IFNAME);
        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp);
        mWiFiNetworkAgent.connect(true);
        waitForIdle();

        final ArrayTrackRecord<Pair<String, Long>>.ReadHead readHead =
                mDeps.mRateLimitHistory.newReadHead();

        setIngressRateLimit(1000);
        waitForIdle();

        assertNull(readHead.poll(TEST_CALLBACK_TIMEOUT_MS, it -> true));
    }

    @Test
    public void testIgnoreValidationAfterRoamDisabled() throws Exception {
        assumeFalse(SdkLevel.isAtLeastT());
        // testIgnoreValidationAfterRoam off
        doReturn(-1).when(mResources)
                .getInteger(R.integer.config_validationFailureAfterRoamIgnoreTimeMillis);

        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
        mCellNetworkAgent.connect(true);
        NetworkCapabilities wifiNc1 = new NetworkCapabilities()
                .addTransportType(TRANSPORT_WIFI)
                .setTransportInfo(new WifiInfo.Builder().setBssid("AA:AA:AA:AA:AA:AA").build());
        NetworkCapabilities wifiNc2 = new NetworkCapabilities()
                .addTransportType(TRANSPORT_WIFI)
                .setTransportInfo(new WifiInfo.Builder().setBssid("BB:BB:BB:BB:BB:BB").build());
        final LinkProperties wifiLp = new LinkProperties();
        wifiLp.setInterfaceName(WIFI_IFNAME);
        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp, wifiNc1);
        mWiFiNetworkAgent.connect(true);

        // The default network will be switching to Wi-Fi Network.
        final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback();
        final NetworkRequest wifiRequest = new NetworkRequest.Builder()
                .addTransportType(TRANSPORT_WIFI).build();
        mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback);
        wifiNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
        registerDefaultNetworkCallbacks();
        mDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);

        // Wi-Fi roaming from wifiNc1 to wifiNc2.
        mWiFiNetworkAgent.setNetworkCapabilities(wifiNc2, true);
        mWiFiNetworkAgent.setNetworkInvalid(false);
        mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
        mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
    }

    @Test
    public void testIgnoreValidationAfterRoamEnabled() throws Exception {
        assumeFalse(SdkLevel.isAtLeastT());
        // testIgnoreValidationAfterRoam on
        doReturn(5000).when(mResources)
                .getInteger(R.integer.config_validationFailureAfterRoamIgnoreTimeMillis);

        mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR);
        mCellNetworkAgent.connect(true);
        NetworkCapabilities wifiNc1 = new NetworkCapabilities()
                .addTransportType(TRANSPORT_WIFI)
                .setTransportInfo(new WifiInfo.Builder().setBssid("AA:AA:AA:AA:AA:AA").build());
        NetworkCapabilities wifiNc2 = new NetworkCapabilities()
                .addTransportType(TRANSPORT_WIFI)
                .setTransportInfo(new WifiInfo.Builder().setBssid("BB:BB:BB:BB:BB:BB").build());
        final LinkProperties wifiLp = new LinkProperties();
        wifiLp.setInterfaceName(WIFI_IFNAME);
        mWiFiNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_WIFI, wifiLp, wifiNc1);
        mWiFiNetworkAgent.connect(true);

        // The default network will be switching to Wi-Fi Network.
        final TestNetworkCallback wifiNetworkCallback = new TestNetworkCallback();
        final NetworkRequest wifiRequest = new NetworkRequest.Builder()
                .addTransportType(TRANSPORT_WIFI).build();
        mCm.registerNetworkCallback(wifiRequest, wifiNetworkCallback);
        wifiNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
        registerDefaultNetworkCallbacks();
        mDefaultNetworkCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);

        // Wi-Fi roaming from wifiNc1 to wifiNc2.
        mWiFiNetworkAgent.setNetworkCapabilities(wifiNc2, true);
        mWiFiNetworkAgent.setNetworkInvalid(false);
        mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);

        // Network validation failed, but the result will be ignored.
        assertTrue(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork()).hasCapability(
                NET_CAPABILITY_VALIDATED));
        mWiFiNetworkAgent.setNetworkValid(false);

        // Behavior of after config_validationFailureAfterRoamIgnoreTimeMillis
        ConditionVariable waitForValidationBlock = new ConditionVariable();
        doReturn(50).when(mResources)
                .getInteger(R.integer.config_validationFailureAfterRoamIgnoreTimeMillis);
        // Wi-Fi roaming from wifiNc2 to wifiNc1.
        mWiFiNetworkAgent.setNetworkCapabilities(wifiNc1, true);
        mWiFiNetworkAgent.setNetworkInvalid(false);
        waitForValidationBlock.block(150);
        mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false);
        mDefaultNetworkCallback.expectAvailableCallbacksValidated(mCellNetworkAgent);
    }
}
