Snap for 7277594 from 445f742223d8d27cff0e732bf7cd44aa0d26d32a to mainline-documentsui-release
Change-Id: Ib6d26afeb432e23fa19b4603d570d8a917ffbc6f
diff --git a/TEST_MAPPING b/TEST_MAPPING
index b840d01..490fb5d 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -14,7 +14,7 @@
"path": "frameworks/base/packages/Tethering"
},
{
- "path": "frameworks/opt/net/wifi"
+ "path": "packages/modules/Wifi/framework"
}
]
-}
\ No newline at end of file
+}
diff --git a/common/Android.bp b/common/Android.bp
index 03296e7..74aafd3 100644
--- a/common/Android.bp
+++ b/common/Android.bp
@@ -59,6 +59,7 @@
"//packages/modules/NetworkStack:__subpackages__",
"//packages/modules/CaptivePortalLogin",
"//frameworks/libs/net/common/tests:__subpackages__",
+ "//frameworks/libs/net/common/testutils:__subpackages__",
],
static_libs: [
"net-utils-framework-common",
@@ -68,25 +69,6 @@
],
}
-java_library {
- // Consider using net-tests-utils instead if writing device code.
- // That library has a lot more useful tools into it for users that
- // work on Android and includes this lib.
- name: "net-tests-utils-host-device-common",
- srcs: [
- "hostdevice/**/*.java",
- "hostdevice/**/*.kt",
- ],
- host_supported: true,
- visibility: [
- "//frameworks/libs/net/common/tests:__subpackages__",
- "//frameworks/libs/net/client-libs/tests:__subpackages__",
- ],
- static_libs: [
- "kotlin-test"
- ]
-}
-
java_defaults {
name: "lib_mockito_extended",
static_libs: [
@@ -98,25 +80,6 @@
],
}
-java_library {
- name: "net-tests-utils",
- srcs: [
- "devicetests/**/*.java",
- "devicetests/**/*.kt",
- ],
- defaults: ["lib_mockito_extended"],
- libs: [
- "androidx.annotation_annotation",
- ],
- static_libs: [
- "androidx.test.ext.junit",
- "kotlin-reflect",
- "libnanohttpd",
- "net-tests-utils-host-device-common",
- "net-utils-device-common",
- ],
-}
-
filegroup {
name: "net-utils-framework-common-srcs",
srcs: ["framework/**/*.java"],
diff --git a/common/device/com/android/net/module/util/Ipv6Utils.java b/common/device/com/android/net/module/util/Ipv6Utils.java
index 73c2431..fe7c89b 100644
--- a/common/device/com/android/net/module/util/Ipv6Utils.java
+++ b/common/device/com/android/net/module/util/Ipv6Utils.java
@@ -20,7 +20,9 @@
import static com.android.net.module.util.IpUtils.icmpv6Checksum;
import static com.android.net.module.util.NetworkStackConstants.ETHER_TYPE_IPV6;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_ADVERTISEMENT;
+import static com.android.net.module.util.NetworkStackConstants.ICMPV6_NEIGHBOR_SOLICITATION;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT;
import static com.android.net.module.util.NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION;
@@ -30,6 +32,7 @@
import com.android.net.module.util.structs.Icmpv6Header;
import com.android.net.module.util.structs.Ipv6Header;
import com.android.net.module.util.structs.NaHeader;
+import com.android.net.module.util.structs.NsHeader;
import com.android.net.module.util.structs.RaHeader;
import com.android.net.module.util.structs.RsHeader;
@@ -123,6 +126,19 @@
}
/**
+ * Build an ICMPv6 Neighbor Solicitation packet from the required specified parameters.
+ */
+ public static ByteBuffer buildNsPacket(final MacAddress srcMac, final MacAddress dstMac,
+ final Inet6Address srcIp, final Inet6Address dstIp,
+ final Inet6Address target, final ByteBuffer... options) {
+ final NsHeader nsHeader = new NsHeader(target);
+ final ByteBuffer[] payload = buildIcmpv6Payload(
+ ByteBuffer.wrap(nsHeader.writeToBytes(ByteOrder.BIG_ENDIAN)), options);
+ return buildIcmpv6Packet(srcMac, dstMac, srcIp, dstIp,
+ (byte) ICMPV6_NEIGHBOR_SOLICITATION /* type */, (byte) 0 /* code */, payload);
+ }
+
+ /**
* Build an ICMPv6 Router Solicitation packet from the required specified parameters.
*/
public static ByteBuffer buildRsPacket(final MacAddress srcMac, final MacAddress dstMac,
@@ -133,4 +149,14 @@
return buildIcmpv6Packet(srcMac, dstMac, srcIp, dstIp,
(byte) ICMPV6_ROUTER_SOLICITATION /* type */, (byte) 0 /* code */, payload);
}
+
+ /**
+ * Build an ICMPv6 Echo Request packet from the required specified parameters.
+ */
+ public static ByteBuffer buildEchoRequestPacket(final MacAddress srcMac,
+ final MacAddress dstMac, final Inet6Address srcIp, final Inet6Address dstIp) {
+ final ByteBuffer payload = ByteBuffer.allocate(4); // ID and Sequence number may be zero.
+ return buildIcmpv6Packet(srcMac, dstMac, srcIp, dstIp,
+ (byte) ICMPV6_ECHO_REQUEST_TYPE /* type */, (byte) 0 /* code */, payload);
+ }
}
diff --git a/common/device/com/android/net/module/util/structs/NaHeader.java b/common/device/com/android/net/module/util/structs/NaHeader.java
index 571d67b..90c078e 100644
--- a/common/device/com/android/net/module/util/structs/NaHeader.java
+++ b/common/device/com/android/net/module/util/structs/NaHeader.java
@@ -26,8 +26,8 @@
* ICMPv6 Neighbor Advertisement header, follow {@link Icmpv6Header}, as per
* https://tools.ietf.org/html/rfc4861. This does not contain any option.
*
- * 0 1 2 3
- * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* | Type | Code | Checksum |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
diff --git a/common/device/com/android/net/module/util/structs/NsHeader.java b/common/device/com/android/net/module/util/structs/NsHeader.java
new file mode 100644
index 0000000..6e0aa50
--- /dev/null
+++ b/common/device/com/android/net/module/util/structs/NsHeader.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 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.net.module.util.structs;
+
+import com.android.net.module.util.Struct;
+import com.android.net.module.util.Struct.Field;
+import com.android.net.module.util.Struct.Type;
+
+import java.net.Inet6Address;
+
+/**
+ * ICMPv6 Neighbor Solicitation header, follow {@link Icmpv6Header}, as per
+ * https://tools.ietf.org/html/rfc4861. This does not contain any option.
+ *
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Type | Code | Checksum |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Reserved |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | |
+ * + +
+ * | |
+ * + Target Address +
+ * | |
+ * + +
+ * | |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Options ...
+ * +-+-+-+-+-+-+-+-+-+-+-+-
+ */
+public class NsHeader extends Struct {
+ @Field(order = 0, type = Type.S32)
+ public int reserved; // 32 Reserved bits.
+ @Field(order = 1, type = Type.Ipv6Address)
+ public Inet6Address target;
+
+ public NsHeader(final Inet6Address target) {
+ this.reserved = 0;
+ this.target = target;
+ }
+}
diff --git a/common/framework/com/android/net/module/util/CollectionUtils.java b/common/framework/com/android/net/module/util/CollectionUtils.java
index 2223443..4fce8f5 100644
--- a/common/framework/com/android/net/module/util/CollectionUtils.java
+++ b/common/framework/com/android/net/module/util/CollectionUtils.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.util.SparseArray;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
import java.util.function.Predicate;
@@ -148,4 +149,21 @@
}
return -1;
}
+
+ /**
+ * Returns a new collection of elements that match the passed predicate.
+ * @param source the elements to filter.
+ * @param test the predicate to test for.
+ * @return a new collection containing only the source elements that satisfy the predicate.
+ */
+ @NonNull public static <T> ArrayList<T> filter(@NonNull final Collection<T> source,
+ @NonNull final Predicate<T> test) {
+ final ArrayList<T> matches = new ArrayList<>();
+ for (final T e : source) {
+ if (test.test(e)) {
+ matches.add(e);
+ }
+ }
+ return matches;
+ }
}
diff --git a/common/framework/com/android/net/module/util/Inet4AddressUtils.java b/common/framework/com/android/net/module/util/Inet4AddressUtils.java
index a1d34a0..87f43d5 100644
--- a/common/framework/com/android/net/module/util/Inet4AddressUtils.java
+++ b/common/framework/com/android/net/module/util/Inet4AddressUtils.java
@@ -163,4 +163,30 @@
throws IllegalArgumentException {
return intToInet4AddressHTH(prefixLengthToV4NetmaskIntHTH(prefixLength));
}
+
+ /**
+ * Trim leading zeros from IPv4 address strings
+ * Non-v4 addresses and host names remain unchanged.
+ * For example, 192.168.000.010 -> 192.168.0.10
+ * @param addr a string representing an ip address
+ * @return a string properly trimmed
+ */
+ public static String trimAddressZeros(String addr) {
+ if (addr == null) return null;
+ String[] octets = addr.split("\\.");
+ if (octets.length != 4) return addr;
+ StringBuilder builder = new StringBuilder(16);
+ String result = null;
+ for (int i = 0; i < 4; i++) {
+ try {
+ if (octets[i].length() > 3) return addr;
+ builder.append(Integer.parseInt(octets[i]));
+ } catch (NumberFormatException e) {
+ return addr;
+ }
+ if (i < 3) builder.append('.');
+ }
+ result = builder.toString();
+ return result;
+ }
}
diff --git a/common/framework/com/android/net/module/util/InetAddressUtils.java b/common/framework/com/android/net/module/util/InetAddressUtils.java
index 6300328..31d0729 100644
--- a/common/framework/com/android/net/module/util/InetAddressUtils.java
+++ b/common/framework/com/android/net/module/util/InetAddressUtils.java
@@ -18,6 +18,7 @@
import android.os.Parcel;
+import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
@@ -27,6 +28,8 @@
*/
public class InetAddressUtils {
+ private static final int INET6_ADDR_LENGTH = 16;
+
/**
* Writes an InetAddress to a parcel. The address may be null. This is likely faster than
* calling writeSerializable.
@@ -35,6 +38,13 @@
public static void parcelInetAddress(Parcel parcel, InetAddress address, int flags) {
byte[] addressArray = (address != null) ? address.getAddress() : null;
parcel.writeByteArray(addressArray);
+ if (address instanceof Inet6Address) {
+ final Inet6Address v6Address = (Inet6Address) address;
+ final boolean hasScopeId = v6Address.getScopeId() != 0;
+ parcel.writeBoolean(hasScopeId);
+ if (hasScopeId) parcel.writeInt(v6Address.getScopeId());
+ }
+
}
/**
@@ -47,7 +57,14 @@
if (addressArray == null) {
return null;
}
+
try {
+ if (addressArray.length == INET6_ADDR_LENGTH) {
+ final boolean hasScopeId = in.readBoolean();
+ final int scopeId = hasScopeId ? in.readInt() : 0;
+ return Inet6Address.getByAddress(null /* host */, addressArray, scopeId);
+ }
+
return InetAddress.getByAddress(addressArray);
} catch (UnknownHostException e) {
return null;
diff --git a/common/framework/com/android/net/module/util/LocationPermissionChecker.java b/common/framework/com/android/net/module/util/LocationPermissionChecker.java
new file mode 100644
index 0000000..e4ce9e8
--- /dev/null
+++ b/common/framework/com/android/net/module/util/LocationPermissionChecker.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2020 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.net.module.util;
+
+import android.Manifest;
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+import android.net.NetworkStack;
+import android.os.Binder;
+import android.os.Build;
+import android.os.UserHandle;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+
+/**
+ * The methods used for location permission and location mode checking.
+ *
+ * @hide
+ */
+public class LocationPermissionChecker {
+
+ private static final String TAG = "LocationPermissionChecker";
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"LOCATION_PERMISSION_CHECK_STATUS_"}, value = {
+ SUCCEEDED,
+ ERROR_LOCATION_MODE_OFF,
+ ERROR_LOCATION_PERMISSION_MISSING,
+ })
+ public @interface LocationPermissionCheckStatus{}
+
+ // The location permission check succeeded.
+ public static final int SUCCEEDED = 0;
+ // The location mode turns off for the caller.
+ public static final int ERROR_LOCATION_MODE_OFF = 1;
+ // The location permission isn't granted for the caller.
+ public static final int ERROR_LOCATION_PERMISSION_MISSING = 2;
+
+ private final Context mContext;
+ private final AppOpsManager mAppOpsManager;
+
+ public LocationPermissionChecker(Context context) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
+ throw new UnsupportedOperationException("This utility is not supported before R");
+ }
+
+ mContext = context;
+ mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+ }
+
+ /**
+ * Check location permission granted by the caller.
+ *
+ * This API check if the location mode enabled for the caller and the caller has
+ * ACCESS_COARSE_LOCATION permission is targetSDK<29, otherwise, has ACCESS_FINE_LOCATION.
+ *
+ * @param pkgName package name of the application requesting access
+ * @param featureId The feature in the package
+ * @param uid The uid of the package
+ * @param message A message describing why the permission was checked. Only needed if this is
+ * not inside of a two-way binder call from the data receiver
+ *
+ * @return {@code true} returns if the caller has location permission and the location mode is
+ * enabled.
+ */
+ public boolean checkLocationPermission(String pkgName, @Nullable String featureId,
+ int uid, @Nullable String message) {
+ return checkLocationPermissionInternal(pkgName, featureId, uid, message) == SUCCEEDED;
+ }
+
+ /**
+ * Check location permission granted by the caller.
+ *
+ * This API check if the location mode enabled for the caller and the caller has
+ * ACCESS_COARSE_LOCATION permission is targetSDK<29, otherwise, has ACCESS_FINE_LOCATION.
+ * Compared with {@link #checkLocationPermission(String, String, int, String)}, this API returns
+ * the detail information about the checking result, including the reason why it's failed and
+ * logs the error for the caller.
+ *
+ * @param pkgName package name of the application requesting access
+ * @param featureId The feature in the package
+ * @param uid The uid of the package
+ * @param message A message describing why the permission was checked. Only needed if this is
+ * not inside of a two-way binder call from the data receiver
+ *
+ * @return {@link LocationPermissionCheckStatus} the result of the location permission check.
+ */
+ public @LocationPermissionCheckStatus int checkLocationPermissionWithDetailInfo(
+ String pkgName, @Nullable String featureId, int uid, @Nullable String message) {
+ final int result = checkLocationPermissionInternal(pkgName, featureId, uid, message);
+ switch (result) {
+ case ERROR_LOCATION_MODE_OFF:
+ Log.e(TAG, "Location mode is disabled for the device");
+ break;
+ case ERROR_LOCATION_PERMISSION_MISSING:
+ Log.e(TAG, "UID " + uid + " has no location permission");
+ break;
+ }
+ return result;
+ }
+
+ /**
+ * Enforce the caller has location permission.
+ *
+ * This API determines if the location mode enabled for the caller and the caller has
+ * ACCESS_COARSE_LOCATION permission is targetSDK<29, otherwise, has ACCESS_FINE_LOCATION.
+ * SecurityException is thrown if the caller has no permission or the location mode is disabled.
+ *
+ * @param pkgName package name of the application requesting access
+ * @param featureId The feature in the package
+ * @param uid The uid of the package
+ * @param message A message describing why the permission was checked. Only needed if this is
+ * not inside of a two-way binder call from the data receiver
+ */
+ public void enforceLocationPermission(String pkgName, @Nullable String featureId, int uid,
+ @Nullable String message) throws SecurityException {
+ final int result = checkLocationPermissionInternal(pkgName, featureId, uid, message);
+
+ switch (result) {
+ case ERROR_LOCATION_MODE_OFF:
+ throw new SecurityException("Location mode is disabled for the device");
+ case ERROR_LOCATION_PERMISSION_MISSING:
+ throw new SecurityException("UID " + uid + " has no location permission");
+ }
+ }
+
+ private int checkLocationPermissionInternal(String pkgName, @Nullable String featureId,
+ int uid, @Nullable String message) {
+ checkPackage(uid, pkgName);
+
+ // Apps with NETWORK_SETTINGS, NETWORK_SETUP_WIZARD, NETWORK_STACK & MAINLINE_NETWORK_STACK
+ // are granted a bypass.
+ if (checkNetworkSettingsPermission(uid) || checkNetworkSetupWizardPermission(uid)
+ || checkNetworkStackPermission(uid) || checkMainlineNetworkStackPermission(uid)) {
+ return SUCCEEDED;
+ }
+
+ // Location mode must be enabled
+ if (!isLocationModeEnabled()) {
+ return ERROR_LOCATION_MODE_OFF;
+ }
+
+ // LocationAccess by App: caller must have Coarse/Fine Location permission to have access to
+ // location information.
+ if (!checkCallersLocationPermission(pkgName, featureId, uid,
+ true /* coarseForTargetSdkLessThanQ */, message)) {
+ return ERROR_LOCATION_PERMISSION_MISSING;
+ }
+ return SUCCEEDED;
+ }
+
+ /**
+ * Checks that calling process has android.Manifest.permission.ACCESS_FINE_LOCATION or
+ * android.Manifest.permission.ACCESS_COARSE_LOCATION (depending on config/targetSDK level)
+ * and a corresponding app op is allowed for this package and uid.
+ *
+ * @param pkgName PackageName of the application requesting access
+ * @param featureId The feature in the package
+ * @param uid The uid of the package
+ * @param coarseForTargetSdkLessThanQ If true and the targetSDK < Q then will check for COARSE
+ * else (false or targetSDK >= Q) then will check for FINE
+ * @param message A message describing why the permission was checked. Only needed if this is
+ * not inside of a two-way binder call from the data receiver
+ */
+ public boolean checkCallersLocationPermission(String pkgName, @Nullable String featureId,
+ int uid, boolean coarseForTargetSdkLessThanQ, @Nullable String message) {
+
+ boolean isTargetSdkLessThanQ = isTargetSdkLessThan(pkgName, Build.VERSION_CODES.Q, uid);
+
+ String permissionType = Manifest.permission.ACCESS_FINE_LOCATION;
+ if (coarseForTargetSdkLessThanQ && isTargetSdkLessThanQ) {
+ // Having FINE permission implies having COARSE permission (but not the reverse)
+ permissionType = Manifest.permission.ACCESS_COARSE_LOCATION;
+ }
+ if (getUidPermission(permissionType, uid) == PackageManager.PERMISSION_DENIED) {
+ return false;
+ }
+
+ // Always checking FINE - even if will not enforce. This will record the request for FINE
+ // so that a location request by the app is surfaced to the user.
+ boolean isFineLocationAllowed = noteAppOpAllowed(
+ AppOpsManager.OPSTR_FINE_LOCATION, pkgName, featureId, uid, message);
+ if (isFineLocationAllowed) {
+ return true;
+ }
+ if (coarseForTargetSdkLessThanQ && isTargetSdkLessThanQ) {
+ return noteAppOpAllowed(AppOpsManager.OPSTR_COARSE_LOCATION, pkgName, featureId, uid,
+ message);
+ }
+ return false;
+ }
+
+ /**
+ * Retrieves a handle to LocationManager (if not already done) and check if location is enabled.
+ */
+ public boolean isLocationModeEnabled() {
+ final LocationManager LocationManager = mContext.getSystemService(LocationManager.class);
+ try {
+ return LocationManager.isLocationEnabledForUser(UserHandle.of(
+ getCurrentUser()));
+ } catch (Exception e) {
+ Log.e(TAG, "Failure to get location mode via API, falling back to settings", e);
+ return false;
+ }
+ }
+
+ private boolean isTargetSdkLessThan(String packageName, int versionCode, int callingUid) {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ if (mContext.getPackageManager().getApplicationInfoAsUser(
+ packageName, 0,
+ UserHandle.getUserHandleForUid(callingUid)).targetSdkVersion
+ < versionCode) {
+ return true;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // In case of exception, assume unknown app (more strict checking)
+ // Note: This case will never happen since checkPackage is
+ // called to verify validity before checking App's version.
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ return false;
+ }
+
+ private boolean noteAppOpAllowed(String op, String pkgName, @Nullable String featureId,
+ int uid, @Nullable String message) {
+ return mAppOpsManager.noteOp(op, uid, pkgName, featureId, message)
+ == AppOpsManager.MODE_ALLOWED;
+ }
+
+ private void checkPackage(int uid, String pkgName)
+ throws SecurityException {
+ if (pkgName == null) {
+ throw new SecurityException("Checking UID " + uid + " but Package Name is Null");
+ }
+ mAppOpsManager.checkPackage(uid, pkgName);
+ }
+
+ @VisibleForTesting
+ protected int getCurrentUser() {
+ return ActivityManager.getCurrentUser();
+ }
+
+ private int getUidPermission(String permissionType, int uid) {
+ // We don't care about pid, pass in -1
+ return mContext.checkPermission(permissionType, -1, uid);
+ }
+
+ /**
+ * Returns true if the |uid| holds NETWORK_SETTINGS permission.
+ */
+ public boolean checkNetworkSettingsPermission(int uid) {
+ return getUidPermission(android.Manifest.permission.NETWORK_SETTINGS, uid)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ /**
+ * Returns true if the |uid| holds NETWORK_SETUP_WIZARD permission.
+ */
+ public boolean checkNetworkSetupWizardPermission(int uid) {
+ return getUidPermission(android.Manifest.permission.NETWORK_SETUP_WIZARD, uid)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ /**
+ * Returns true if the |uid| holds NETWORK_STACK permission.
+ */
+ public boolean checkNetworkStackPermission(int uid) {
+ return getUidPermission(android.Manifest.permission.NETWORK_STACK, uid)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ /**
+ * Returns true if the |uid| holds MAINLINE_NETWORK_STACK permission.
+ */
+ public boolean checkMainlineNetworkStackPermission(int uid) {
+ return getUidPermission(NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, uid)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+}
diff --git a/common/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java b/common/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java
index 3de78c6..4c7d675 100644
--- a/common/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java
+++ b/common/framework/com/android/net/module/util/NetworkCapabilitiesUtils.java
@@ -16,6 +16,20 @@
package com.android.net.module.util;
+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_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_MCX;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_XCAP;
import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET;
@@ -24,7 +38,9 @@
import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE;
import android.annotation.NonNull;
+import android.net.NetworkCapabilities;
+import com.android.internal.annotations.VisibleForTesting;
/**
* Utilities to examine {@link android.net.NetworkCapabilities}.
@@ -55,6 +71,83 @@
};
/**
+ * See android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE
+ * TODO: Use API constant when all downstream branches are S-based
+ */
+ public static final int NET_CAPABILITY_OEM_PRIVATE = 26;
+
+ /**
+ * See android.net.NetworkCapabilities.NET_CAPABILITY_VEHICLE_INTERNAL
+ * TODO: Use API constant when all downstream branches are S-based
+ */
+ public static final int NET_CAPABILITY_VEHICLE_INTERNAL = 27;
+
+ /**
+ * See android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED
+ * TODO: Use API constant when all downstream branches are S-based
+ */
+ public static final int NET_CAPABILITY_NOT_VCN_MANAGED = 28;
+
+ /**
+ * See android.net.NetworkCapabilities.NET_CAPABILITY_ENTERPRISE
+ * TODO: Use API constant when all downstream branches are S-based
+ */
+ public static final int NET_CAPABILITY_ENTERPRISE = 29;
+
+ /**
+ * See android.net.NetworkCapabilities.NET_CAPABILITY_VSIM
+ * TODO: Use API constant when all downstream branches are S-based
+ */
+ public static final int NET_CAPABILITY_VSIM = 30;
+
+ /**
+ * See android.net.NetworkCapabilities.NET_CAPABILITY_BIP
+ * TODO: Use API constant when all downstream branches are S-based
+ */
+ public static final int NET_CAPABILITY_BIP = 31;
+
+
+ /**
+ * Capabilities that suggest that a network is restricted.
+ * See {@code NetworkCapabilities#maybeMarkCapabilitiesRestricted},
+ * and {@code FORCE_RESTRICTED_CAPABILITIES}.
+ */
+ @VisibleForTesting
+ static final long RESTRICTED_CAPABILITIES =
+ (1 << NET_CAPABILITY_BIP)
+ | (1 << NET_CAPABILITY_CBS)
+ | (1 << NET_CAPABILITY_DUN)
+ | (1 << NET_CAPABILITY_EIMS)
+ | (1 << NET_CAPABILITY_ENTERPRISE)
+ | (1 << NET_CAPABILITY_FOTA)
+ | (1 << NET_CAPABILITY_IA)
+ | (1 << NET_CAPABILITY_IMS)
+ | (1 << NET_CAPABILITY_MCX)
+ | (1 << NET_CAPABILITY_RCS)
+ | (1 << NET_CAPABILITY_VEHICLE_INTERNAL)
+ | (1 << NET_CAPABILITY_VSIM)
+ | (1 << NET_CAPABILITY_XCAP);
+
+ /**
+ * Capabilities that force network to be restricted.
+ * See {@code NetworkCapabilities#maybeMarkCapabilitiesRestricted}.
+ */
+ private static final long FORCE_RESTRICTED_CAPABILITIES =
+ (1 << NET_CAPABILITY_OEM_PAID)
+ | (1 << NET_CAPABILITY_OEM_PRIVATE);
+
+ /**
+ * Capabilities that suggest that a network is unrestricted.
+ * See {@code NetworkCapabilities#maybeMarkCapabilitiesRestricted}.
+ */
+ @VisibleForTesting
+ static final long UNRESTRICTED_CAPABILITIES =
+ (1 << NET_CAPABILITY_INTERNET)
+ | (1 << NET_CAPABILITY_MMS)
+ | (1 << NET_CAPABILITY_SUPL)
+ | (1 << NET_CAPABILITY_WIFI_P2P);
+
+ /**
* Get a transport that can be used to classify a network when displaying its info to users.
*
* While networks can have multiple transports, users generally think of them as "wifi",
@@ -79,6 +172,41 @@
return transports[0];
}
+
+ /**
+ * Infers that all the capabilities it provides are typically provided by restricted networks
+ * or not.
+ *
+ * @param nc the {@link NetworkCapabilities} to infer the restricted capabilities.
+ *
+ * @return {@code true} if the network should be restricted.
+ */
+ // TODO: Use packBits(nc.getCapabilities()) to check more easily using bit masks.
+ public static boolean inferRestrictedCapability(NetworkCapabilities nc) {
+ // Check if we have any capability that forces the network to be restricted.
+ for (int capability : unpackBits(FORCE_RESTRICTED_CAPABILITIES)) {
+ if (nc.hasCapability(capability)) {
+ return true;
+ }
+ }
+
+ // Verify there aren't any unrestricted capabilities. If there are we say
+ // the whole thing is unrestricted unless it is forced to be restricted.
+ for (int capability : unpackBits(UNRESTRICTED_CAPABILITIES)) {
+ if (nc.hasCapability(capability)) {
+ return false;
+ }
+ }
+
+ // Must have at least some restricted capabilities.
+ for (int capability : unpackBits(RESTRICTED_CAPABILITIES)) {
+ if (nc.hasCapability(capability)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Unpacks long value into an array of bits.
*/
diff --git a/common/framework/com/android/net/module/util/NetworkStackConstants.java b/common/framework/com/android/net/module/util/NetworkStackConstants.java
index 499297c..b7062e7 100644
--- a/common/framework/com/android/net/module/util/NetworkStackConstants.java
+++ b/common/framework/com/android/net/module/util/NetworkStackConstants.java
@@ -186,6 +186,27 @@
*/
public static final int VENDOR_SPECIFIC_IE_ID = 0xdd;
+
+ /**
+ * TrafficStats constants.
+ */
+ // These tags are used by the network stack to do traffic for its own purposes. Traffic
+ // tagged with these will be counted toward the network stack and must stay inside the
+ // range defined by
+ // {@link android.net.TrafficStats#TAG_NETWORK_STACK_RANGE_START} and
+ // {@link android.net.TrafficStats#TAG_NETWORK_STACK_RANGE_END}.
+ public static final int TAG_SYSTEM_DHCP = 0xFFFFFE01;
+ public static final int TAG_SYSTEM_NEIGHBOR = 0xFFFFFE02;
+ public static final int TAG_SYSTEM_DHCP_SERVER = 0xFFFFFE03;
+
+ // These tags are used by the network stack to do traffic on behalf of apps. Traffic
+ // tagged with these will be counted toward the app on behalf of which the network
+ // stack is doing this traffic. These values must stay inside the range defined by
+ // {@link android.net.TrafficStats#TAG_NETWORK_STACK_IMPERSONATION_RANGE_START} and
+ // {@link android.net.TrafficStats#TAG_NETWORK_STACK_IMPERSONATION_RANGE_END}.
+ public static final int TAG_SYSTEM_PROBE = 0xFFFFFF81;
+ public static final int TAG_SYSTEM_DNS = 0xFFFFFF82;
+
// TODO: Move to Inet4AddressUtils
// See aosp/1455936: NetworkStackConstants can't depend on it as it causes jarjar-related issues
// for users of both the net-utils-device-common and net-utils-framework-common libraries.
diff --git a/common/tests/unit/Android.bp b/common/tests/unit/Android.bp
index 4ce4b0e..45a89b0 100644
--- a/common/tests/unit/Android.bp
+++ b/common/tests/unit/Android.bp
@@ -15,7 +15,7 @@
"androidx.test.rules",
"mockito-target-extended-minus-junit4",
"net-utils-device-common",
- "net-tests-utils-host-device-common",
+ "net-tests-utils",
],
libs: [
"android.test.runner",
diff --git a/common/tests/unit/src/com/android/net/module/util/Inet4AddressUtilsTest.java b/common/tests/unit/src/com/android/net/module/util/Inet4AddressUtilsTest.java
index 5d5ed91..702bdaf 100644
--- a/common/tests/unit/src/com/android/net/module/util/Inet4AddressUtilsTest.java
+++ b/common/tests/unit/src/com/android/net/module/util/Inet4AddressUtilsTest.java
@@ -26,9 +26,11 @@
import static com.android.net.module.util.Inet4AddressUtils.netmaskToPrefixLength;
import static com.android.net.module.util.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTH;
import static com.android.net.module.util.Inet4AddressUtils.prefixLengthToV4NetmaskIntHTL;
+import static com.android.net.module.util.Inet4AddressUtils.trimAddressZeros;
import static junit.framework.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import android.net.InetAddresses;
@@ -204,6 +206,17 @@
getPrefixMaskAsInet4Address(-1);
}
+ @Test
+ public void testTrimAddressZeros() {
+ assertNull(trimAddressZeros(null));
+ assertEquals("$invalid&", trimAddressZeros("$invalid&"));
+ assertEquals("example.com", trimAddressZeros("example.com"));
+ assertEquals("a.b.c.d", trimAddressZeros("a.b.c.d"));
+
+ assertEquals("192.0.2.2", trimAddressZeros("192.000.02.2"));
+ assertEquals("192.0.2.2", trimAddressZeros("192.0.2.2"));
+ }
+
private Inet4Address ipv4Address(String addr) {
return (Inet4Address) InetAddresses.parseNumericAddress(addr);
}
diff --git a/common/tests/unit/src/com/android/net/module/util/InetAddressUtilsTest.java b/common/tests/unit/src/com/android/net/module/util/InetAddressUtilsTest.java
new file mode 100644
index 0000000..2736c53
--- /dev/null
+++ b/common/tests/unit/src/com/android/net/module/util/InetAddressUtilsTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 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.net.module.util;
+
+import static junit.framework.Assert.assertEquals;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet6Address;
+import java.net.InetAddress;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class InetAddressUtilsTest {
+
+ private InetAddress parcelUnparcelAddress(InetAddress addr) {
+ Parcel p = Parcel.obtain();
+ InetAddressUtils.parcelInetAddress(p, addr, 0 /* flags */);
+ p.setDataPosition(0);
+ byte[] marshalled = p.marshall();
+ p.recycle();
+ p = Parcel.obtain();
+ p.unmarshall(marshalled, 0, marshalled.length);
+ p.setDataPosition(0);
+ InetAddress out = InetAddressUtils.unparcelInetAddress(p);
+ p.recycle();
+ return out;
+ }
+
+ @Test
+ public void testParcelUnparcelIpv4Address() throws Exception {
+ InetAddress ipv4 = InetAddress.getByName("192.0.2.1");
+ assertEquals(ipv4, parcelUnparcelAddress(ipv4));
+ }
+
+ @Test
+ public void testParcelUnparcelIpv6Address() throws Exception {
+ InetAddress ipv6 = InetAddress.getByName("2001:db8::1");
+ assertEquals(ipv6, parcelUnparcelAddress(ipv6));
+ }
+
+ @Test
+ public void testParcelUnparcelScopedIpv6Address() throws Exception {
+ InetAddress ipv6 = InetAddress.getByName("fe80::1%42");
+ assertEquals(42, ((Inet6Address) ipv6).getScopeId());
+ Inet6Address out = (Inet6Address) parcelUnparcelAddress(ipv6);
+ assertEquals(ipv6, out);
+ assertEquals(42, out.getScopeId());
+ }
+}
diff --git a/common/tests/unit/src/com/android/net/module/util/Ipv6UtilsTest.java b/common/tests/unit/src/com/android/net/module/util/Ipv6UtilsTest.java
new file mode 100644
index 0000000..03a1ec9
--- /dev/null
+++ b/common/tests/unit/src/com/android/net/module/util/Ipv6UtilsTest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2020 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.net.module.util;
+
+import static org.junit.Assert.assertEquals;
+
+import android.net.InetAddresses;
+import android.net.IpPrefix;
+import android.net.MacAddress;
+import android.system.OsConstants;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.net.module.util.structs.EthernetHeader;
+import com.android.net.module.util.structs.Icmpv6Header;
+import com.android.net.module.util.structs.Ipv6Header;
+import com.android.net.module.util.structs.PrefixInformationOption;
+import com.android.net.module.util.structs.RaHeader;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet6Address;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class Ipv6UtilsTest {
+
+ private static final MacAddress MAC1 = MacAddress.fromString("11:22:33:44:55:66");
+ private static final MacAddress MAC2 = MacAddress.fromString("aa:bb:cc:dd:ee:ff");
+ private static final Inet6Address LINK_LOCAL = addr("fe80::1");
+ private static final Inet6Address ROUTER_LINK_LOCAL = addr("fe80::cafe:d00d");
+ private static final Inet6Address ALL_ROUTERS =
+ NetworkStackConstants.IPV6_ADDR_ALL_ROUTERS_MULTICAST;
+ private static final Inet6Address ALL_NODES =
+ NetworkStackConstants.IPV6_ADDR_ALL_NODES_MULTICAST;
+
+ @Test
+ public void testBuildRsPacket() {
+ ByteBuffer b = Ipv6Utils.buildRsPacket(MAC1, MAC2, LINK_LOCAL, ALL_ROUTERS /* no opts */);
+
+ EthernetHeader eth = Struct.parse(EthernetHeader.class, b);
+ assertEquals(MAC1, eth.srcMac);
+ assertEquals(MAC2, eth.dstMac);
+
+ Ipv6Header ipv6 = Struct.parse(Ipv6Header.class, b);
+ assertEquals(255, ipv6.hopLimit);
+ assertEquals(OsConstants.IPPROTO_ICMPV6, ipv6.nextHeader);
+ assertEquals(LINK_LOCAL, ipv6.srcIp);
+ assertEquals(ALL_ROUTERS, ipv6.dstIp);
+
+ Icmpv6Header icmpv6 = Struct.parse(Icmpv6Header.class, b);
+ assertEquals(NetworkStackConstants.ICMPV6_ROUTER_SOLICITATION, icmpv6.type);
+ assertEquals(0, icmpv6.code);
+ }
+
+ @Test
+ public void testBuildRaPacket() {
+ final byte pioFlags =
+ NetworkStackConstants.PIO_FLAG_AUTONOMOUS | NetworkStackConstants.PIO_FLAG_ON_LINK;
+ ByteBuffer pio1 = PrefixInformationOption.build(new IpPrefix("2001:db8:1::/64"),
+ pioFlags, 3600 /* validLifetime */, 1800 /* preferredLifetime */);
+ ByteBuffer pio2 = PrefixInformationOption.build(new IpPrefix("fdcd:a17f:6502:1::/64"),
+ pioFlags, 86400 /* validLifetime */, 86400 /* preferredLifetime */);
+
+ ByteBuffer b = Ipv6Utils.buildRaPacket(MAC2, MAC1, ROUTER_LINK_LOCAL, ALL_NODES,
+ (byte) 0 /* flags */, 7200 /* lifetime */,
+ 30_000 /* reachableTime */, 750 /* retransTimer */,
+ pio1, pio2);
+
+ EthernetHeader eth = Struct.parse(EthernetHeader.class, b);
+ assertEquals(MAC2, eth.srcMac);
+ assertEquals(MAC1, eth.dstMac);
+
+ Ipv6Header ipv6 = Struct.parse(Ipv6Header.class, b);
+ assertEquals(255, ipv6.hopLimit);
+ assertEquals(OsConstants.IPPROTO_ICMPV6, ipv6.nextHeader);
+ assertEquals(ROUTER_LINK_LOCAL, ipv6.srcIp);
+ assertEquals(ALL_NODES, ipv6.dstIp);
+
+ Icmpv6Header icmpv6 = Struct.parse(Icmpv6Header.class, b);
+ assertEquals(NetworkStackConstants.ICMPV6_ROUTER_ADVERTISEMENT, icmpv6.type);
+ assertEquals(0, icmpv6.code);
+
+ RaHeader ra = Struct.parse(RaHeader.class, b);
+ assertEquals(0, ra.hopLimit); // Hop limit: unspecified.
+ assertEquals(0, ra.flags);
+ assertEquals(7200, ra.lifetime);
+ assertEquals(30_000, ra.reachableTime);
+ assertEquals(750, ra.retransTimer);
+
+ PrefixInformationOption pio = Struct.parse(PrefixInformationOption.class, b);
+ assertPioEquals(pio, "2001:db8:1::/64", pioFlags, 3600, 1800);
+ pio = Struct.parse(PrefixInformationOption.class, b);
+ assertPioEquals(pio, "fdcd:a17f:6502:1::/64", pioFlags, 86400, 86400);
+ }
+
+ @Test
+ public void testBuildEchoRequestPacket() {
+ final ByteBuffer b = Ipv6Utils.buildEchoRequestPacket(MAC2, MAC1, LINK_LOCAL, ALL_NODES);
+
+ EthernetHeader eth = Struct.parse(EthernetHeader.class, b);
+ assertEquals(MAC2, eth.srcMac);
+ assertEquals(MAC1, eth.dstMac);
+
+ Ipv6Header ipv6 = Struct.parse(Ipv6Header.class, b);
+ assertEquals(255, ipv6.hopLimit);
+ assertEquals(OsConstants.IPPROTO_ICMPV6, ipv6.nextHeader);
+ assertEquals(LINK_LOCAL, ipv6.srcIp);
+ assertEquals(ALL_NODES, ipv6.dstIp);
+
+ Icmpv6Header icmpv6 = Struct.parse(Icmpv6Header.class, b);
+ assertEquals(NetworkStackConstants.ICMPV6_ECHO_REQUEST_TYPE, icmpv6.type);
+ assertEquals(0, icmpv6.code);
+ }
+
+ private void assertPioEquals(PrefixInformationOption pio, String prefix, byte flags,
+ long valid, long preferred) {
+ assertEquals(NetworkStackConstants.ICMPV6_ND_OPTION_PIO, pio.type);
+ assertEquals(4, pio.length);
+ assertEquals(flags, pio.flags);
+ assertEquals(valid, pio.validLifetime);
+ assertEquals(preferred, pio.preferredLifetime);
+ IpPrefix expected = new IpPrefix(prefix);
+ IpPrefix actual = new IpPrefix(pio.prefix, pio.prefixLen);
+ assertEquals(expected, actual);
+ }
+
+ private static Inet6Address addr(String addr) {
+ return (Inet6Address) InetAddresses.parseNumericAddress(addr);
+ }
+
+ private byte[] slice(byte[] array, int length) {
+ return Arrays.copyOf(array, length);
+ }
+}
diff --git a/common/tests/unit/src/com/android/net/module/util/LocationPermissionCheckerTest.java b/common/tests/unit/src/com/android/net/module/util/LocationPermissionCheckerTest.java
new file mode 100644
index 0000000..3fb1e92
--- /dev/null
+++ b/common/tests/unit/src/com/android/net/module/util/LocationPermissionCheckerTest.java
@@ -0,0 +1,312 @@
+/*
+ * Copyright (C) 2020 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.net.module.util;
+
+import static android.Manifest.permission.NETWORK_SETTINGS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import com.android.testutils.DevSdkIgnoreRule;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.HashMap;
+
+/** Unit tests for {@link LocationPermissionChecker}. */
+public class LocationPermissionCheckerTest {
+ @Rule
+ public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule(
+ Build.VERSION_CODES.Q /* ignoreClassUpTo */);
+
+ // Mock objects for testing
+ @Mock private Context mMockContext;
+ @Mock private PackageManager mMockPkgMgr;
+ @Mock private ApplicationInfo mMockApplInfo;
+ @Mock private AppOpsManager mMockAppOps;
+ @Mock private UserManager mMockUserManager;
+ @Mock private LocationManager mLocationManager;
+
+ private static final String TEST_PKG_NAME = "com.google.somePackage";
+ private static final String TEST_FEATURE_ID = "com.google.someFeature";
+ private static final int MANAGED_PROFILE_UID = 1100000;
+ private static final int OTHER_USER_UID = 1200000;
+
+ private final String mInteractAcrossUsersFullPermission =
+ "android.permission.INTERACT_ACROSS_USERS_FULL";
+ private final String mManifestStringCoarse =
+ Manifest.permission.ACCESS_COARSE_LOCATION;
+ private final String mManifestStringFine =
+ Manifest.permission.ACCESS_FINE_LOCATION;
+
+ // Test variables
+ private int mWifiScanAllowApps;
+ private int mUid;
+ private int mCoarseLocationPermission;
+ private int mAllowCoarseLocationApps;
+ private int mFineLocationPermission;
+ private int mAllowFineLocationApps;
+ private int mNetworkSettingsPermission;
+ private int mCurrentUid;
+ private boolean mIsLocationEnabled;
+ private boolean mThrowSecurityException;
+ private Answer<Integer> mReturnPermission;
+ private HashMap<String, Integer> mPermissionsList = new HashMap<String, Integer>();
+ private LocationPermissionChecker mChecker;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ initTestVars();
+ }
+
+ private void setupMocks() throws Exception {
+ when(mMockPkgMgr.getApplicationInfoAsUser(eq(TEST_PKG_NAME), eq(0), any()))
+ .thenReturn(mMockApplInfo);
+ when(mMockContext.getPackageManager()).thenReturn(mMockPkgMgr);
+ when(mMockAppOps.noteOp(AppOpsManager.OPSTR_WIFI_SCAN, mUid, TEST_PKG_NAME,
+ TEST_FEATURE_ID, null)).thenReturn(mWifiScanAllowApps);
+ when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_COARSE_LOCATION), eq(mUid),
+ eq(TEST_PKG_NAME), eq(TEST_FEATURE_ID), nullable(String.class)))
+ .thenReturn(mAllowCoarseLocationApps);
+ when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_FINE_LOCATION), eq(mUid),
+ eq(TEST_PKG_NAME), eq(TEST_FEATURE_ID), nullable(String.class)))
+ .thenReturn(mAllowFineLocationApps);
+ if (mThrowSecurityException) {
+ doThrow(new SecurityException("Package " + TEST_PKG_NAME + " doesn't belong"
+ + " to application bound to user " + mUid))
+ .when(mMockAppOps).checkPackage(mUid, TEST_PKG_NAME);
+ }
+ mockSystemService(Context.APP_OPS_SERVICE, AppOpsManager.class, mMockAppOps);
+ mockSystemService(Context.USER_SERVICE, UserManager.class, mMockUserManager);
+ mockSystemService(Context.LOCATION_SERVICE, LocationManager.class, mLocationManager);
+ }
+
+ private <T> void mockSystemService(String name, Class<T> clazz, T service) {
+ when(mMockContext.getSystemService(name)).thenReturn(service);
+ when(mMockContext.getSystemServiceName(clazz)).thenReturn(name);
+ // Do not use mockito extended final method mocking
+ when(mMockContext.getSystemService(clazz)).thenCallRealMethod();
+ }
+
+ private void setupTestCase() throws Exception {
+ setupMocks();
+ setupMockInterface();
+ mChecker = new LocationPermissionChecker(mMockContext) {
+ @Override
+ protected int getCurrentUser() {
+ // Get the user ID of the process running the test rather than the foreground user
+ // id: ActivityManager.getCurrentUser() requires privileged permissions.
+ return UserHandle.getUserHandleForUid(Process.myUid()).getIdentifier();
+ }
+ };
+ }
+
+ private void initTestVars() {
+ mPermissionsList.clear();
+ mReturnPermission = createPermissionAnswer();
+ mWifiScanAllowApps = AppOpsManager.MODE_ERRORED;
+ mUid = OTHER_USER_UID;
+ mThrowSecurityException = true;
+ mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.M;
+ mIsLocationEnabled = false;
+ mCurrentUid = Process.myUid();
+ mCoarseLocationPermission = PackageManager.PERMISSION_DENIED;
+ mFineLocationPermission = PackageManager.PERMISSION_DENIED;
+ mAllowCoarseLocationApps = AppOpsManager.MODE_ERRORED;
+ mAllowFineLocationApps = AppOpsManager.MODE_ERRORED;
+ mNetworkSettingsPermission = PackageManager.PERMISSION_DENIED;
+ }
+
+ private void setupMockInterface() {
+ Binder.restoreCallingIdentity((((long) mUid) << 32) | Binder.getCallingPid());
+ doAnswer(mReturnPermission).when(mMockContext).checkPermission(
+ anyString(), anyInt(), anyInt());
+ when(mMockUserManager.isSameProfileGroup(UserHandle.SYSTEM,
+ UserHandle.getUserHandleForUid(MANAGED_PROFILE_UID)))
+ .thenReturn(true);
+ when(mMockContext.checkPermission(mManifestStringCoarse, -1, mUid))
+ .thenReturn(mCoarseLocationPermission);
+ when(mMockContext.checkPermission(mManifestStringFine, -1, mUid))
+ .thenReturn(mFineLocationPermission);
+ when(mMockContext.checkPermission(NETWORK_SETTINGS, -1, mUid))
+ .thenReturn(mNetworkSettingsPermission);
+ when(mLocationManager.isLocationEnabledForUser(any())).thenReturn(mIsLocationEnabled);
+ }
+
+ private Answer<Integer> createPermissionAnswer() {
+ return new Answer<Integer>() {
+ @Override
+ public Integer answer(InvocationOnMock invocation) {
+ int myUid = (int) invocation.getArguments()[1];
+ String myPermission = (String) invocation.getArguments()[0];
+ mPermissionsList.get(myPermission);
+ if (mPermissionsList.containsKey(myPermission)) {
+ int uid = mPermissionsList.get(myPermission);
+ if (myUid == uid) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+ }
+ return PackageManager.PERMISSION_DENIED;
+ }
+ };
+ }
+
+ @Test
+ public void testEnforceLocationPermission_HasAllPermissions_BeforeQ() throws Exception {
+ mIsLocationEnabled = true;
+ mThrowSecurityException = false;
+ mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED;
+ mAllowCoarseLocationApps = AppOpsManager.MODE_ALLOWED;
+ mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+ mUid = mCurrentUid;
+ setupTestCase();
+
+ final int result =
+ mChecker.checkLocationPermissionWithDetailInfo(
+ TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
+ assertEquals(LocationPermissionChecker.SUCCEEDED, result);
+ }
+
+ @Test
+ public void testEnforceLocationPermission_HasAllPermissions_AfterQ() throws Exception {
+ mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.Q;
+ mIsLocationEnabled = true;
+ mThrowSecurityException = false;
+ mUid = mCurrentUid;
+ mFineLocationPermission = PackageManager.PERMISSION_GRANTED;
+ mAllowFineLocationApps = AppOpsManager.MODE_ALLOWED;
+ mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+ setupTestCase();
+
+ final int result =
+ mChecker.checkLocationPermissionWithDetailInfo(
+ TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
+ assertEquals(LocationPermissionChecker.SUCCEEDED, result);
+ }
+
+ @Test
+ public void testEnforceLocationPermission_PkgNameAndUidMismatch() throws Exception {
+ mThrowSecurityException = true;
+ mIsLocationEnabled = true;
+ mFineLocationPermission = PackageManager.PERMISSION_GRANTED;
+ mAllowFineLocationApps = AppOpsManager.MODE_ALLOWED;
+ mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+ setupTestCase();
+
+ assertThrows(SecurityException.class,
+ () -> mChecker.checkLocationPermissionWithDetailInfo(
+ TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
+ }
+
+ @Test
+ public void testenforceCanAccessScanResults_NoCoarseLocationPermission() throws Exception {
+ mThrowSecurityException = false;
+ mIsLocationEnabled = true;
+ setupTestCase();
+
+ final int result =
+ mChecker.checkLocationPermissionWithDetailInfo(
+ TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
+ assertEquals(LocationPermissionChecker.ERROR_LOCATION_PERMISSION_MISSING, result);
+ }
+
+ @Test
+ public void testenforceCanAccessScanResults_NoFineLocationPermission() throws Exception {
+ mThrowSecurityException = false;
+ mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.Q;
+ mIsLocationEnabled = true;
+ mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED;
+ mAllowFineLocationApps = AppOpsManager.MODE_ERRORED;
+ mUid = MANAGED_PROFILE_UID;
+ setupTestCase();
+
+ final int result =
+ mChecker.checkLocationPermissionWithDetailInfo(
+ TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
+ assertEquals(LocationPermissionChecker.ERROR_LOCATION_PERMISSION_MISSING, result);
+ verify(mMockAppOps, never()).noteOp(anyInt(), anyInt(), anyString());
+ }
+
+ @Test
+ public void testenforceCanAccessScanResults_LocationModeDisabled() throws Exception {
+ mThrowSecurityException = false;
+ mUid = MANAGED_PROFILE_UID;
+ mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+ mPermissionsList.put(mInteractAcrossUsersFullPermission, mUid);
+ mIsLocationEnabled = false;
+
+ setupTestCase();
+
+ final int result =
+ mChecker.checkLocationPermissionWithDetailInfo(
+ TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
+ assertEquals(LocationPermissionChecker.ERROR_LOCATION_MODE_OFF, result);
+ }
+
+ @Test
+ public void testenforceCanAccessScanResults_LocationModeDisabledHasNetworkSettings()
+ throws Exception {
+ mThrowSecurityException = false;
+ mIsLocationEnabled = false;
+ mNetworkSettingsPermission = PackageManager.PERMISSION_GRANTED;
+ setupTestCase();
+
+ final int result =
+ mChecker.checkLocationPermissionWithDetailInfo(
+ TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
+ assertEquals(LocationPermissionChecker.SUCCEEDED, result);
+ }
+
+
+ private static void assertThrows(Class<? extends Exception> exceptionClass, Runnable r) {
+ try {
+ r.run();
+ Assert.fail("Expected " + exceptionClass + " to be thrown.");
+ } catch (Exception exception) {
+ assertTrue(exceptionClass.isInstance(exception));
+ }
+ }
+}
diff --git a/common/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt b/common/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt
index df2f459..5f15c6a 100644
--- a/common/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt
+++ b/common/tests/unit/src/com/android/net/module/util/NetworkCapabilitiesUtilsTest.kt
@@ -16,6 +16,12 @@
package com.android.net.module.util
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_CBS
+import android.net.NetworkCapabilities.NET_CAPABILITY_EIMS
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
+import android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID
import android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
@@ -25,6 +31,8 @@
import android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
+import com.android.net.module.util.NetworkCapabilitiesUtils.RESTRICTED_CAPABILITIES
+import com.android.net.module.util.NetworkCapabilitiesUtils.UNRESTRICTED_CAPABILITIES
import com.android.net.module.util.NetworkCapabilitiesUtils.getDisplayTransport
import com.android.net.module.util.NetworkCapabilitiesUtils.packBits
import com.android.net.module.util.NetworkCapabilitiesUtils.unpackBits
@@ -33,6 +41,7 @@
import java.lang.IllegalArgumentException
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
import kotlin.test.assertTrue
@RunWith(AndroidJUnit4::class)
@@ -78,4 +87,41 @@
assertEquals(packedBits, packBits(bits))
assertTrue(bits contentEquals unpackBits(packedBits))
}
+
+ @Test
+ fun testInferRestrictedCapability() {
+ val nc = NetworkCapabilities()
+ // Default capabilities don't have restricted capability.
+ assertFalse(NetworkCapabilitiesUtils.inferRestrictedCapability(nc))
+ // If there is a force restricted capability, then the network capabilities is restricted.
+ nc.addCapability(NET_CAPABILITY_OEM_PAID)
+ nc.addCapability(NET_CAPABILITY_INTERNET)
+ assertTrue(NetworkCapabilitiesUtils.inferRestrictedCapability(nc))
+ // Except for the force restricted capability, if there is any unrestricted capability in
+ // capabilities, then the network capabilities is not restricted.
+ nc.removeCapability(NET_CAPABILITY_OEM_PAID)
+ nc.addCapability(NET_CAPABILITY_CBS)
+ assertFalse(NetworkCapabilitiesUtils.inferRestrictedCapability(nc))
+ // Except for the force restricted capability, the network capabilities will only be treated
+ // as restricted when there is no any unrestricted capability.
+ nc.removeCapability(NET_CAPABILITY_INTERNET)
+ assertTrue(NetworkCapabilitiesUtils.inferRestrictedCapability(nc))
+ }
+
+ @Test
+ fun testRestrictedUnrestrictedCapabilities() {
+ // verify EIMS is restricted
+ assertEquals((1 shl NET_CAPABILITY_EIMS).toLong() and RESTRICTED_CAPABILITIES,
+ (1 shl NET_CAPABILITY_EIMS).toLong())
+
+ // verify CBS is also restricted
+ assertEquals((1 shl NET_CAPABILITY_CBS).toLong() and RESTRICTED_CAPABILITIES,
+ (1 shl NET_CAPABILITY_CBS).toLong())
+
+ // verify default is not restricted
+ assertEquals((1 shl NET_CAPABILITY_INTERNET).toLong() and RESTRICTED_CAPABILITIES, 0)
+
+ // just to see
+ assertEquals(RESTRICTED_CAPABILITIES and UNRESTRICTED_CAPABILITIES, 0)
+ }
}
diff --git a/common/tests/unit/src/com/android/net/module/util/TrackRecordTest.kt b/common/tests/unit/src/com/android/net/module/util/TrackRecordTest.kt
new file mode 100644
index 0000000..9fb4d8c
--- /dev/null
+++ b/common/tests/unit/src/com/android/net/module/util/TrackRecordTest.kt
@@ -0,0 +1,446 @@
+/*
+ * Copyright (C) 2019 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.net.module.util
+
+import com.android.testutils.ConcurrentInterpreter
+import com.android.testutils.InterpretException
+import com.android.testutils.InterpretMatcher
+import com.android.testutils.SyntaxException
+import com.android.testutils.__FILE__
+import com.android.testutils.__LINE__
+import com.android.testutils.intArg
+import com.android.testutils.strArg
+import com.android.testutils.timeArg
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import java.util.concurrent.CyclicBarrier
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicInteger
+import kotlin.system.measureTimeMillis
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import kotlin.test.fail
+
+val TEST_VALUES = listOf(4, 13, 52, 94, 41, 68, 11, 13, 51, 0, 91, 94, 33, 98, 14)
+const val ABSENT_VALUE = 2
+// Caution in changing these : some tests rely on the fact that TEST_TIMEOUT > 2 * SHORT_TIMEOUT
+// and LONG_TIMEOUT > 2 * TEST_TIMEOUT
+const val SHORT_TIMEOUT = 40L // ms
+const val TEST_TIMEOUT = 200L // ms
+const val LONG_TIMEOUT = 5000L // ms
+
+@RunWith(JUnit4::class)
+class TrackRecordTest {
+ @Test
+ fun testAddAndSizeAndGet() {
+ val repeats = 22 // arbitrary
+ val record = ArrayTrackRecord<Int>()
+ assertEquals(0, record.size)
+ repeat(repeats) { i -> record.add(i + 2) }
+ assertEquals(repeats, record.size)
+ record.add(2)
+ assertEquals(repeats + 1, record.size)
+
+ assertEquals(11, record[9])
+ assertEquals(11, record.getOrNull(9))
+ assertEquals(2, record[record.size - 1])
+ assertEquals(2, record.getOrNull(record.size - 1))
+
+ assertFailsWith<IndexOutOfBoundsException> { record[800] }
+ assertFailsWith<IndexOutOfBoundsException> { record[-1] }
+ assertFailsWith<IndexOutOfBoundsException> { record[repeats + 1] }
+ assertNull(record.getOrNull(800))
+ assertNull(record.getOrNull(-1))
+ assertNull(record.getOrNull(repeats + 1))
+ assertNull(record.getOrNull(800) { true })
+ assertNull(record.getOrNull(-1) { true })
+ assertNull(record.getOrNull(repeats + 1) { true })
+ }
+
+ @Test
+ fun testIndexOf() {
+ val record = ArrayTrackRecord<Int>()
+ TEST_VALUES.forEach { record.add(it) }
+ with(record) {
+ assertEquals(9, indexOf(0))
+ assertEquals(9, lastIndexOf(0))
+ assertEquals(1, indexOf(13))
+ assertEquals(7, lastIndexOf(13))
+ assertEquals(3, indexOf(94))
+ assertEquals(11, lastIndexOf(94))
+ assertEquals(-1, indexOf(ABSENT_VALUE))
+ assertEquals(-1, lastIndexOf(ABSENT_VALUE))
+ }
+ }
+
+ @Test
+ fun testContains() {
+ val record = ArrayTrackRecord<Int>()
+ TEST_VALUES.forEach { record.add(it) }
+ TEST_VALUES.forEach { assertTrue(record.contains(it)) }
+ assertFalse(record.contains(ABSENT_VALUE))
+ assertTrue(record.containsAll(TEST_VALUES))
+ assertTrue(record.containsAll(TEST_VALUES.sorted()))
+ assertTrue(record.containsAll(TEST_VALUES.sortedDescending()))
+ assertTrue(record.containsAll(TEST_VALUES.distinct()))
+ assertTrue(record.containsAll(TEST_VALUES.subList(0, TEST_VALUES.size / 2)))
+ assertTrue(record.containsAll(TEST_VALUES.subList(0, TEST_VALUES.size / 2).sorted()))
+ assertTrue(record.containsAll(listOf()))
+ assertFalse(record.containsAll(listOf(ABSENT_VALUE)))
+ assertFalse(record.containsAll(TEST_VALUES + listOf(ABSENT_VALUE)))
+ }
+
+ @Test
+ fun testEmpty() {
+ val record = ArrayTrackRecord<Int>()
+ assertTrue(record.isEmpty())
+ record.add(1)
+ assertFalse(record.isEmpty())
+ }
+
+ @Test
+ fun testIterate() {
+ val record = ArrayTrackRecord<Int>()
+ record.forEach { fail("Expected nothing to iterate") }
+ TEST_VALUES.forEach { record.add(it) }
+ // zip relies on the iterator (this calls extension function Iterable#zip(Iterable))
+ record.zip(TEST_VALUES).forEach { assertEquals(it.first, it.second) }
+ // Also test reverse iteration (to test hasPrevious() and friends)
+ record.reversed().zip(TEST_VALUES.reversed()).forEach { assertEquals(it.first, it.second) }
+ }
+
+ @Test
+ fun testIteratorIsSnapshot() {
+ val record = ArrayTrackRecord<Int>()
+ TEST_VALUES.forEach { record.add(it) }
+ val iterator = record.iterator()
+ val expectedSize = record.size
+ record.add(ABSENT_VALUE)
+ record.add(ABSENT_VALUE)
+ var measuredSize = 0
+ iterator.forEach {
+ ++measuredSize
+ assertNotEquals(ABSENT_VALUE, it)
+ }
+ assertEquals(expectedSize, measuredSize)
+ }
+
+ @Test
+ fun testSublist() {
+ val record = ArrayTrackRecord<Int>()
+ TEST_VALUES.forEach { record.add(it) }
+ assertEquals(record.subList(3, record.size - 3),
+ TEST_VALUES.subList(3, TEST_VALUES.size - 3))
+ }
+
+ fun testPollReturnsImmediately(record: TrackRecord<Int>) {
+ record.add(4)
+ val elapsed = measureTimeMillis { assertEquals(4, record.poll(LONG_TIMEOUT, 0)) }
+ // Should not have waited at all, in fact.
+ assertTrue(elapsed < LONG_TIMEOUT)
+ record.add(7)
+ record.add(9)
+ // Can poll multiple times for the same position, in whatever order
+ assertEquals(9, record.poll(0, 2))
+ assertEquals(7, record.poll(Long.MAX_VALUE, 1))
+ assertEquals(9, record.poll(0, 2))
+ assertEquals(4, record.poll(0, 0))
+ assertEquals(9, record.poll(0, 2) { it > 5 })
+ assertEquals(7, record.poll(0, 0) { it > 5 })
+ }
+
+ @Test
+ fun testPollReturnsImmediately() {
+ testPollReturnsImmediately(ArrayTrackRecord())
+ testPollReturnsImmediately(ArrayTrackRecord<Int>().newReadHead())
+ }
+
+ @Test
+ fun testPollTimesOut() {
+ val record = ArrayTrackRecord<Int>()
+ var delay = measureTimeMillis { assertNull(record.poll(SHORT_TIMEOUT, 0)) }
+ assertTrue(delay >= SHORT_TIMEOUT, "Delay $delay < $SHORT_TIMEOUT")
+ delay = measureTimeMillis { assertNull(record.poll(SHORT_TIMEOUT, 0) { it < 10 }) }
+ assertTrue(delay >= SHORT_TIMEOUT)
+ }
+
+ @Test
+ fun testConcurrentPollDisallowed() {
+ val failures = AtomicInteger(0)
+ val readHead = ArrayTrackRecord<Int>().newReadHead()
+ val barrier = CyclicBarrier(2)
+ Thread {
+ barrier.await(LONG_TIMEOUT, TimeUnit.MILLISECONDS) // barrier 1
+ try {
+ readHead.poll(LONG_TIMEOUT)
+ } catch (e: ConcurrentModificationException) {
+ failures.incrementAndGet()
+ // Unblock the other thread
+ readHead.add(0)
+ }
+ }.start()
+ barrier.await() // barrier 1
+ try {
+ readHead.poll(LONG_TIMEOUT)
+ } catch (e: ConcurrentModificationException) {
+ failures.incrementAndGet()
+ // Unblock the other thread
+ readHead.add(0)
+ }
+ // One of the threads must have gotten an exception.
+ assertEquals(failures.get(), 1)
+ }
+
+ @Test
+ fun testPollWakesUp() {
+ val record = ArrayTrackRecord<Int>()
+ val barrier = CyclicBarrier(2)
+ Thread {
+ barrier.await(LONG_TIMEOUT, TimeUnit.MILLISECONDS) // barrier 1
+ barrier.await() // barrier 2
+ Thread.sleep(SHORT_TIMEOUT * 2)
+ record.add(31)
+ }.start()
+ barrier.await() // barrier 1
+ // Should find the element in more than SHORT_TIMEOUT but less than TEST_TIMEOUT
+ var delay = measureTimeMillis {
+ barrier.await() // barrier 2
+ assertEquals(31, record.poll(TEST_TIMEOUT, 0))
+ }
+ assertTrue(delay in SHORT_TIMEOUT..TEST_TIMEOUT)
+ // Polling for an element already added in anothe thread (pos 0) : should return immediately
+ delay = measureTimeMillis { assertEquals(31, record.poll(TEST_TIMEOUT, 0)) }
+ assertTrue(delay < TEST_TIMEOUT, "Delay $delay > $TEST_TIMEOUT")
+ // Waiting for an element that never comes
+ delay = measureTimeMillis { assertNull(record.poll(SHORT_TIMEOUT, 1)) }
+ assertTrue(delay >= SHORT_TIMEOUT, "Delay $delay < $SHORT_TIMEOUT")
+ // Polling for an element that doesn't match what is already there
+ delay = measureTimeMillis { assertNull(record.poll(SHORT_TIMEOUT, 0) { it < 10 }) }
+ assertTrue(delay >= SHORT_TIMEOUT)
+ }
+
+ // Just make sure the interpreter actually throws an exception when the spec
+ // does not conform to the behavior. The interpreter is just a tool to test a
+ // tool used for a tool for test, let's not have hundreds of tests for it ;
+ // if it's broken one of the tests using it will break.
+ @Test
+ fun testInterpreter() {
+ val interpretLine = __LINE__ + 2
+ try {
+ TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """
+ add(4) | poll(1, 0) = 5
+ """)
+ fail("This spec should have thrown")
+ } catch (e: InterpretException) {
+ assertTrue(e.cause is AssertionError)
+ assertEquals(interpretLine + 1, e.stackTrace[0].lineNumber)
+ assertTrue(e.stackTrace[0].fileName.contains(__FILE__))
+ assertTrue(e.stackTrace[0].methodName.contains("testInterpreter"))
+ assertTrue(e.stackTrace[0].methodName.contains("thread1"))
+ }
+ }
+
+ @Test
+ fun testMultipleAdds() {
+ TRTInterpreter.interpretTestSpec(useReadHeads = false, spec = """
+ add(2) | | |
+ | add(4) | |
+ | | add(6) |
+ | | | add(8)
+ poll(0, 0) = 2 time 0..1 | poll(0, 0) = 2 | poll(0, 0) = 2 | poll(0, 0) = 2
+ poll(0, 1) = 4 time 0..1 | poll(0, 1) = 4 | poll(0, 1) = 4 | poll(0, 1) = 4
+ poll(0, 2) = 6 time 0..1 | poll(0, 2) = 6 | poll(0, 2) = 6 | poll(0, 2) = 6
+ poll(0, 3) = 8 time 0..1 | poll(0, 3) = 8 | poll(0, 3) = 8 | poll(0, 3) = 8
+ """)
+ }
+
+ @Test
+ fun testConcurrentAdds() {
+ TRTInterpreter.interpretTestSpec(useReadHeads = false, spec = """
+ add(2) | add(4) | add(6) | add(8)
+ add(1) | add(3) | add(5) | add(7)
+ poll(0, 1) is even | poll(0, 0) is even | poll(0, 3) is even | poll(0, 2) is even
+ poll(0, 5) is odd | poll(0, 4) is odd | poll(0, 7) is odd | poll(0, 6) is odd
+ """)
+ }
+
+ @Test
+ fun testMultiplePoll() {
+ TRTInterpreter.interpretTestSpec(useReadHeads = false, spec = """
+ add(4) | poll(1, 0) = 4
+ | poll(0, 1) = null time 0..1
+ | poll(1, 1) = null time 1..2
+ sleep; add(7) | poll(2, 1) = 7 time 1..2
+ sleep; add(18) | poll(2, 2) = 18 time 1..2
+ """)
+ }
+
+ @Test
+ fun testMultiplePollWithPredicate() {
+ TRTInterpreter.interpretTestSpec(useReadHeads = false, spec = """
+ | poll(1, 0) = null | poll(1, 0) = null
+ add(6) | poll(1, 0) = 6 |
+ add(11) | poll(1, 0) { > 20 } = null | poll(1, 0) { = 11 } = 11
+ | poll(1, 0) { > 8 } = 11 |
+ """)
+ }
+
+ @Test
+ fun testMultipleReadHeads() {
+ TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """
+ | poll() = null | poll() = null | poll() = null
+ add(5) | | poll() = 5 |
+ | poll() = 5 | |
+ add(8) | poll() = 8 | poll() = 8 |
+ | | | poll() = 5
+ | | | poll() = 8
+ | | | poll() = null
+ | | poll() = null |
+ """)
+ }
+
+ @Test
+ fun testReadHeadPollWithPredicate() {
+ TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """
+ add(5) | poll() { < 0 } = null
+ | poll() { > 5 } = null
+ add(10) |
+ | poll() { = 5 } = null // The "5" was skipped in the previous line
+ add(15) | poll() { > 8 } = 15 // The "10" was skipped in the previous line
+ | poll(1, 0) { > 8 } = 10 // 10 is the first element after pos 0 matching > 8
+ """)
+ }
+
+ @Test
+ fun testPollImmediatelyAdvancesReadhead() {
+ TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """
+ add(1) | add(2) | add(3) | add(4)
+ mark = 0 | poll(0) { > 3 } = 4 | |
+ poll(0) { > 10 } = null | | |
+ mark = 4 | | |
+ poll() = null | | |
+ """)
+ }
+
+ @Test
+ fun testParallelReadHeads() {
+ TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """
+ mark = 0 | mark = 0 | mark = 0 | mark = 0
+ add(2) | | |
+ | add(4) | |
+ | | add(6) |
+ | | | add(8)
+ poll() = 2 | poll() = 2 | poll() = 2 | poll() = 2
+ poll() = 4 | poll() = 4 | poll() = 4 | poll() = 4
+ poll() = 6 | poll() = 6 | poll() = 6 | mark = 2
+ poll() = 8 | poll() = 8 | mark = 3 | poll() = 6
+ mark = 4 | mark = 4 | poll() = 8 | poll() = 8
+ """)
+ }
+
+ @Test
+ fun testPeek() {
+ TRTInterpreter.interpretTestSpec(useReadHeads = true, spec = """
+ add(2) | | |
+ | add(4) | |
+ | | add(6) |
+ | | | add(8)
+ peek() = 2 | poll() = 2 | poll() = 2 | peek() = 2
+ peek() = 2 | peek() = 4 | poll() = 4 | peek() = 2
+ peek() = 2 | peek() = 4 | peek() = 6 | poll() = 2
+ peek() = 2 | mark = 1 | mark = 2 | poll() = 4
+ mark = 0 | peek() = 4 | peek() = 6 | peek() = 6
+ poll() = 2 | poll() = 4 | poll() = 6 | poll() = 6
+ poll() = 4 | mark = 2 | poll() = 8 | peek() = 8
+ peek() = 6 | peek() = 6 | peek() = null | mark = 3
+ """)
+ }
+}
+
+private object TRTInterpreter : ConcurrentInterpreter<TrackRecord<Int>>(interpretTable) {
+ fun interpretTestSpec(spec: String, useReadHeads: Boolean) = if (useReadHeads) {
+ interpretTestSpec(spec, initial = ArrayTrackRecord(),
+ threadTransform = { (it as ArrayTrackRecord).newReadHead() })
+ } else {
+ interpretTestSpec(spec, ArrayTrackRecord())
+ }
+}
+
+/*
+ * Quick ref of supported expressions :
+ * sleep(x) : sleeps for x time units and returns Unit ; sleep alone means sleep(1)
+ * add(x) : calls and returns TrackRecord#add.
+ * poll(time, pos) [{ predicate }] : calls and returns TrackRecord#poll(x time units, pos).
+ * Optionally, a predicate may be specified.
+ * poll() [{ predicate }] : calls and returns ReadHead#poll(1 time unit). Optionally, a predicate
+ * may be specified.
+ * EXPR = VALUE : asserts that EXPR equals VALUE. EXPR is interpreted. VALUE can either be the
+ * string "null" or an int. Returns Unit.
+ * EXPR time x..y : measures the time taken by EXPR and asserts it took at least x and at most
+ * y time units.
+ * predicate must be one of "= x", "< x" or "> x".
+ */
+private val interpretTable = listOf<InterpretMatcher<TrackRecord<Int>>>(
+ // Interpret "XXX is odd" : run XXX and assert its return value is odd ("even" works too)
+ Regex("(.*)\\s+is\\s+(even|odd)") to { i, t, r ->
+ i.interpret(r.strArg(1), t).also {
+ assertEquals((it as Int) % 2, if ("even" == r.strArg(2)) 0 else 1)
+ }
+ },
+ // Interpret "add(XXX)" as TrackRecord#add(int)
+ Regex("""add\((\d+)\)""") to { i, t, r ->
+ t.add(r.intArg(1))
+ },
+ // Interpret "poll(x, y)" as TrackRecord#poll(timeout = x * INTERPRET_TIME_UNIT, pos = y)
+ // Accepts an optional {} argument for the predicate (see makePredicate for syntax)
+ Regex("""poll\((\d+),\s*(\d+)\)\s*(\{.*\})?""") to { i, t, r ->
+ t.poll(r.timeArg(1), r.intArg(2), makePredicate(r.strArg(3)))
+ },
+ // ReadHead#poll. If this throws in the cast, the code is malformed and has passed "poll()"
+ // in a test that takes a TrackRecord that is not a ReadHead. It's technically possible to get
+ // the test code to not compile instead of throw, but it's vastly more complex and this will
+ // fail 100% at runtime any test that would not have compiled.
+ Regex("""poll\((\d+)?\)\s*(\{.*\})?""") to { i, t, r ->
+ (if (r.strArg(1).isEmpty()) i.interpretTimeUnit else r.timeArg(1)).let { time ->
+ (t as ArrayTrackRecord<Int>.ReadHead).poll(time, makePredicate(r.strArg(2)))
+ }
+ },
+ // ReadHead#mark. The same remarks apply as with ReadHead#poll.
+ Regex("mark") to { i, t, _ -> (t as ArrayTrackRecord<Int>.ReadHead).mark },
+ // ReadHead#peek. The same remarks apply as with ReadHead#poll.
+ Regex("peek\\(\\)") to { i, t, _ -> (t as ArrayTrackRecord<Int>.ReadHead).peek() }
+)
+
+// Parses a { = x } or { < x } or { > x } string and returns the corresponding predicate
+// Returns an always-true predicate for empty and null arguments
+private fun makePredicate(spec: String?): (Int) -> Boolean {
+ if (spec.isNullOrEmpty()) return { true }
+ val match = Regex("""\{\s*([<>=])\s*(\d+)\s*\}""").matchEntire(spec)
+ ?: throw SyntaxException("Predicate \"${spec}\"")
+ val arg = match.intArg(2)
+ return when (match.strArg(1)) {
+ ">" -> { i -> i > arg }
+ "<" -> { i -> i < arg }
+ "=" -> { i -> i == arg }
+ else -> throw RuntimeException("How did \"${spec}\" match this regexp ?")
+ }
+}
diff --git a/common/testutils/Android.bp b/common/testutils/Android.bp
new file mode 100644
index 0000000..d4465dd
--- /dev/null
+++ b/common/testutils/Android.bp
@@ -0,0 +1,55 @@
+// Copyright (C) 2021 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+ name: "net-tests-utils",
+ srcs: [
+ "devicetests/**/*.java",
+ "devicetests/**/*.kt",
+ ],
+ defaults: ["lib_mockito_extended"],
+ libs: [
+ "androidx.annotation_annotation",
+ ],
+ static_libs: [
+ "androidx.test.ext.junit",
+ "kotlin-reflect",
+ "libnanohttpd",
+ "net-tests-utils-host-device-common",
+ "net-utils-device-common",
+ ],
+}
+
+java_library {
+ // Consider using net-tests-utils instead if writing device code.
+ // That library has a lot more useful tools into it for users that
+ // work on Android and includes this lib.
+ name: "net-tests-utils-host-device-common",
+ srcs: [
+ "hostdevice/**/*.java",
+ "hostdevice/**/*.kt",
+ ],
+ host_supported: true,
+ visibility: [
+ "//frameworks/libs/net/common/tests:__subpackages__",
+ "//frameworks/libs/net/client-libs/tests:__subpackages__",
+ ],
+ static_libs: [
+ "kotlin-test"
+ ]
+}
diff --git a/common/devicetests/com/android/testutils/ArpResponder.kt b/common/testutils/devicetests/com/android/testutils/ArpResponder.kt
similarity index 100%
rename from common/devicetests/com/android/testutils/ArpResponder.kt
rename to common/testutils/devicetests/com/android/testutils/ArpResponder.kt
diff --git a/common/devicetests/com/android/testutils/CompatUtil.kt b/common/testutils/devicetests/com/android/testutils/CompatUtil.kt
similarity index 91%
rename from common/devicetests/com/android/testutils/CompatUtil.kt
rename to common/testutils/devicetests/com/android/testutils/CompatUtil.kt
index 4bb90a8..ff8c668 100644
--- a/common/devicetests/com/android/testutils/CompatUtil.kt
+++ b/common/testutils/devicetests/com/android/testutils/CompatUtil.kt
@@ -27,7 +27,7 @@
fun makeTestNetworkSpecifier(ifName: String): NetworkSpecifier {
// Until R, there was no TestNetworkSpecifier, StringNetworkSpecifier was used instead
if (isDevSdkInRange(minExclusive = null, maxInclusive = Build.VERSION_CODES.R)) {
- makeNetworkSpecifierInternal("android.net.StringNetworkSpecifier", ifName)
+ return makeNetworkSpecifierInternal("android.net.StringNetworkSpecifier", ifName)
}
// TestNetworkSpecifier is not part of the SDK in some branches using this utility
// TODO: replace with a direct call to the constructor
@@ -38,7 +38,7 @@
fun makeEthernetNetworkSpecifier(ifName: String): NetworkSpecifier {
// Until R, there was no EthernetNetworkSpecifier, StringNetworkSpecifier was used instead
if (isDevSdkInRange(minExclusive = null, maxInclusive = Build.VERSION_CODES.R)) {
- makeNetworkSpecifierInternal("android.net.StringNetworkSpecifier", ifName)
+ return makeNetworkSpecifierInternal("android.net.StringNetworkSpecifier", ifName)
}
// EthernetNetworkSpecifier is not part of the SDK in some branches using this utility
// TODO: replace with a direct call to the constructor
diff --git a/common/devicetests/com/android/testutils/ConcurrentInterpreter.kt b/common/testutils/devicetests/com/android/testutils/ConcurrentInterpreter.kt
similarity index 100%
rename from common/devicetests/com/android/testutils/ConcurrentInterpreter.kt
rename to common/testutils/devicetests/com/android/testutils/ConcurrentInterpreter.kt
diff --git a/common/devicetests/com/android/testutils/DevSdkIgnoreRule.kt b/common/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt
similarity index 100%
rename from common/devicetests/com/android/testutils/DevSdkIgnoreRule.kt
rename to common/testutils/devicetests/com/android/testutils/DevSdkIgnoreRule.kt
diff --git a/common/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt b/common/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
similarity index 100%
rename from common/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
rename to common/testutils/devicetests/com/android/testutils/DevSdkIgnoreRunner.kt
diff --git a/common/devicetests/com/android/testutils/FakeDns.kt b/common/testutils/devicetests/com/android/testutils/FakeDns.kt
similarity index 100%
rename from common/devicetests/com/android/testutils/FakeDns.kt
rename to common/testutils/devicetests/com/android/testutils/FakeDns.kt
diff --git a/common/devicetests/com/android/testutils/HandlerUtils.kt b/common/testutils/devicetests/com/android/testutils/HandlerUtils.kt
similarity index 100%
rename from common/devicetests/com/android/testutils/HandlerUtils.kt
rename to common/testutils/devicetests/com/android/testutils/HandlerUtils.kt
diff --git a/common/testutils/devicetests/com/android/testutils/NetworkStatsProviderCbStubCompat.java b/common/testutils/devicetests/com/android/testutils/NetworkStatsProviderCbStubCompat.java
new file mode 100644
index 0000000..e84a224
--- /dev/null
+++ b/common/testutils/devicetests/com/android/testutils/NetworkStatsProviderCbStubCompat.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 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.testutils;
+
+import android.net.NetworkStats;
+import android.net.netstats.provider.INetworkStatsProviderCallback;
+import android.os.RemoteException;
+
+/**
+ * A shim class that allows {@link TestableNetworkStatsProviderCbBinder} to be built against
+ * different SDK versions.
+ */
+public class NetworkStatsProviderCbStubCompat extends INetworkStatsProviderCallback.Stub {
+ @Override
+ public void notifyStatsUpdated(int token, NetworkStats ifaceStats, NetworkStats uidStats)
+ throws RemoteException {}
+
+ @Override
+ public void notifyAlertReached() throws RemoteException {}
+
+ // Removed in S.
+ public void notifyLimitReached() throws RemoteException {}
+
+ // Added in S.
+ public void notifyWarningOrLimitReached() throws RemoteException {}
+
+ @Override
+ public void unregister() throws RemoteException {}
+}
diff --git a/common/testutils/devicetests/com/android/testutils/NetworkStatsProviderStubCompat.java b/common/testutils/devicetests/com/android/testutils/NetworkStatsProviderStubCompat.java
new file mode 100644
index 0000000..a77aa02
--- /dev/null
+++ b/common/testutils/devicetests/com/android/testutils/NetworkStatsProviderStubCompat.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 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.testutils;
+
+import android.net.netstats.provider.INetworkStatsProvider;
+
+/**
+ * A shim class that allows {@link TestableNetworkStatsProviderBinder} to be built against
+ * different SDK versions.
+ */
+public class NetworkStatsProviderStubCompat extends INetworkStatsProvider.Stub {
+ @Override
+ public void onRequestStatsUpdate(int token) {}
+
+ // Removed and won't be called in S+.
+ public void onSetLimit(String iface, long quotaBytes) {}
+
+ @Override
+ public void onSetAlert(long bytes) {}
+
+ // Added in S.
+ public void onSetWarningAndLimit(String iface, long warningBytes, long limitBytes) {}
+}
diff --git a/common/devicetests/com/android/testutils/NetworkStatsUtils.kt b/common/testutils/devicetests/com/android/testutils/NetworkStatsUtils.kt
similarity index 100%
rename from common/devicetests/com/android/testutils/NetworkStatsUtils.kt
rename to common/testutils/devicetests/com/android/testutils/NetworkStatsUtils.kt
diff --git a/common/devicetests/com/android/testutils/PacketResponder.kt b/common/testutils/devicetests/com/android/testutils/PacketResponder.kt
similarity index 100%
rename from common/devicetests/com/android/testutils/PacketResponder.kt
rename to common/testutils/devicetests/com/android/testutils/PacketResponder.kt
diff --git a/common/devicetests/com/android/testutils/ParcelUtils.kt b/common/testutils/devicetests/com/android/testutils/ParcelUtils.kt
similarity index 100%
rename from common/devicetests/com/android/testutils/ParcelUtils.kt
rename to common/testutils/devicetests/com/android/testutils/ParcelUtils.kt
diff --git a/common/devicetests/com/android/testutils/TapPacketReader.java b/common/testutils/devicetests/com/android/testutils/TapPacketReader.java
similarity index 100%
rename from common/devicetests/com/android/testutils/TapPacketReader.java
rename to common/testutils/devicetests/com/android/testutils/TapPacketReader.java
diff --git a/common/devicetests/com/android/testutils/TapPacketReaderRule.kt b/common/testutils/devicetests/com/android/testutils/TapPacketReaderRule.kt
similarity index 100%
rename from common/devicetests/com/android/testutils/TapPacketReaderRule.kt
rename to common/testutils/devicetests/com/android/testutils/TapPacketReaderRule.kt
diff --git a/common/devicetests/com/android/testutils/TestHttpServer.kt b/common/testutils/devicetests/com/android/testutils/TestHttpServer.kt
similarity index 100%
rename from common/devicetests/com/android/testutils/TestHttpServer.kt
rename to common/testutils/devicetests/com/android/testutils/TestHttpServer.kt
diff --git a/common/devicetests/com/android/testutils/TestNetworkTracker.kt b/common/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt
similarity index 100%
rename from common/devicetests/com/android/testutils/TestNetworkTracker.kt
rename to common/testutils/devicetests/com/android/testutils/TestNetworkTracker.kt
diff --git a/common/devicetests/com/android/testutils/TestPermissionUtil.kt b/common/testutils/devicetests/com/android/testutils/TestPermissionUtil.kt
similarity index 100%
rename from common/devicetests/com/android/testutils/TestPermissionUtil.kt
rename to common/testutils/devicetests/com/android/testutils/TestPermissionUtil.kt
diff --git a/common/devicetests/com/android/testutils/TestableNetworkCallback.kt b/common/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
similarity index 90%
rename from common/devicetests/com/android/testutils/TestableNetworkCallback.kt
rename to common/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
index 043654f..1b3d0f6 100644
--- a/common/devicetests/com/android/testutils/TestableNetworkCallback.kt
+++ b/common/testutils/devicetests/com/android/testutils/TestableNetworkCallback.kt
@@ -25,6 +25,7 @@
import com.android.net.module.util.ArrayTrackRecord
import com.android.testutils.RecorderCallback.CallbackEntry.Available
import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus
+import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatusInt
import com.android.testutils.RecorderCallback.CallbackEntry.CapabilitiesChanged
import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged
import com.android.testutils.RecorderCallback.CallbackEntry.Losing
@@ -82,7 +83,10 @@
override val network: Network,
val blocked: Boolean
) : CallbackEntry()
-
+ data class BlockedStatusInt(
+ override val network: Network,
+ val blocked: Int
+ ) : CallbackEntry()
// Convenience constants for expecting a type
companion object {
@JvmField
@@ -103,6 +107,8 @@
val UNAVAILABLE = Unavailable::class
@JvmField
val BLOCKED_STATUS = BlockedStatus::class
+ @JvmField
+ val BLOCKED_STATUS_INT = BlockedStatusInt::class
}
}
@@ -131,6 +137,9 @@
history.add(BlockedStatus(network, blocked))
}
+ // Cannot do:
+ // fun onBlockedStatusChanged(network: Network, blocked: Int) {
+ // because on S, that needs to be "override fun", and on R, that cannot be "override fun".
override fun onNetworkSuspended(network: Network) {
Log.d(TAG, "onNetworkSuspended $network $network")
history.add(Suspended(network))
@@ -272,13 +281,33 @@
blocked: Boolean = false,
tmt: Long = defaultTimeoutMs
) {
+ expectAvailableCallbacksCommon(net, suspended, validated, tmt)
+ expectBlockedStatusCallback(blocked, net, tmt)
+ }
+
+ fun expectAvailableCallbacks(
+ net: Network,
+ suspended: Boolean,
+ validated: Boolean,
+ blockedStatus: Int,
+ tmt: Long
+ ) {
+ expectAvailableCallbacksCommon(net, suspended, validated, tmt)
+ expectBlockedStatusCallback(blockedStatus, net)
+ }
+
+ private fun expectAvailableCallbacksCommon(
+ net: Network,
+ suspended: Boolean,
+ validated: Boolean,
+ tmt: Long
+ ) {
expectCallback<Available>(net, tmt)
if (suspended) {
expectCallback<Suspended>(net, tmt)
}
expectCapabilitiesThat(net, tmt) { validated == it.hasCapability(NET_CAPABILITY_VALIDATED) }
expectCallback<LinkPropertiesChanged>(net, tmt)
- expectBlockedStatusCallback(blocked, net)
}
// Backward compatibility for existing Java code. Use named arguments instead and remove all
@@ -295,6 +324,12 @@
}
}
+ fun expectBlockedStatusCallback(blocked: Int, net: Network, tmt: Long = defaultTimeoutMs) {
+ expectCallback<BlockedStatusInt>(net, tmt).also {
+ assertEquals(it.blocked, blocked, "Unexpected blocked status ${it.blocked}")
+ }
+ }
+
// Expects the available callbacks (where the onCapabilitiesChanged must contain the
// VALIDATED capability), plus another onCapabilitiesChanged which is identical to the
// one we just sent.
@@ -314,6 +349,16 @@
expectCapabilitiesThat(net, tmt) { it.hasCapability(NET_CAPABILITY_VALIDATED) }
}
+ fun expectAvailableThenValidatedCallbacks(
+ net: Network,
+ blockedStatus: Int,
+ tmt: Long = defaultTimeoutMs
+ ) {
+ expectAvailableCallbacks(net, validated = false, suspended = false,
+ blockedStatus = blockedStatus, tmt = tmt)
+ expectCapabilitiesThat(net, tmt) { it.hasCapability(NET_CAPABILITY_VALIDATED) }
+ }
+
// Temporary Java compat measure : have MockNetworkAgent implement this so that all existing
// calls with networkAgent can be routed through here without moving MockNetworkAgent.
// TODO: clean this up, remove this method.
diff --git a/common/devicetests/com/android/testutils/TestableNetworkStatsProvider.kt b/common/testutils/devicetests/com/android/testutils/TestableNetworkStatsProvider.kt
similarity index 100%
rename from common/devicetests/com/android/testutils/TestableNetworkStatsProvider.kt
rename to common/testutils/devicetests/com/android/testutils/TestableNetworkStatsProvider.kt
diff --git a/common/devicetests/com/android/testutils/TestableNetworkStatsProviderBinder.kt b/common/testutils/devicetests/com/android/testutils/TestableNetworkStatsProviderBinder.kt
similarity index 73%
rename from common/devicetests/com/android/testutils/TestableNetworkStatsProviderBinder.kt
rename to common/testutils/devicetests/com/android/testutils/TestableNetworkStatsProviderBinder.kt
index 02922d8..643346b 100644
--- a/common/devicetests/com/android/testutils/TestableNetworkStatsProviderBinder.kt
+++ b/common/testutils/devicetests/com/android/testutils/TestableNetworkStatsProviderBinder.kt
@@ -16,18 +16,21 @@
package com.android.testutils
-import android.net.netstats.provider.INetworkStatsProvider
import com.android.net.module.util.ArrayTrackRecord
import kotlin.test.assertEquals
import kotlin.test.fail
private const val DEFAULT_TIMEOUT_MS = 200L
-open class TestableNetworkStatsProviderBinder : INetworkStatsProvider.Stub() {
+open class TestableNetworkStatsProviderBinder : NetworkStatsProviderStubCompat() {
sealed class CallbackType {
data class OnRequestStatsUpdate(val token: Int) : CallbackType()
- data class OnSetLimit(val iface: String?, val quotaBytes: Long) : CallbackType()
data class OnSetAlert(val quotaBytes: Long) : CallbackType()
+ data class OnSetWarningAndLimit(
+ val iface: String,
+ val warningBytes: Long,
+ val limitBytes: Long
+ ) : CallbackType()
}
private val history = ArrayTrackRecord<CallbackType>().ReadHead()
@@ -36,20 +39,21 @@
history.add(CallbackType.OnRequestStatsUpdate(token))
}
- override fun onSetLimit(iface: String?, quotaBytes: Long) {
- history.add(CallbackType.OnSetLimit(iface, quotaBytes))
- }
-
override fun onSetAlert(quotaBytes: Long) {
history.add(CallbackType.OnSetAlert(quotaBytes))
}
+ override fun onSetWarningAndLimit(iface: String, warningBytes: Long, limitBytes: Long) {
+ history.add(CallbackType.OnSetWarningAndLimit(iface, warningBytes, limitBytes))
+ }
+
fun expectOnRequestStatsUpdate(token: Int) {
assertEquals(CallbackType.OnRequestStatsUpdate(token), history.poll(DEFAULT_TIMEOUT_MS))
}
- fun expectOnSetLimit(iface: String?, quotaBytes: Long) {
- assertEquals(CallbackType.OnSetLimit(iface, quotaBytes), history.poll(DEFAULT_TIMEOUT_MS))
+ fun expectOnSetWarningAndLimit(iface: String, warningBytes: Long, limitBytes: Long) {
+ assertEquals(CallbackType.OnSetWarningAndLimit(iface, warningBytes, limitBytes),
+ history.poll(DEFAULT_TIMEOUT_MS))
}
fun expectOnSetAlert(quotaBytes: Long) {
diff --git a/common/devicetests/com/android/testutils/TestableNetworkStatsProviderCbBinder.kt b/common/testutils/devicetests/com/android/testutils/TestableNetworkStatsProviderCbBinder.kt
similarity index 85%
rename from common/devicetests/com/android/testutils/TestableNetworkStatsProviderCbBinder.kt
rename to common/testutils/devicetests/com/android/testutils/TestableNetworkStatsProviderCbBinder.kt
index 163473a..f15f610 100644
--- a/common/devicetests/com/android/testutils/TestableNetworkStatsProviderCbBinder.kt
+++ b/common/testutils/devicetests/com/android/testutils/TestableNetworkStatsProviderCbBinder.kt
@@ -17,7 +17,6 @@
package com.android.testutils
import android.net.NetworkStats
-import android.net.netstats.provider.INetworkStatsProviderCallback
import com.android.net.module.util.ArrayTrackRecord
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@@ -25,14 +24,14 @@
private const val DEFAULT_TIMEOUT_MS = 3000L
-open class TestableNetworkStatsProviderCbBinder : INetworkStatsProviderCallback.Stub() {
+open class TestableNetworkStatsProviderCbBinder : NetworkStatsProviderCbStubCompat() {
sealed class CallbackType {
data class NotifyStatsUpdated(
val token: Int,
val ifaceStats: NetworkStats,
val uidStats: NetworkStats
) : CallbackType()
- object NotifyLimitReached : CallbackType()
+ object NotifyWarningOrLimitReached : CallbackType()
object NotifyAlertReached : CallbackType()
object Unregister : CallbackType()
}
@@ -43,8 +42,8 @@
history.add(CallbackType.NotifyStatsUpdated(token, ifaceStats, uidStats))
}
- override fun notifyLimitReached() {
- history.add(CallbackType.NotifyLimitReached)
+ override fun notifyWarningOrLimitReached() {
+ history.add(CallbackType.NotifyWarningOrLimitReached)
}
override fun notifyAlertReached() {
@@ -70,8 +69,8 @@
assertNetworkStatsEquals(uidStats, event.uidStats)
}
- fun expectNotifyLimitReached() =
- assertEquals(CallbackType.NotifyLimitReached, history.poll(DEFAULT_TIMEOUT_MS))
+ fun expectNotifyWarningOrLimitReached() =
+ assertEquals(CallbackType.NotifyWarningOrLimitReached, history.poll(DEFAULT_TIMEOUT_MS))
fun expectNotifyAlertReached() =
assertEquals(CallbackType.NotifyAlertReached, history.poll(DEFAULT_TIMEOUT_MS))
diff --git a/common/hostdevice/com/android/net/module/util/TrackRecord.kt b/common/testutils/hostdevice/com/android/net/module/util/TrackRecord.kt
similarity index 100%
rename from common/hostdevice/com/android/net/module/util/TrackRecord.kt
rename to common/testutils/hostdevice/com/android/net/module/util/TrackRecord.kt
diff --git a/common/hostdevice/com/android/testutils/ConcurrentUtils.kt b/common/testutils/hostdevice/com/android/testutils/ConcurrentUtils.kt
similarity index 100%
rename from common/hostdevice/com/android/testutils/ConcurrentUtils.kt
rename to common/testutils/hostdevice/com/android/testutils/ConcurrentUtils.kt
diff --git a/common/hostdevice/com/android/testutils/ExceptionUtils.java b/common/testutils/hostdevice/com/android/testutils/ExceptionUtils.java
similarity index 100%
rename from common/hostdevice/com/android/testutils/ExceptionUtils.java
rename to common/testutils/hostdevice/com/android/testutils/ExceptionUtils.java
diff --git a/common/hostdevice/com/android/testutils/FileUtils.kt b/common/testutils/hostdevice/com/android/testutils/FileUtils.kt
similarity index 100%
rename from common/hostdevice/com/android/testutils/FileUtils.kt
rename to common/testutils/hostdevice/com/android/testutils/FileUtils.kt
diff --git a/common/hostdevice/com/android/testutils/MiscAsserts.kt b/common/testutils/hostdevice/com/android/testutils/MiscAsserts.kt
similarity index 96%
rename from common/hostdevice/com/android/testutils/MiscAsserts.kt
rename to common/testutils/hostdevice/com/android/testutils/MiscAsserts.kt
index 91fb1cd..a8c6b3c 100644
--- a/common/hostdevice/com/android/testutils/MiscAsserts.kt
+++ b/common/testutils/hostdevice/com/android/testutils/MiscAsserts.kt
@@ -32,8 +32,8 @@
assertEquals(0, len, "Expected empty array, but length was $len")
}
-fun <T> assertEmpty(ts: List<T>) = ts.size.let { len ->
- assertEquals(0, len, "Expected empty list, but length was $len")
+fun <T> assertEmpty(ts: Collection<T>) = ts.size.let { len ->
+ assertEquals(0, len, "Expected empty collection, but length was $len")
}
fun <T> assertLength(expected: Int, got: Array<T>) = got.size.let { len ->
diff --git a/common/hostdevice/com/android/testutils/PacketFilter.kt b/common/testutils/hostdevice/com/android/testutils/PacketFilter.kt
similarity index 100%
rename from common/hostdevice/com/android/testutils/PacketFilter.kt
rename to common/testutils/hostdevice/com/android/testutils/PacketFilter.kt
diff --git a/common/hostdevice/com/android/testutils/SkipPresubmit.kt b/common/testutils/hostdevice/com/android/testutils/SkipPresubmit.kt
similarity index 100%
rename from common/hostdevice/com/android/testutils/SkipPresubmit.kt
rename to common/testutils/hostdevice/com/android/testutils/SkipPresubmit.kt