[automerger skipped] Merge "ClatCoordinator: dump BPF forwarding rules" into tm-dev am: b2db0b5119 -s ours
am skip reason: Merged-In I597709663477b62005b0bc5cc1bf0fc22743e10b with SHA-1 707675511a is already in history
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/modules/Connectivity/+/17988444
Change-Id: I2e96d8e3c9d8335d9fbe7f00d6c8867b200cdce9
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp
index d79edb4..9076dca 100644
--- a/Tethering/apex/Android.bp
+++ b/Tethering/apex/Android.bp
@@ -70,9 +70,9 @@
canned_fs_config: "canned_fs_config",
bpfs: [
"block.o",
- "clatd.o_mainline",
+ "clatd.o",
"dscp_policy.o",
- "netd.o_mainline",
+ "netd.o",
"offload.o",
"test.o",
],
diff --git a/bpf_progs/Android.bp b/bpf_progs/Android.bp
index 1fe0e9a..0e7b22d 100644
--- a/bpf_progs/Android.bp
+++ b/bpf_progs/Android.bp
@@ -97,7 +97,7 @@
}
bpf {
- name: "clatd.o_mainline",
+ name: "clatd.o",
srcs: ["clatd.c"],
cflags: [
"-Wall",
@@ -110,7 +110,7 @@
}
bpf {
- name: "netd.o_mainline",
+ name: "netd.o",
srcs: ["netd.c"],
cflags: [
"-Wall",
diff --git a/bpf_progs/bpf_tethering.h b/bpf_progs/bpf_tethering.h
index b0ec8f6..f9ef6ef 100644
--- a/bpf_progs/bpf_tethering.h
+++ b/bpf_progs/bpf_tethering.h
@@ -73,10 +73,6 @@
#define STRUCT_SIZE(name, size) _Static_assert(sizeof(name) == (size), "Incorrect struct size.")
-#define BPF_PATH_TETHER BPF_PATH "tethering/"
-
-#define TETHER_STATS_MAP_PATH BPF_PATH_TETHER "map_offload_tether_stats_map"
-
typedef uint32_t TetherStatsKey; // upstream ifindex
typedef struct {
@@ -89,19 +85,9 @@
} TetherStatsValue;
STRUCT_SIZE(TetherStatsValue, 6 * 8); // 48
-#define TETHER_LIMIT_MAP_PATH BPF_PATH_TETHER "map_offload_tether_limit_map"
-
typedef uint32_t TetherLimitKey; // upstream ifindex
typedef uint64_t TetherLimitValue; // in bytes
-#define TETHER_DOWNSTREAM6_TC_PROG_RAWIP_NAME "prog_offload_schedcls_tether_downstream6_rawip"
-#define TETHER_DOWNSTREAM6_TC_PROG_ETHER_NAME "prog_offload_schedcls_tether_downstream6_ether"
-
-#define TETHER_DOWNSTREAM6_TC_PROG_RAWIP_PATH BPF_PATH_TETHER TETHER_DOWNSTREAM6_TC_PROG_RAWIP_NAME
-#define TETHER_DOWNSTREAM6_TC_PROG_ETHER_PATH BPF_PATH_TETHER TETHER_DOWNSTREAM6_TC_PROG_ETHER_NAME
-
-#define TETHER_DOWNSTREAM6_MAP_PATH BPF_PATH_TETHER "map_offload_tether_downstream6_map"
-
// For now tethering offload only needs to support downstreams that use 6-byte MAC addresses,
// because all downstream types that are currently supported (WiFi, USB, Bluetooth and
// Ethernet) have 6-byte MAC addresses.
@@ -121,8 +107,6 @@
} Tether6Value;
STRUCT_SIZE(Tether6Value, 4 + 14 + 2); // 20
-#define TETHER_DOWNSTREAM64_MAP_PATH BPF_PATH_TETHER "map_offload_tether_downstream64_map"
-
typedef struct {
uint32_t iif; // The input interface index
uint8_t dstMac[ETH_ALEN]; // destination ethernet mac address (zeroed iff rawip ingress)
@@ -146,14 +130,6 @@
} TetherDownstream64Value;
STRUCT_SIZE(TetherDownstream64Value, 4 + 14 + 2 + 4 + 4 + 2 + 2 + 8); // 40
-#define TETHER_UPSTREAM6_TC_PROG_RAWIP_NAME "prog_offload_schedcls_tether_upstream6_rawip"
-#define TETHER_UPSTREAM6_TC_PROG_ETHER_NAME "prog_offload_schedcls_tether_upstream6_ether"
-
-#define TETHER_UPSTREAM6_TC_PROG_RAWIP_PATH BPF_PATH_TETHER TETHER_UPSTREAM6_TC_PROG_RAWIP_NAME
-#define TETHER_UPSTREAM6_TC_PROG_ETHER_PATH BPF_PATH_TETHER TETHER_UPSTREAM6_TC_PROG_ETHER_NAME
-
-#define TETHER_UPSTREAM6_MAP_PATH BPF_PATH_TETHER "map_offload_tether_upstream6_map"
-
typedef struct {
uint32_t iif; // The input interface index
uint8_t dstMac[ETH_ALEN]; // destination ethernet mac address (zeroed iff rawip ingress)
@@ -162,23 +138,6 @@
} TetherUpstream6Key;
STRUCT_SIZE(TetherUpstream6Key, 12);
-#define TETHER_DOWNSTREAM4_TC_PROG_RAWIP_NAME "prog_offload_schedcls_tether_downstream4_rawip"
-#define TETHER_DOWNSTREAM4_TC_PROG_ETHER_NAME "prog_offload_schedcls_tether_downstream4_ether"
-
-#define TETHER_DOWNSTREAM4_TC_PROG_RAWIP_PATH BPF_PATH_TETHER TETHER_DOWNSTREAM4_TC_PROG_RAWIP_NAME
-#define TETHER_DOWNSTREAM4_TC_PROG_ETHER_PATH BPF_PATH_TETHER TETHER_DOWNSTREAM4_TC_PROG_ETHER_NAME
-
-#define TETHER_DOWNSTREAM4_MAP_PATH BPF_PATH_TETHER "map_offload_tether_downstream4_map"
-
-
-#define TETHER_UPSTREAM4_TC_PROG_RAWIP_NAME "prog_offload_schedcls_tether_upstream4_rawip"
-#define TETHER_UPSTREAM4_TC_PROG_ETHER_NAME "prog_offload_schedcls_tether_upstream4_ether"
-
-#define TETHER_UPSTREAM4_TC_PROG_RAWIP_PATH BPF_PATH_TETHER TETHER_UPSTREAM4_TC_PROG_RAWIP_NAME
-#define TETHER_UPSTREAM4_TC_PROG_ETHER_PATH BPF_PATH_TETHER TETHER_UPSTREAM4_TC_PROG_ETHER_NAME
-
-#define TETHER_UPSTREAM4_MAP_PATH BPF_PATH_TETHER "map_offload_tether_upstream4_map"
-
typedef struct {
uint32_t iif; // The input interface index
uint8_t dstMac[ETH_ALEN]; // destination ethernet mac address (zeroed iff rawip ingress)
@@ -202,16 +161,4 @@
} Tether4Value;
STRUCT_SIZE(Tether4Value, 4 + 14 + 2 + 16 + 16 + 2 + 2 + 8); // 64
-#define TETHER_DOWNSTREAM_XDP_PROG_RAWIP_NAME "prog_offload_xdp_tether_downstream_rawip"
-#define TETHER_DOWNSTREAM_XDP_PROG_ETHER_NAME "prog_offload_xdp_tether_downstream_ether"
-
-#define TETHER_DOWNSTREAM_XDP_PROG_RAWIP_PATH BPF_PATH_TETHER TETHER_DOWNSTREAM_XDP_PROG_RAWIP_NAME
-#define TETHER_DOWNSTREAM_XDP_PROG_ETHER_PATH BPF_PATH_TETHER TETHER_DOWNSTREAM_XDP_PROG_ETHER_NAME
-
-#define TETHER_UPSTREAM_XDP_PROG_RAWIP_NAME "prog_offload_xdp_tether_upstream_rawip"
-#define TETHER_UPSTREAM_XDP_PROG_ETHER_NAME "prog_offload_xdp_tether_upstream_ether"
-
-#define TETHER_UPSTREAM_XDP_PROG_RAWIP_PATH BPF_PATH_TETHER TETHER_UPSTREAM_XDP_PROG_RAWIP_NAME
-#define TETHER_UPSTREAM_XDP_PROG_ETHER_PATH BPF_PATH_TETHER TETHER_UPSTREAM_XDP_PROG_ETHER_NAME
-
#undef STRUCT_SIZE
diff --git a/framework-t/src/android/net/EthernetManager.java b/framework-t/src/android/net/EthernetManager.java
index 2b76dd9..886d194 100644
--- a/framework-t/src/android/net/EthernetManager.java
+++ b/framework-t/src/android/net/EthernetManager.java
@@ -32,13 +32,13 @@
import android.os.Build;
import android.os.OutcomeReceiver;
import android.os.RemoteException;
+import android.util.ArrayMap;
import com.android.internal.annotations.GuardedBy;
import com.android.modules.utils.BackgroundThread;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -56,37 +56,12 @@
private final IEthernetManager mService;
@GuardedBy("mListenerLock")
- private final ArrayList<ListenerInfo<InterfaceStateListener>> mIfaceListeners =
- new ArrayList<>();
+ private final ArrayMap<InterfaceStateListener, IEthernetServiceListener>
+ mIfaceServiceListeners = new ArrayMap<>();
@GuardedBy("mListenerLock")
- private final ArrayList<ListenerInfo<IntConsumer>> mEthernetStateListeners =
- new ArrayList<>();
+ private final ArrayMap<IntConsumer, IEthernetServiceListener> mStateServiceListeners =
+ new ArrayMap<>();
final Object mListenerLock = new Object();
- private final IEthernetServiceListener.Stub mServiceListener =
- new IEthernetServiceListener.Stub() {
- @Override
- public void onEthernetStateChanged(int state) {
- synchronized (mListenerLock) {
- for (ListenerInfo<IntConsumer> li : mEthernetStateListeners) {
- li.executor.execute(() -> {
- li.listener.accept(state);
- });
- }
- }
- }
-
- @Override
- public void onInterfaceStateChanged(String iface, int state, int role,
- IpConfiguration configuration) {
- synchronized (mListenerLock) {
- for (ListenerInfo<InterfaceStateListener> li : mIfaceListeners) {
- li.executor.execute(() ->
- li.listener.onInterfaceStateChanged(iface, state, role,
- configuration));
- }
- }
- }
- };
/**
* Indicates that Ethernet is disabled.
@@ -104,18 +79,6 @@
@SystemApi(client = MODULE_LIBRARIES)
public static final int ETHERNET_STATE_ENABLED = 1;
- private static class ListenerInfo<T> {
- @NonNull
- public final Executor executor;
- @NonNull
- public final T listener;
-
- private ListenerInfo(@NonNull Executor executor, @NonNull T listener) {
- this.executor = executor;
- this.listener = listener;
- }
- }
-
/**
* The interface is absent.
* @hide
@@ -323,18 +286,28 @@
if (listener == null || executor == null) {
throw new NullPointerException("listener and executor must not be null");
}
+
+ final IEthernetServiceListener.Stub serviceListener = new IEthernetServiceListener.Stub() {
+ @Override
+ public void onEthernetStateChanged(int state) {}
+
+ @Override
+ public void onInterfaceStateChanged(String iface, int state, int role,
+ IpConfiguration configuration) {
+ executor.execute(() ->
+ listener.onInterfaceStateChanged(iface, state, role, configuration));
+ }
+ };
synchronized (mListenerLock) {
- maybeAddServiceListener();
- mIfaceListeners.add(new ListenerInfo<InterfaceStateListener>(executor, listener));
+ addServiceListener(serviceListener);
+ mIfaceServiceListeners.put(listener, serviceListener);
}
}
@GuardedBy("mListenerLock")
- private void maybeAddServiceListener() {
- if (!mIfaceListeners.isEmpty() || !mEthernetStateListeners.isEmpty()) return;
-
+ private void addServiceListener(@NonNull final IEthernetServiceListener listener) {
try {
- mService.addListener(mServiceListener);
+ mService.addListener(listener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -364,17 +337,16 @@
public void removeInterfaceStateListener(@NonNull InterfaceStateListener listener) {
Objects.requireNonNull(listener);
synchronized (mListenerLock) {
- mIfaceListeners.removeIf(l -> l.listener == listener);
- maybeRemoveServiceListener();
+ maybeRemoveServiceListener(mIfaceServiceListeners.remove(listener));
}
}
@GuardedBy("mListenerLock")
- private void maybeRemoveServiceListener() {
- if (!mIfaceListeners.isEmpty() || !mEthernetStateListeners.isEmpty()) return;
+ private void maybeRemoveServiceListener(@Nullable final IEthernetServiceListener listener) {
+ if (listener == null) return;
try {
- mService.removeListener(mServiceListener);
+ mService.removeListener(listener);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -687,9 +659,19 @@
@NonNull IntConsumer listener) {
Objects.requireNonNull(executor);
Objects.requireNonNull(listener);
+ final IEthernetServiceListener.Stub serviceListener = new IEthernetServiceListener.Stub() {
+ @Override
+ public void onEthernetStateChanged(int state) {
+ executor.execute(() -> listener.accept(state));
+ }
+
+ @Override
+ public void onInterfaceStateChanged(String iface, int state, int role,
+ IpConfiguration configuration) {}
+ };
synchronized (mListenerLock) {
- maybeAddServiceListener();
- mEthernetStateListeners.add(new ListenerInfo<IntConsumer>(executor, listener));
+ addServiceListener(serviceListener);
+ mStateServiceListeners.put(listener, serviceListener);
}
}
@@ -705,8 +687,7 @@
public void removeEthernetStateListener(@NonNull IntConsumer listener) {
Objects.requireNonNull(listener);
synchronized (mListenerLock) {
- mEthernetStateListeners.removeIf(l -> l.listener == listener);
- maybeRemoveServiceListener();
+ maybeRemoveServiceListener(mStateServiceListeners.remove(listener));
}
}
diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java
index a174fe3..d16a6f5 100644
--- a/framework/src/android/net/ConnectivityManager.java
+++ b/framework/src/android/net/ConnectivityManager.java
@@ -2589,9 +2589,24 @@
* {@hide}
*/
public ConnectivityManager(Context context, IConnectivityManager service) {
+ this(context, service, true /* newStatic */);
+ }
+
+ private ConnectivityManager(Context context, IConnectivityManager service, boolean newStatic) {
mContext = Objects.requireNonNull(context, "missing context");
mService = Objects.requireNonNull(service, "missing IConnectivityManager");
- sInstance = this;
+ // sInstance is accessed without a lock, so it may actually be reassigned several times with
+ // different ConnectivityManager, but that's still OK considering its usage.
+ if (sInstance == null && newStatic) {
+ final Context appContext = mContext.getApplicationContext();
+ // Don't create static ConnectivityManager instance again to prevent infinite loop.
+ // If the application context is null, we're either in the system process or
+ // it's the application context very early in app initialization. In both these
+ // cases, the passed-in Context will not be freed, so it's safe to pass it to the
+ // service. http://b/27532714 .
+ sInstance = new ConnectivityManager(appContext != null ? appContext : context, service,
+ false /* newStatic */);
+ }
}
/** {@hide} */
diff --git a/framework/src/android/net/LinkProperties.java b/framework/src/android/net/LinkProperties.java
index 8782b33..126a5ff 100644
--- a/framework/src/android/net/LinkProperties.java
+++ b/framework/src/android/net/LinkProperties.java
@@ -64,7 +64,7 @@
* @hide
*/
@ChangeId
- @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S) // Switch to S_V2 when it is available.
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.S_V2)
@VisibleForTesting
public static final long EXCLUDED_ROUTES = 186082280;
diff --git a/framework/src/android/net/NetworkAgent.java b/framework/src/android/net/NetworkAgent.java
index 29add1c..2c50c73 100644
--- a/framework/src/android/net/NetworkAgent.java
+++ b/framework/src/android/net/NetworkAgent.java
@@ -1076,11 +1076,12 @@
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public final void sendNetworkInfo(NetworkInfo networkInfo) {
- queueOrSendNetworkInfo(new NetworkInfo(networkInfo));
+ queueOrSendNetworkInfo(networkInfo);
}
private void queueOrSendNetworkInfo(NetworkInfo networkInfo) {
- queueOrSendMessage(reg -> reg.sendNetworkInfo(networkInfo));
+ final NetworkInfo ni = new NetworkInfo(networkInfo);
+ queueOrSendMessage(reg -> reg.sendNetworkInfo(ni));
}
/**
diff --git a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
index ba836b2..e2c5a63 100644
--- a/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
+++ b/service/jni/com_android_server_connectivity_ClatCoordinator.cpp
@@ -315,10 +315,7 @@
// TODO: use android::base::ScopeGuard.
if (int ret = posix_spawnattr_setflags(&attr, POSIX_SPAWN_USEVFORK
-#ifdef POSIX_SPAWN_CLOEXEC_DEFAULT
- | POSIX_SPAWN_CLOEXEC_DEFAULT
-#endif
- )) {
+ | POSIX_SPAWN_CLOEXEC_DEFAULT)) {
posix_spawnattr_destroy(&attr);
throwIOException(env, "posix_spawnattr_setflags failed", ret);
return -1;
diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java
index 6de6625..ae00a3a 100644
--- a/service/src/com/android/server/ConnectivityService.java
+++ b/service/src/com/android/server/ConnectivityService.java
@@ -108,6 +108,7 @@
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
import android.app.usage.NetworkStatsManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -10645,13 +10646,29 @@
mQosCallbackTracker.unregisterCallback(callback);
}
+ private boolean isNetworkPreferenceAllowedForProfile(@NonNull UserHandle profile) {
+ // UserManager.isManagedProfile returns true for all apps in managed user profiles.
+ // Enterprise device can be fully managed like device owner and such use case
+ // also should be supported. Calling app check for work profile and fully managed device
+ // is already done in DevicePolicyManager.
+ // This check is an extra caution to be sure device is fully managed or not.
+ final UserManager um = mContext.getSystemService(UserManager.class);
+ final DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+ if (um.isManagedProfile(profile.getIdentifier())) {
+ return true;
+ }
+ if (SdkLevel.isAtLeastT() && dpm.getDeviceOwner() != null) return true;
+ return false;
+ }
+
/**
- * Request that a user profile is put by default on a network matching a given preference.
+ * Set a list of default network selection policies for a user profile or device owner.
*
* See the documentation for the individual preferences for a description of the supported
* behaviors.
*
- * @param profile the user profile for whih the preference is being set.
+ * @param profile If the device owner is set, any profile is allowed.
+ Otherwise, the given profile can only be managed profile.
* @param preferences the list of profile network preferences for the
* provided profile.
* @param listener an optional listener to listen for completion of the operation.
@@ -10676,9 +10693,9 @@
throw new IllegalArgumentException("Must explicitly specify a user handle ("
+ "UserHandle.CURRENT not supported)");
}
- final UserManager um = mContext.getSystemService(UserManager.class);
- if (!um.isManagedProfile(profile.getIdentifier())) {
- throw new IllegalArgumentException("Profile must be a managed profile");
+ if (!isNetworkPreferenceAllowedForProfile(profile)) {
+ throw new IllegalArgumentException("Profile must be a managed profile "
+ + "or the device owner must be set. ");
}
final List<ProfileNetworkPreferenceList.Preference> preferenceList =
diff --git a/tests/cts/hostside/Android.bp b/tests/cts/hostside/Android.bp
index b684068..47ea53e 100644
--- a/tests/cts/hostside/Android.bp
+++ b/tests/cts/hostside/Android.bp
@@ -34,4 +34,10 @@
"general-tests",
"sts"
],
+ data: [
+ ":CtsHostsideNetworkTestsApp",
+ ":CtsHostsideNetworkTestsApp2",
+ ":CtsHostsideNetworkTestsAppNext",
+ ],
+ per_testcase_directory: true,
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index e4a9ebe..eb7d1ea 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -916,7 +916,7 @@
final Intent intent = new Intent();
if (type == TYPE_COMPONENT_ACTIVTIY) {
intent.setComponent(new ComponentName(TEST_APP2_PKG, TEST_APP2_ACTIVITY_CLASS))
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
} else if (type == TYPE_COMPONENT_FOREGROUND_SERVICE) {
intent.setComponent(new ComponentName(TEST_APP2_PKG, TEST_APP2_SERVICE_CLASS))
.setFlags(1);
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java
deleted file mode 100644
index 098f295..0000000
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/ConnOnActivityStartTest.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2022 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.cts.net.hostside;
-
-
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getUiDevice;
-import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
-import static com.android.cts.net.hostside.Property.APP_STANDBY_MODE;
-import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
-import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
-import static com.android.cts.net.hostside.Property.DOZE_MODE;
-import static com.android.cts.net.hostside.Property.METERED_NETWORK;
-import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
-
-import android.util.Log;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-@RequiredProperties({NON_METERED_NETWORK})
-public class ConnOnActivityStartTest extends AbstractRestrictBackgroundNetworkTestCase {
- private static final int TEST_ITERATION_COUNT = 5;
-
- @Before
- public final void setUp() throws Exception {
- super.setUp();
- resetDeviceState();
- }
-
- @After
- public final void tearDown() throws Exception {
- super.tearDown();
- resetDeviceState();
- }
-
- private void resetDeviceState() throws Exception {
- resetBatteryState();
- setBatterySaverMode(false);
- setRestrictBackground(false);
- setAppIdle(false);
- setDozeMode(false);
- }
-
-
- @Test
- @RequiredProperties({BATTERY_SAVER_MODE})
- public void testStartActivity_batterySaver() throws Exception {
- setBatterySaverMode(true);
- assertLaunchedActivityHasNetworkAccess("testStartActivity_batterySaver");
- }
-
- @Test
- @RequiredProperties({DATA_SAVER_MODE, METERED_NETWORK})
- public void testStartActivity_dataSaver() throws Exception {
- setRestrictBackground(true);
- assertLaunchedActivityHasNetworkAccess("testStartActivity_dataSaver");
- }
-
- @Test
- @RequiredProperties({DOZE_MODE})
- public void testStartActivity_doze() throws Exception {
- setDozeMode(true);
- assertLaunchedActivityHasNetworkAccess("testStartActivity_doze");
- }
-
- @Test
- @RequiredProperties({APP_STANDBY_MODE})
- public void testStartActivity_appStandby() throws Exception {
- turnBatteryOn();
- setAppIdle(true);
- assertLaunchedActivityHasNetworkAccess("testStartActivity_appStandby");
- }
-
- private void assertLaunchedActivityHasNetworkAccess(String testName) throws Exception {
- for (int i = 0; i < TEST_ITERATION_COUNT; ++i) {
- Log.i(TAG, testName + " start #" + i);
- launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
- getUiDevice().pressHome();
- assertBackgroundState();
- Log.i(TAG, testName + " end #" + i);
- }
- }
-}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
index 7842eec..0a0f24b 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
@@ -57,7 +57,6 @@
import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.uiautomator.UiDevice;
import com.android.compatibility.common.util.AppStandbyUtils;
import com.android.compatibility.common.util.BatteryUtils;
@@ -439,10 +438,6 @@
return InstrumentationRegistry.getInstrumentation();
}
- public static UiDevice getUiDevice() {
- return UiDevice.getInstance(getInstrumentation());
- }
-
// When power saver mode or restrict background enabled or adding any white/black list into
// those modes, NetworkPolicy may need to take some time to update the rules of uids. So having
// this function and using PollingCheck to try to make sure the uid has updated and reduce the
diff --git a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
index a337fe2..eb7dca7 100644
--- a/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
+++ b/tests/cts/hostside/app2/src/com/android/cts/net/hostside/app2/MyActivity.java
@@ -39,33 +39,6 @@
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "MyActivity.onCreate()");
- }
-
- @Override
- public void finish() {
- if (finishCommandReceiver != null) {
- unregisterReceiver(finishCommandReceiver);
- }
- super.finish();
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- Log.d(TAG, "MyActivity.onStart()");
- }
-
- @Override
- protected void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
- Log.d(TAG, "MyActivity.onNewIntent()");
- setIntent(intent);
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- Log.d(TAG, "MyActivity.onResume(): " + getIntent());
Common.notifyNetworkStateObserver(this, getIntent(), TYPE_COMPONENT_ACTIVTY);
finishCommandReceiver = new BroadcastReceiver() {
@Override
@@ -84,6 +57,20 @@
}
@Override
+ public void finish() {
+ if (finishCommandReceiver != null) {
+ unregisterReceiver(finishCommandReceiver);
+ }
+ super.finish();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ Log.d(TAG, "MyActivity.onStart()");
+ }
+
+ @Override
protected void onDestroy() {
Log.d(TAG, "MyActivity.onDestroy()");
super.onDestroy();
diff --git a/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java b/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
deleted file mode 100644
index 3387fd7..0000000
--- a/tests/cts/hostside/src/com/android/cts/net/HostsideConnOnActivityStartTest.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2022 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.cts.net;
-
-public class HostsideConnOnActivityStartTest extends HostsideNetworkTestCase {
- private static final String TEST_CLASS = TEST_PKG + ".ConnOnActivityStartTest";
- @Override
- public void setUp() throws Exception {
- super.setUp();
-
- uninstallPackage(TEST_APP2_PKG, false);
- installPackage(TEST_APP2_APK);
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
-
- uninstallPackage(TEST_APP2_PKG, true);
- }
-
- public void testStartActivity_batterySaver() throws Exception {
- runDeviceTests(TEST_PKG, TEST_CLASS, "testStartActivity_batterySaver");
- }
-
- public void testStartActivity_dataSaver() throws Exception {
- runDeviceTests(TEST_PKG, TEST_CLASS, "testStartActivity_dataSaver");
- }
-
- public void testStartActivity_doze() throws Exception {
- runDeviceTests(TEST_PKG, TEST_CLASS, "testStartActivity_doze");
- }
-
- public void testStartActivity_appStandby() throws Exception {
- runDeviceTests(TEST_PKG, TEST_CLASS, "testStartActivity_appStandby");
- }
-}
diff --git a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
index f7a2421..04434e5 100644
--- a/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
+++ b/tests/cts/net/src/android/net/cts/EthernetManagerTest.kt
@@ -75,7 +75,7 @@
private val em by lazy { EthernetManagerShimImpl.newInstance(context) }
private val createdIfaces = ArrayList<EthernetTestInterface>()
- private val addedListeners = ArrayList<InterfaceStateListener>()
+ private val addedListeners = ArrayList<EthernetStateListener>()
private class EthernetTestInterface(
context: Context,
@@ -171,7 +171,7 @@
}
}
- private fun addInterfaceStateListener(executor: Executor, listener: InterfaceStateListener) {
+ private fun addInterfaceStateListener(executor: Executor, listener: EthernetStateListener) {
em.addInterfaceStateListener(executor, listener)
addedListeners.add(listener)
}
@@ -212,15 +212,25 @@
listener.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
listener.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
+ // Register a new listener, it should see state of all existing interfaces immediately.
+ val listener2 = EthernetStateListener()
+ addInterfaceStateListener(executor, listener2)
+ listener2.expectCallback(iface, STATE_LINK_UP, ROLE_CLIENT)
+ listener2.expectCallback(iface2, STATE_LINK_UP, ROLE_CLIENT)
+
// Removing interfaces first sends link down, then STATE_ABSENT/ROLE_NONE.
removeInterface(iface)
- listener.expectCallback(iface, STATE_LINK_DOWN, ROLE_CLIENT)
- listener.expectCallback(iface, STATE_ABSENT, ROLE_NONE)
+ for (listener in addedListeners) {
+ listener.expectCallback(iface, STATE_LINK_DOWN, ROLE_CLIENT)
+ listener.expectCallback(iface, STATE_ABSENT, ROLE_NONE)
+ }
removeInterface(iface2)
- listener.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
- listener.expectCallback(iface2, STATE_ABSENT, ROLE_NONE)
- listener.assertNoCallback()
+ for (listener in addedListeners) {
+ listener.expectCallback(iface2, STATE_LINK_DOWN, ROLE_CLIENT)
+ listener.expectCallback(iface2, STATE_ABSENT, ROLE_NONE)
+ listener.assertNoCallback()
+ }
}
@Test
diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
index 0504973..d4f3d57 100644
--- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
+++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt
@@ -1275,4 +1275,23 @@
matchAllCallback.expectCallback<Lost>(wifiNetwork)
wifiAgent.expectCallback<OnNetworkUnwanted>()
}
+
+ @Test
+ fun testUnregisterAgentBeforeAgentFullyConnected() {
+ val specifier = UUID.randomUUID().toString()
+ val callback = TestableNetworkCallback()
+ val transports = intArrayOf(TRANSPORT_CELLULAR)
+ // Ensure this NetworkAgent is never unneeded by filing a request with its specifier.
+ requestNetwork(makeTestNetworkRequest(specifier = specifier), callback)
+ val nc = makeTestNetworkCapabilities(specifier, transports)
+ val agent = createNetworkAgent(realContext, initialNc = nc)
+ // Connect the agent
+ agent.register()
+ // Mark agent connected then unregister agent immediately. Verify that both available and
+ // lost callback should be sent still.
+ agent.markConnected()
+ agent.unregister()
+ callback.expectCallback<Available>(agent.network!!)
+ callback.eventuallyExpect<Lost> { it.network == agent.network }
+ }
}
diff --git a/tests/unit/java/android/net/ConnectivityManagerTest.java b/tests/unit/java/android/net/ConnectivityManagerTest.java
index f324630..c327868 100644
--- a/tests/unit/java/android/net/ConnectivityManagerTest.java
+++ b/tests/unit/java/android/net/ConnectivityManagerTest.java
@@ -41,6 +41,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -72,6 +73,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
@@ -82,6 +84,8 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.lang.ref.WeakReference;
+
@RunWith(DevSdkIgnoreRunner.class)
@SmallTest
@DevSdkIgnoreRule.IgnoreUpTo(VERSION_CODES.R)
@@ -461,4 +465,49 @@
}
fail("expected exception of type " + throwableType);
}
+
+ private static class MockContext extends BroadcastInterceptingContext {
+ MockContext(Context base) {
+ super(base);
+ }
+
+ @Override
+ public Context getApplicationContext() {
+ return mock(Context.class);
+ }
+ }
+
+ private WeakReference<Context> makeConnectivityManagerAndReturnContext() {
+ // Mockito may have an internal reference to the mock, creating MockContext for testing.
+ final Context c = new MockContext(mock(Context.class));
+
+ new ConnectivityManager(c, mService);
+
+ return new WeakReference<>(c);
+ }
+
+ private void forceGC() {
+ // First GC ensures that objects are collected for finalization, then second GC ensures
+ // they're garbage-collected after being finalized.
+ System.gc();
+ System.runFinalization();
+ System.gc();
+ }
+
+ @Test
+ public void testConnectivityManagerDoesNotLeakContext() throws Exception {
+ final WeakReference<Context> ref = makeConnectivityManagerAndReturnContext();
+
+ final int attempts = 100;
+ final long waitIntervalMs = 50;
+ for (int i = 0; i < attempts; i++) {
+ forceGC();
+ if (ref.get() == null) break;
+
+ Thread.sleep(waitIntervalMs);
+ }
+
+ assertNull("ConnectivityManager weak reference still not null after " + attempts
+ + " attempts", ref.get());
+ }
}
diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
index 6316c72..d63f9d2 100644
--- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java
@@ -195,6 +195,7 @@
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;
@@ -340,6 +341,7 @@
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;
@@ -542,6 +544,7 @@
@Mock NetworkPolicyManager mNetworkPolicyManager;
@Mock VpnProfileStore mVpnProfileStore;
@Mock SystemConfigManager mSystemConfigManager;
+ @Mock DevicePolicyManager mDevicePolicyManager;
@Mock Resources mResources;
@Mock ClatCoordinator mClatCoordinator;
@Mock PacProxyManager mPacProxyManager;
@@ -647,7 +650,8 @@
@Override
public ComponentName startService(Intent service) {
final String action = service.getAction();
- if (!VpnConfig.SERVICE_INTERFACE.equals(action)) {
+ 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");
@@ -664,6 +668,7 @@
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;
@@ -693,6 +698,14 @@
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;
@@ -14733,12 +14746,42 @@
public void testProfileNetworkPrefWrongProfile() throws Exception {
final UserHandle testHandle = UserHandle.of(TEST_WORK_PROFILE_USER_ID);
mServiceContext.setWorkProfile(testHandle, false);
- assertThrows("Should not be able to set a user pref for a non-work profile",
+ 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);
diff --git a/tests/unit/java/com/android/server/connectivity/VpnTest.java b/tests/unit/java/com/android/server/connectivity/VpnTest.java
index 33c0868..6da4f07 100644
--- a/tests/unit/java/com/android/server/connectivity/VpnTest.java
+++ b/tests/unit/java/com/android/server/connectivity/VpnTest.java
@@ -27,6 +27,7 @@
import static android.net.ConnectivityManager.NetworkCallback;
import static android.net.INetd.IF_STATE_DOWN;
import static android.net.INetd.IF_STATE_UP;
+import static android.net.VpnManager.TYPE_VPN_PLATFORM;
import static android.os.UserHandle.PER_USER_RANGE;
import static com.android.modules.utils.build.SdkLevel.isAtLeastT;
@@ -54,6 +55,7 @@
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.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -65,6 +67,7 @@
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -88,10 +91,15 @@
import android.net.RouteInfo;
import android.net.UidRangeParcel;
import android.net.VpnManager;
+import android.net.VpnProfileState;
import android.net.VpnService;
import android.net.VpnTransportInfo;
import android.net.ipsec.ike.IkeSessionCallback;
+import android.net.ipsec.ike.exceptions.IkeException;
+import android.net.ipsec.ike.exceptions.IkeNetworkLostException;
+import android.net.ipsec.ike.exceptions.IkeNonProtocolException;
import android.net.ipsec.ike.exceptions.IkeProtocolException;
+import android.net.ipsec.ike.exceptions.IkeTimeoutException;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.ConditionVariable;
@@ -118,6 +126,7 @@
import com.android.testutils.DevSdkIgnoreRule;
import com.android.testutils.DevSdkIgnoreRunner;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -135,6 +144,7 @@
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -271,6 +281,11 @@
doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(any());
}
+ @After
+ public void tearDown() throws Exception {
+ doReturn(PERMISSION_DENIED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN);
+ }
+
private <T> void mockService(Class<T> clazz, String name, T service) {
doReturn(service).when(mContext).getSystemService(name);
doReturn(name).when(mContext).getSystemServiceName(clazz);
@@ -783,6 +798,30 @@
verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
}
+ private void verifyPlatformVpnIsActivated(String packageName) {
+ verify(mAppOps).noteOpNoThrow(
+ eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
+ eq(Process.myUid()),
+ eq(packageName),
+ eq(null) /* attributionTag */,
+ eq(null) /* message */);
+ verify(mAppOps).startOp(
+ eq(AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER),
+ eq(Process.myUid()),
+ eq(packageName),
+ eq(null) /* attributionTag */,
+ eq(null) /* message */);
+ }
+
+ private void verifyPlatformVpnIsDeactivated(String packageName) {
+ // Add a small delay to double confirm that finishOp is only called once.
+ verify(mAppOps, after(100)).finishOp(
+ eq(AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER),
+ eq(Process.myUid()),
+ eq(packageName),
+ eq(null) /* attributionTag */);
+ }
+
@Test
public void testStartVpnProfile() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
@@ -793,13 +832,7 @@
vpn.startVpnProfile(TEST_VPN_PKG);
verify(mVpnProfileStore).get(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG)));
- verify(mAppOps)
- .noteOpNoThrow(
- eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
- eq(Process.myUid()),
- eq(TEST_VPN_PKG),
- eq(null) /* attributionTag */,
- eq(null) /* message */);
+ verifyPlatformVpnIsActivated(TEST_VPN_PKG);
}
@Test
@@ -811,7 +844,7 @@
vpn.startVpnProfile(TEST_VPN_PKG);
- // Verify that the the ACTIVATE_VPN appop was checked, but no error was thrown.
+ // Verify that the ACTIVATE_VPN appop was checked, but no error was thrown.
verify(mAppOps).noteOpNoThrow(AppOpsManager.OPSTR_ACTIVATE_VPN, Process.myUid(),
TEST_VPN_PKG, null /* attributionTag */, null /* message */);
}
@@ -896,18 +929,7 @@
when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
.thenReturn(mVpnProfile.encode());
vpn.startVpnProfile(TEST_VPN_PKG);
- verify(mAppOps).noteOpNoThrow(
- eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
- eq(Process.myUid()),
- eq(TEST_VPN_PKG),
- eq(null) /* attributionTag */,
- eq(null) /* message */);
- verify(mAppOps).startOp(
- eq(AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER),
- eq(Process.myUid()),
- eq(TEST_VPN_PKG),
- eq(null) /* attributionTag */,
- eq(null) /* message */);
+ verifyPlatformVpnIsActivated(TEST_VPN_PKG);
// Add a small delay to make sure that startOp is only called once.
verify(mAppOps, after(100).times(1)).startOp(
eq(AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER),
@@ -923,12 +945,7 @@
eq(null) /* attributionTag */,
eq(null) /* message */);
vpn.stopVpnProfile(TEST_VPN_PKG);
- // Add a small delay to double confirm that startOp is only called once.
- verify(mAppOps, after(100)).finishOp(
- eq(AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER),
- eq(Process.myUid()),
- eq(TEST_VPN_PKG),
- eq(null) /* attributionTag */);
+ verifyPlatformVpnIsDeactivated(TEST_VPN_PKG);
}
@Test
@@ -964,6 +981,128 @@
eq(null) /* message */);
}
+ private void verifyVpnManagerEvent(String sessionKey, String category, int errorClass,
+ int errorCode, VpnProfileState... profileState) {
+ final Context userContext =
+ mContext.createContextAsUser(UserHandle.of(primaryUser.id), 0 /* flags */);
+ final ArgumentCaptor<Intent> intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class);
+
+ final int verifyTimes = (profileState == null) ? 1 : profileState.length;
+ verify(userContext, times(verifyTimes)).startService(intentArgumentCaptor.capture());
+
+ for (int i = 0; i < verifyTimes; i++) {
+ final Intent intent = intentArgumentCaptor.getAllValues().get(i);
+ assertEquals(sessionKey, intent.getStringExtra(VpnManager.EXTRA_SESSION_KEY));
+ final Set<String> categories = intent.getCategories();
+ assertTrue(categories.contains(category));
+ assertEquals(errorClass,
+ intent.getIntExtra(VpnManager.EXTRA_ERROR_CLASS, -1 /* defaultValue */));
+ assertEquals(errorCode,
+ intent.getIntExtra(VpnManager.EXTRA_ERROR_CODE, -1 /* defaultValue */));
+ if (profileState != null) {
+ assertEquals(profileState[i], intent.getParcelableExtra(
+ VpnManager.EXTRA_VPN_PROFILE_STATE, VpnProfileState.class));
+ }
+ }
+ reset(userContext);
+ }
+
+ @Test
+ public void testVpnManagerEventForUserDeactivated() throws Exception {
+ assumeTrue(SdkLevel.isAtLeastT());
+ // For security reasons, Vpn#prepare() will check that oldPackage and newPackage are either
+ // null or the package of the caller. This test will call Vpn#prepare() to pretend the old
+ // VPN is replaced by a new one. But only Settings can change to some other packages, and
+ // this is checked with CONTROL_VPN so simulate holding CONTROL_VPN in order to pass the
+ // security checks.
+ doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN);
+ final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+ when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+ .thenReturn(mVpnProfile.encode());
+
+ // Test the case that the user deactivates the vpn in vpn app.
+ final String sessionKey1 = vpn.startVpnProfile(TEST_VPN_PKG);
+ verifyPlatformVpnIsActivated(TEST_VPN_PKG);
+ vpn.stopVpnProfile(TEST_VPN_PKG);
+ verifyPlatformVpnIsDeactivated(TEST_VPN_PKG);
+ // CATEGORY_EVENT_DEACTIVATED_BY_USER is not an error event, so both of errorClass and
+ // errorCode won't be set.
+ verifyVpnManagerEvent(sessionKey1, VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
+ -1 /* errorClass */, -1 /* errorCode */, null /* profileState */);
+ reset(mAppOps);
+
+ // Test the case that the user chooses another vpn and the original one is replaced.
+ final String sessionKey2 = vpn.startVpnProfile(TEST_VPN_PKG);
+ verifyPlatformVpnIsActivated(TEST_VPN_PKG);
+ vpn.prepare(TEST_VPN_PKG, "com.new.vpn" /* newPackage */, TYPE_VPN_PLATFORM);
+ verifyPlatformVpnIsDeactivated(TEST_VPN_PKG);
+ // CATEGORY_EVENT_DEACTIVATED_BY_USER is not an error event, so both of errorClass and
+ // errorCode won't be set.
+ verifyVpnManagerEvent(sessionKey2, VpnManager.CATEGORY_EVENT_DEACTIVATED_BY_USER,
+ -1 /* errorClass */, -1 /* errorCode */, null /* profileState */);
+ }
+
+ @Test
+ public void testVpnManagerEventForAlwaysOnChanged() throws Exception {
+ assumeTrue(SdkLevel.isAtLeastT());
+ // Calling setAlwaysOnPackage() needs to hold CONTROL_VPN.
+ doReturn(PERMISSION_GRANTED).when(mContext).checkCallingOrSelfPermission(CONTROL_VPN);
+ final Vpn vpn = createVpn(primaryUser.id);
+ // Enable VPN always-on for PKGS[1].
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false /* lockdown */,
+ null /* lockdownAllowlist */));
+ verifyVpnManagerEvent(null /* sessionKey */,
+ VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
+ -1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+ null /* sessionKey */, true /* alwaysOn */, false /* lockdown */));
+
+ // Enable VPN lockdown for PKGS[1].
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], true /* lockdown */,
+ null /* lockdownAllowlist */));
+ verifyVpnManagerEvent(null /* sessionKey */,
+ VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
+ -1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+ null /* sessionKey */, true /* alwaysOn */, true /* lockdown */));
+
+ // Disable VPN lockdown for PKGS[1].
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false /* lockdown */,
+ null /* lockdownAllowlist */));
+ verifyVpnManagerEvent(null /* sessionKey */,
+ VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
+ -1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+ null /* sessionKey */, true /* alwaysOn */, false /* lockdown */));
+
+ // Disable VPN always-on.
+ assertTrue(vpn.setAlwaysOnPackage(null, false /* lockdown */,
+ null /* lockdownAllowlist */));
+ verifyVpnManagerEvent(null /* sessionKey */,
+ VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
+ -1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+ null /* sessionKey */, false /* alwaysOn */, false /* lockdown */));
+
+ // Enable VPN always-on for PKGS[1] again.
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[1], false /* lockdown */,
+ null /* lockdownAllowlist */));
+ verifyVpnManagerEvent(null /* sessionKey */,
+ VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
+ -1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+ null /* sessionKey */, true /* alwaysOn */, false /* lockdown */));
+
+ // Enable VPN always-on for PKGS[2].
+ assertTrue(vpn.setAlwaysOnPackage(PKGS[2], false /* lockdown */,
+ null /* lockdownAllowlist */));
+ // PKGS[1] is replaced with PKGS[2].
+ // Pass 2 VpnProfileState objects to verifyVpnManagerEvent(), the first one is sent to
+ // PKGS[1] to notify PKGS[1] that the VPN always-on is disabled, the second one is sent to
+ // PKGS[2] to notify PKGS[2] that the VPN always-on is enabled.
+ verifyVpnManagerEvent(null /* sessionKey */,
+ VpnManager.CATEGORY_EVENT_ALWAYS_ON_STATE_CHANGED, -1 /* errorClass */,
+ -1 /* errorCode */, new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+ null /* sessionKey */, false /* alwaysOn */, false /* lockdown */),
+ new VpnProfileState(VpnProfileState.STATE_DISCONNECTED,
+ null /* sessionKey */, true /* alwaysOn */, false /* lockdown */));
+ }
+
@Test
public void testSetPackageAuthorizationVpnService() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks();
@@ -981,7 +1120,7 @@
public void testSetPackageAuthorizationPlatformVpn() throws Exception {
final Vpn vpn = createVpnAndSetupUidChecks();
- assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, VpnManager.TYPE_VPN_PLATFORM));
+ assertTrue(vpn.setPackageAuthorization(TEST_VPN_PKG, TYPE_VPN_PLATFORM));
verify(mAppOps)
.setMode(
eq(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN),
@@ -1031,15 +1170,16 @@
config -> Arrays.asList(config.flags).contains(flag)));
}
- @Test
- public void testStartPlatformVpnAuthenticationFailed() throws Exception {
+ private void setupPlatformVpnWithSpecificExceptionAndItsErrorCode(IkeException exception,
+ String category, int errorType, int errorCode) throws Exception {
final ArgumentCaptor<IkeSessionCallback> captor =
ArgumentCaptor.forClass(IkeSessionCallback.class);
- final IkeProtocolException exception = mock(IkeProtocolException.class);
- when(exception.getErrorType())
- .thenReturn(IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED);
- final Vpn vpn = startLegacyVpn(createVpn(primaryUser.id), (mVpnProfile));
+ final Vpn vpn = createVpnAndSetupUidChecks(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN);
+ when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG)))
+ .thenReturn(mVpnProfile.encode());
+
+ final String sessionKey = vpn.startVpnProfile(TEST_VPN_PKG);
final NetworkCallback cb = triggerOnAvailableAndGetCallback();
verifyInterfaceSetCfgWithFlags(IF_STATE_UP);
@@ -1049,10 +1189,75 @@
verify(mIkev2SessionCreator, timeout(TEST_TIMEOUT_MS))
.createIkeSession(any(), any(), any(), any(), captor.capture(), any());
final IkeSessionCallback ikeCb = captor.getValue();
- ikeCb.onClosedExceptionally(exception);
+ ikeCb.onClosedWithException(exception);
- verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS)).unregisterNetworkCallback(eq(cb));
- assertEquals(LegacyVpnInfo.STATE_FAILED, vpn.getLegacyVpnInfo().state);
+ verifyVpnManagerEvent(sessionKey, category, errorType, errorCode, null /* profileState */);
+ if (errorType == VpnManager.ERROR_CLASS_NOT_RECOVERABLE) {
+ verify(mConnectivityManager, timeout(TEST_TIMEOUT_MS))
+ .unregisterNetworkCallback(eq(cb));
+ }
+ }
+
+ @Test
+ public void testStartPlatformVpnAuthenticationFailed() throws Exception {
+ final IkeProtocolException exception = mock(IkeProtocolException.class);
+ final int errorCode = IkeProtocolException.ERROR_TYPE_AUTHENTICATION_FAILED;
+ when(exception.getErrorType()).thenReturn(errorCode);
+ setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+ VpnManager.CATEGORY_EVENT_IKE_ERROR, VpnManager.ERROR_CLASS_NOT_RECOVERABLE,
+ errorCode);
+ }
+
+ @Test
+ public void testStartPlatformVpnFailedWithRecoverableError() throws Exception {
+ final IkeProtocolException exception = mock(IkeProtocolException.class);
+ final int errorCode = IkeProtocolException.ERROR_TYPE_TEMPORARY_FAILURE;
+ when(exception.getErrorType()).thenReturn(errorCode);
+ setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+ VpnManager.CATEGORY_EVENT_IKE_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE, errorCode);
+ }
+
+ @Test
+ public void testStartPlatformVpnFailedWithUnknownHostException() throws Exception {
+ final IkeNonProtocolException exception = mock(IkeNonProtocolException.class);
+ final UnknownHostException unknownHostException = new UnknownHostException();
+ final int errorCode = VpnManager.ERROR_CODE_NETWORK_UNKNOWN_HOST;
+ when(exception.getCause()).thenReturn(unknownHostException);
+ setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+ VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
+ errorCode);
+ }
+
+ @Test
+ public void testStartPlatformVpnFailedWithIkeTimeoutException() throws Exception {
+ final IkeNonProtocolException exception = mock(IkeNonProtocolException.class);
+ final IkeTimeoutException ikeTimeoutException =
+ new IkeTimeoutException("IkeTimeoutException");
+ final int errorCode = VpnManager.ERROR_CODE_NETWORK_PROTOCOL_TIMEOUT;
+ when(exception.getCause()).thenReturn(ikeTimeoutException);
+ setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+ VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
+ errorCode);
+ }
+
+ @Test
+ public void testStartPlatformVpnFailedWithIkeNetworkLostException() throws Exception {
+ final IkeNetworkLostException exception = new IkeNetworkLostException(
+ new Network(100));
+ setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+ VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
+ VpnManager.ERROR_CODE_NETWORK_LOST);
+ }
+
+ @Test
+ public void testStartPlatformVpnFailedWithIOException() throws Exception {
+ final IkeNonProtocolException exception = mock(IkeNonProtocolException.class);
+ final IOException ioException = new IOException();
+ final int errorCode = VpnManager.ERROR_CODE_NETWORK_IO;
+ when(exception.getCause()).thenReturn(ioException);
+ setupPlatformVpnWithSpecificExceptionAndItsErrorCode(exception,
+ VpnManager.CATEGORY_EVENT_NETWORK_ERROR, VpnManager.ERROR_CLASS_RECOVERABLE,
+ errorCode);
}
@Test